Notificatiesysteem in Drupal 8

Drupal 8 notificatiesysteem

Notificatiesysteem in Drupal 8

Ons intranet zorgt voor de automatisatie van onze facturatie, het opvolgen van alle taken en budgetten en bevat ons digitaal SCRUM board. Tot voor kort deden we de opvolging en controle manueel, maar daar brengt ons nieuw notificatiesysteem nu verandering in.

We implementeerden notificaties zoals gekend op Facebook, Twitter en andere social media-platformen en -apps. Als team werken we dagdagelijks op het Intranet, dus is het de perfecte plaats om notificaties in te bouwen om de aandacht te vestigen op interne triggers of events. Deze notificaties worden getoond achter een icoontje met teller. Voorbeelden van notificaties zijn:

  • het budget van een project of story bereikt een bepaald percentage;
  • je bent vergeten je bubbletimer (tijdsregistratie) in te vullen.

Voor de technische geesten onder ons doen we graag uit de doeken hoe we dit hebben aangepakt.

Custom Entity

Al onze notificaties worden opgeslagen in een custom entity “Notification”. Dit laat ons toe om de notificaties te gebruiken in Views, Display Suite, Rules, enz. Deze entity bevat volgende velden:

  • Label
  • Read (boolean die aangeeft of de notifcatie is gelezen)
  • Type
  • Severity (string die de prioriteit van de notificatie aangeeft)
  • URL (de URL waar de notificatie naar moet verwijzen wanneer erop geklikt wordt)

Notificatietypes

Om het systeem makkelijk aan te passen en uit te breiden, is elke notificatie een klasse die de interface "NotificationInterface" implementeert. De structuur ziet er als volgt uit:

/**
 * Interface NotificationInterface
 * @package Drupal\notification\Model
 */
interface NotificationInterface {

  /**
   * @return string
   */
  public function getTitle();

  /**
   * @return UserInterface
   */
  public function getUser();

  /**
   * @return string
   */
  public function getType();

  /**
   * @return string
   */
  public function getSeverity();

  /**
   * @return string
   */
  public function getUri();

}

Verder hebben we ook een abstracte klasse "BaseNotification" voorzien, die wat basisfunctionaliteit zoals de constructor en de methode "save()" bevat. Daarnaast definieert deze klasse ook 2 abstracte methodes "setSeverity()" en "setType()". Elke concrete klasse moet deze functies dus implementeren. Samen met de interface zorgt deze klasse voor het raamwerk van ons model.

/**
 * Class BaseNotification
 * @package Drupal\notification\Model
 */
abstract class BaseNotification implements NotificationInterface {

  protected $type;
  protected $severity;
  protected $title;
  protected $user;
  protected $uri;

  /**
   * BaseNotification constructor.
   * @param \Drupal\user\UserInterface $user
   * @param $title
   * @param $uri
   */
  public function __construct(UserInterface $user, $title, Url $uri) {
    $this->user = $user;
    $this->title = $title;
    $this->type = $this->setType();
    $this->severity = $this->setSeverity();
    $this->uri = $uri;
  }

  [...]

  /**
   * Saves notification as entity.
   * @return \Drupal\Core\Entity\EntityInterface|static
   */
  public function save() {
    return Notification::create([
      'title' => $this->getTitle(),
      'notification_type' => $this->getType(),
      'notification_severity' => $this->getSeverity(),
      'read' => 0,
      'notification_uri' => $this->getUri()->toString(),
      'user_id' => $this->getUser()->id(),
    ])->save();
  }

  /**
   * Returns type.
   * @return mixed
   */
  protected abstract function setType();

  /**
   * Returns severity.
   * @return mixed
   */
  protected abstract function setSeverity();
}

Een concrete notificatieklasse zal er dus zo uit zien:

/**
 * Class MyAwesomeNotification
 * @package Drupal\notification\Model
 */
class MyAwesomeNotification extends BaseNotification {

  /**
   * @inheritdoc
   */
  protected function setType() {
    return 'my_awesome_notfication';
  }

  /**
   * @inheritdoc
   */
  protected function setSeverity() {
    return 'critical';
  }

}

Aangepaste plug-ins

Het systeem voorziet (nog) geen push-notificaties. Deze worden allemaal in cron aangemaakt. De creatie van de notificaties gebeurt aan de hand van een custom plug-in. Op deze manier kunnen andere modules inhaken op het systeem en hun eigen notificaties aanmaken. Dit weerhoudt je er echter niet van om notificaties te creëren op bepaalde gebeurtenissen buiten de cronjob. Lees hier meer over de plug-in API in Drupal 8.

