Entity API

Le entity sono oggetti utilizzati per la memorizzazione persistente di contenuti e configurazione e l'Entity API è il sistema utilizzato da Drupal per rappresentarli e gestirli. Ogni entità appartiene a uno dei due entity type: content entity type e configuration entity type. Alcuni content entity type supportano inoltre dei sottotipi chiamati bundle (content type per i node, vocabolari per i taxonomy term, media type per i media).

Content entity type

  • node
  • user
  • taxonomy term
  • media
  • comment

Configuration entity type

  • content type
  • Views
  • user roles
  • block configuration
  • field configuration

Base field e configurable field

I base fields sono, ad esempio per i nodi: nid, langcode, title, status (published o unpublished), created, promoted, sticky. I configurable fields sono aggiunti tramite configurazione e vengono descritti attraverso configuration entity.

Esempio di creazione del content type Weather alert nel file config/install/node.type.weather_alert.yml (verrà creato all'installazione del modulo):

langcode: en 
status: true 
dependencies: 
  enforced: 
    module: 
      - nome_modulo 
name: 'Weather alert' 
type: weather_alert 
description: 'Provides weather alerts for a geographical area.' 
help: null 
new_revision: true 
preview_mode: 1 
display_submitted: true

Field API

Field Type

Definisce il tipo di campo, ad esempio list_string indica un stringa che può avere un valore tra una lista predefinita.

Field widget

Definisce come il valore viene inserito dall'utente nella UI, ad esempio un campo list_string può essere inserito con un tag <select> o con dei radio buttons.

Field formatter

Definisce come il valore viene visualizzato a forntend.

Field storage configuration

Definisce caratteristiche che valgono per tutto l'entity type, ad esempio nome del campo, cardinalità (quanti valori può avere il campo), storage settings. Ad esempio nel file config/install/field.storage.node.field_alert_level.yml:

langcode: en 
status: true 
dependencies: 
  module: 
    - options 

id: node.field_alert_level 
field_name: field_alert_level 
entity_type: node 
type: list_string 

settings: 
  allowed_values: 
    - 
      value: advisory 
      label: Advisory 
    - 
      value: warning 
      label: Warning 
    - 
      value: severe 
      label: Severe 
  allowed_values_function: '' 

module: options 
locked: false 
cardinality: 1 
translatable: false 
indexes: { } 
persist_with_no_fields: false 
custom_storage: false

Field configuration

Definisce cose come label, descrizione, se è required, valore di default.

Validazione

Prima di utilizzare un field bisogna assicurarsi che questo esista e abbia un valore:

// verificare che esista
if ( $node->hasField('field_alert_level') ) { 
  // ... 
}

// per verificare che abbia un valore
if ( 
    !$node 
      ->get('field_alert_level') 
      ->isEmpty() 
) { 
   // ... 
}

Per verificare che non violi nessuna regola:

$violations = $alert->validate(); 

if ($violations->count() === 0) { 
    $alert->save(); 
}

// leggere le violazioni
foreach ($violations as $violation) { 
  $message = $violation->getMessage(); 
  $path = $violation->getPropertyPath(); 
}

Per verificare che il salvataggio sia andato a buon fine:

// valori possibili SAVED_NEW o SAVED_UPDATED
$result = $alert->save();

// dopo il primo salvataggio questo restituisce FALSE
$alert->isNew();

// dopo il primo salvataggio è disponibile
$alert->id();

Per verificare che il nodo caricato sia del bundle corretto:

// se il nodo esiste ritorna un NodeInterface, se non esiste ritorna NULL
$alert = $this->entityTypeManager 
    ->getStorage('node') 
    ->load(42);

if ( 
  $alert instanceof NodeInterface 
  && $alert->bundle() === 'weather_alert' 
) { 
  // Il nodo caricato è del bundle Weather alert
}

// caricare più nodi
$alerts = $this->entityTypeManager 
  ->getStorage('node') 
  ->loadMultiple([ 
    12, 
    35, 
    48, 
]);

// il risultato è un array dove la chiave è l'id
[ 
  12 => $node12, 
  35 => $node35, 
  48 => $node48, 
]

Modificare una entity:

$alert = $storage->load(42);

if (
  $alert instanceof NodeInterface
  && $alert->bundle() === 'weather_alert'
) {
  $alert->set(
    'field_alert_level', // nome del campo
    'severe', // valore
  );

  $alert->setTitle( // metodo semantico (esiste anche setPublished() setUnpublished e altri)
    'Severe weather warning',
  );

  $violations = $alert->validate();

  if ($violations->count() === 0) {
    $alert->save();
  }
}

Metodo procedurale:

<?php

use Drupal\node\Entity\Node; 

$node = Node::load(42);

$node = Node::create([ 
   'type' => 'weather_alert', 
]);

?>

Definizione di costraint custom

Nel file src/Plugin/Validation/Constraint/ WeatherAlertDateRangeConstraint.php:

<?php 

declare(strict_types=1);

namespace Drupal\nome_modulo\Plugin\Validation\Constraint;

use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Validation\Attribute\Constraint;
use Symfony\Component\Validator\Constraint as SymfonyConstraint; 

/** 
* Validates the date range of a weather alert. 
*/
#[
    Constraint(
        id: "WeatherAlertDateRange",
        label: new TranslatableMarkup(
            "Weather alert date range",
            [],
            ["context" => "Validation"]
        ),
        type: false
    )
]
final class WeatherAlertDateRangeConstraint extends SymfonyConstraint {

  /** 
  * Validation error message. 
  */
    public string $message = "The end date must be later than the start date.";
}

?>

Nel file src/Plugin/Validation/Constraint/ WeatherAlertDateRangeConstraintValidator.php:

<?php 

declare(strict_types=1);

namespace Drupal\nome_modulo\Plugin\Validation\Constraint;

use Drupal\Core\Field\FieldItemListInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

/** 
* Validates the weather alert date range. 
*/ 
final class WeatherAlertDateRangeConstraintValidator extends ConstraintValidator
{
    /** 
    * {@inheritdoc} 
    */ 
     public function validate(
        mixed $value,
        Constraint $constraint
    ): void {
        if (
            !$constraint instanceof WeatherAlertDateRangeConstraint ||
            !$value instanceof FieldItemListInterface ||
            $value->isEmpty()
        ) {
            return;
        }

        $entity = $value->getEntity();

        if (
            !$entity->hasField("field_alert_start") ||
            $entity->get("field_alert_start")->isEmpty()
        ) {
            return;
        }

        $startItem = $entity->get("field_alert_start")->first();

        $endItem = $value->first();

        if (
            $startItem === null ||
            $endItem === null ||
            empty($startItem->date) ||
            empty($endItem->date)
        ) {
            return;
        }

        if ($endItem->date <= $startItem->date) {
            $this->context
                ->buildViolation($constraint->message)
                ->addViolation(); // mostrerà il messaggio The end date must be later than the start date.
        }
    }
}