Hoe onze Drupal helpers-library voor efficiënte code zorgt
Kwatongen beweren weleens dat Drupal-developers 'clickers' zijn in plaats van 'coders'. Elke Drupal-developer weet natuurlijk dat dat nergens op slaat. Een blik in de interne keuken van Intracto geeft genoeg tegenargumenten. Kijk maar even mee.

Bij Intracto maken we heel wat Drupal-projecten op maat van de klant, platformen die veel verder gaan dan de standaard Drupal 8-website. We zorgen voor custom overviews, zoekintegraties, custom entities ... Allemaal volgens de specificaties van het project in kwestie.
Vaak moeten we entities (nodes) die een contentmanager heeft ingegeven gaan manipuleren en preprocessen. En als je iets vaak genoeg doet, vind je altijd wel manieren om het efficiënter te doen.
Onze Drupal helpers-library
Je zou kunnen scripten dat het koffieapparaat begint te werken zodra je opstaat van je bureau zodat je kopje klaarstaat zodra je bij het toestel komt, of handige codesnippets voorzien die je kan hergebruiken.
Onze Drupal entity helpers-library kan je niet helpen om efficiënter aan koffie te geraken, maar wel om makkelijker entity properties te fetchen.
Kijk zelf maar eens:
Een praktijkvoorbeeld
Een klant wilt een overzicht van artikels op de site. Niet alleen zijn er filters beschikbaar om enkel de artikels van bepaalde categorieën te tonen, er is ook een tekstveld om mee te zoeken.
Wanneer een gebruiker dat zoekveld gebruikt, wilt de klant dat de volledige artikels doorzocht worden en dat er rekening gehouden wordt met tikfouten, gelijkaardige woorden en ga zo maar door.
We kozen ervoor om Elasticsearch te gebruiken om het overzicht op de bouwen. En in plaats van search_api te gebruiken, schrijven we onze eigen integratie.
Zonder in detail te treden over hoe het Elasticsearch-gedeelte werkt, zoomen we even in op hoe de artikels geïndexeerd worden in Elasticsearch. Om dat voor mekaar te krijgen, moet de node omgezet worden in een Elasticsearch-document. Daar gaan we even dieper op in.
De node-velden
Onze artikel-node heeft de volgende eigenschappen of velden:
- Een titel
- Een beschrijving
- Een referentie naar de taxonomieterm binnen de woordenlijst
Het Elasticsearch-document
Ons Elasticsearch-document heeft deze mapping:
$mapping = [
'nid' => [
'type' => 'integer',
],
'title' => [
'type' => 'text',
],
'category' => [
'type' => 'text',
'fields' => [
'raw' => [
'type' => 'keyword',
],
]
],
];
De syntax voor Elasticsearch is onbelangrijk. Het is vooral belangrijk dat we weten dat we deze velden moeten verwerken:
- nid
- title
- category
Transformer-class
Een artikel-node wordt uiteindelijk omgezet in een 'Transformer'-class. Die bevat een functie waarmee de node omgezet wordt in een array. Met dat array wordt er dan een Elasticsearch-document aangemaakt.
/**
* Creates an array that contains the Elasticsearch mapping values.
*
* @param \Drupal\node\NodeInterface $node
* The node that needs to be transformed.
*
* @return array
* An array containing values used for an Elasticsearch document.
*/
protected function nodeToDocumentValues(NodeInterface $node) : array {
if ($node->bundle() !== 'article') {
return [];
}
return [
'nid' => $node->id(),
'title' => $node->label(),
'category' => $this->getCategoryName($node),
];
}
Onze standaardwaarden zoals 'nid' en 'title' kunnen makkelijk uit de node entity zelf gehaald worden. Die bevat immers functies om ze op te halen.
Onze 'description' en 'category' zijn een ander paar mouwen, omdat we de veldwaarden moeten ophalen. Die zijn niet beschikbaar als functies.
De meerwaarde van onze Drupal helpers
Onze Drupal helpers doen het zware werk voor ons. Neem bijvoorbeeld de getCategories-functie:
/**
* Returns the description of the node.
*
* @param \Drupal\node\NodeInterface $node
* The node that contains a description.
*
* @return array
* An array containing all category names.
*/
protected function getCategories(NodeInterface $node): array {
if (!$node->hasField('field_category')) {
return [];
}
$categories = $node->get('field_category');
if ($categories->isEmpty()) {
return [];
}
$transformedCategories = [];
$nodeLangcode = $node->language()->getId();
foreach ($categories as $category) {
$categoryEntity = $category->entity;
if (!$categoryEntity instanceof TranslatableInterface) {
continue;
}
if (!$categoryEntity->hasTranslation($nodeLangcode)) {
continue;
}
$categoryEntity = $categoryEntity->getTranslation($nodeLangcode);
$transformedCategories[] = $categoryEntity->label();
}
return $transformedCategories;
}
Het doel van deze functie is om alle categorieën die toegewezen zijn aan het artikel op te halen. Daarnaast moet ook de juiste vertaling van het label opgehaald worden, maar alleen als het beschikbaar is in die taal.
Problemen en oplossingen
Dat werkt, maar we moeten heel wat checks inbouwen om ervoor te zorgen dat onze applicatie geen error uitlokt. Voor één functie is dat geen ramp, maar als je verschillende functies met gelijkaardige taken hebt (bv. getTypes, getReferencedArticles ...), moet je gelijkaardige code schrijven.
Daarom hebben we een helpers-library gemaakt, zodat we dit soort dingen makkelijker kunnen doen zonder ons zorgen te hoeven maken over de checks in onze function-code. Het risico op een foutmelding ligt dus veel lager.
Dankzij onze EntityHelpers-library kunnen we de bovenstaande functie vervangen door:
/**
* Returns the description of the node.
*
* @param \Drupal\node\NodeInterface $node
* The node that contains a description.
*
* @return array
* An array containing all category names.
*/
protected function getCategories(NodeInterface $node): array {
$categories = $this->getReferencedEntitiesByField($node, 'field_category', TRUE, TRUE);
if (empty($categories)) {
return [];
}
$transformedCategories = [];
foreach ($categories as $category) {
$transformedCategories[] = $category->label();
}
return $transformedCategories;
}
De getReferencedEntitiesByField-functie wordt mee opgenomen dankzij onze helper-trait:
use Intracto\DrupalHelpers\Traits\EntityHelperTrait;
use EntityHelperTrait;
Zo krijg je een hoop extra functionaliteiten, bv. 'getReferencedEntitiesByField'.
/**
* Returns the referenced entities from a entity_reference field.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity for which to retrieve the field value.
* @param string $field
* The name of the field to return.
* @param bool $translated
* Use the current entity langcode to return translated entities?
* @param bool $removeUntranslated
* Skip a referenced entity that is not translated in the correct langcode.
*
* @return array
*/
public function getReferencedEntitiesByField(EntityInterface $entity, string $field, bool $translated = TRUE, bool $removeUntranslated = FALSE) : array {
if (!$list = $this->getEntityFieldList($entity, $field)) {
return [];
}
$entities = [];
foreach ($list as $fieldItem) {
$entity = $fieldItem->entity;
if (!$entity) {
continue;
}
$entities[] = $entity;
}
if (!$translated) {
return $entities;
}
$activeLangcode = $entity->language()->getId();
return $this->translateEntities($entities, $activeLangcode, $removeUntranslated);
}
Je hoeft enkel een entiteit door te geven, de naam van het veld. Je kan aangeven of je de terugkerende entiteiten wil vertalen en of je entiteiten die niet vertaald kunnen worden in de taal van de doorgegeven entiteit wil verwijderen.
De belangrijkste voordelen van onze helpers-library
Door deze library te gebruiken kan je tijd besparen omdat je geen checks meer moet schrijven wanneer je node-waarden wil opvragen. Er zitten verschillende functies in de trait:
- Je kan de gerefereerde entities ophalen.
- Je kan een reeks entities vertalen door een langcode te gebruiken
- Je kan de eenvoudige waarde van een entity-veld ophalen
- Je kan een veldenlijst opvragen
- ...
En de basischecks worden automatisch uitgevoerd:
- Bestaat het veld? Indien niet: NULL
- Kan de entiteit vertaald worden? Indien niet, geef gewoon weer.
- Heeft het veld waardes? Indien niet: NULL
Kortom, heel wat manieren die het verwerken van entities een hoop makkelijker maken.
Doe je voordeel met onze library
Je vindt onze volledige library hier: https://github.com/Intracto/drupal-helpers. Draag gerust bij met merge request, geef je suggesties voor verbeteringen of download de library en repackage hem als je eigen library *.
* Maar niet heus. In dat geval weten we je te vinden, met of zonder Elasticsearch.