De eerste stap in het creëren van een eigen plug-in is het definiëren van een annotatie. Onze annotatie heeft (voorlopig) enkel een ID nodig. Dit wordt als volgt geïmplementeerd:

/**
 * Defines a NotificationCronRule item annotation object.
 *
 * @Annotation
 */
class NotificationCronRule extends Plugin {

  /**
   * The plugin ID.
   *
   * @var string
   */
  public $id;

}

De volgende stap is het aanmaken van de plug-inmanager. Deze zorgt voor laden en creëren van instanties van "NotificationCronRule".

/**
 * Class NotificationCronRuleManager
 * @package Drupal\notification\Manager
 */
class NotificationCronRuleManager extends DefaultPluginManager implements NotificationCronRuleManagerInterface {

  /**
   * {@inheritdoc}
   */
  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
    parent::__construct('Plugin/notification_cron_rule', $namespaces, $module_handler, 'Drupal\notification\NotificationCronRulePluginInterface', 'Drupal\notification\Annotation\NotificationCronRule');
  }

  /**
   * {@inheritdoc}
   */
  public function loadInstance($plugin_id) {
    return $this->createInstance($plugin_id);
  }

  /**
   * {@inheritdoc}
   */
  public function loadAllInstances() {
    $instances = array();
    foreach ($this->getDefinitions() as $definition) {
      array_push($instances, $this->createInstance($definition['id']));
    }

    return $instances;
  }
}

De laatste stap is het aanmaken van een "base plugin" die al wat standaard functionaliteit implementeert.

/**
 * Class NotificationCronRule.
 */
abstract class NotificationCronRulePluginBase extends PluginBase implements NotificationCronRulePluginInterface, ContainerFactoryPluginInterface {

  protected $connection;
  protected $notificationManager;

  /**
   * NotificationCronRulePluginBase constructor.
   * @param array $configuration
   * @param string $plugin_id
   * @param mixed $plugin_definition
   * @param \Drupal\Core\Database\Connection $connection
   * @param \Drupal\notification\Manager\NotificationManager $notification_manager
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $connection, NotificationManager $notification_manager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);

    $this->connection = $connection;
    $this->notificationManager = $notification_manager;
  }

  [...]
}

Nu is het systeem klaar om alle "notification cron rules" op te pikken en eventuele notificaties aan te maken. Zoals eerder vermeld, gebeurt dit in "hook_cron()":

/**
 * Implements hook_cron().
 */
function notification_cron() {
  /** @var \Drupal\notification\Manager\NotificationCronRuleManagerInterface $manager */
  $manager = Drupal::service('notification.cron_rule_manager');
  $rules = $manager->loadAllInstances();

  foreach ($rules as $rule) {
    /** @var \Drupal\notification\NotificationCronRulePluginInterface $rule */
    $rule->execute();
  }
}

Elke module kan nu een plug-in van het type "NotificationCronRule" aanmaken. Dit kan door volgende klasse aan te maken in my_module/Plugin/notification_cron_rule:

/**
 * @NotificationCronRule(
 *   id = "my_awesome_cron_rule"
 * )
 */
class MyAwesomeCronRule extends NotificationCronRulePluginBase {
  /**
   * @inheritdoc
   */
  public function execute() {
   // Check if a notification of type "MyAwesomeNotification" has to be created.
   ...
  }
}

Rendering

Nu de hele back-end in elkaar zit, moeten de notificaties nog aan de gebruiker worden getoond. Het weergeven van de notificaties hebben we geïmplementeerd met de AJAX-API van Drupal.

Bij het laden van de pagina wordt enkel het aantal nieuwe notificaties weergegeven. Hierop kan geklikt worden, waarna de meest recente notificaties via AJAX worden opgehaald. Dit zorgt voor het sneller laden van de pagina.

Example notifications

 

Ultimate Cron

Omdat we momenteel alle notificaties in een cronjob aanmaken, hebben we de interval van cron op serverniveau moeten veranderen van één maal per dag naar elke 15 minuten. Dit wil zeggen dat elke 15 minuten alle implementaties van "hook_cron()" worden uitgevoerd. Dit is natuurlijk overkill en potentieel een grote belasting voor ons intranet. Gelukkig kunnen we dit oplossen met behulp van Ultimate Cron. Deze module is als het ware een laag tussen de cron-daemon op Linux en de standaard Drupal-cron, waarbij je de frequentie van elke implementatie van "hook_cron()" apart kan instellen om zo de belasting van de server minimaal te houden.

ultimate cron example

Blijf op de hoogte via onze nieuwsbrief