FieldTypes, FieldWidgets e FieldFormatters
Panoramica
Drupal 8 viene fornito con una grande libreria di classi di base che ti permettono di lavorare con i tuoi contenuti. Quando si tratta di entità di contenuto, vuoi usare i campi. È importante capire i Campi, perché è lì che le tue entità memorizzano i loro dati.
FieldTypes
I principali tipi di campo:
- boolean
- changed
- comment
- created
- datetime
- daterange
- decimal
- entity_reference
- file
- float
- image
- integer
- language
- link
- list_float
- list_integer
- list_string
- map
- password
- path
- string
- string_long
- telephone
- text
- text_long
- text_with_summary
- timestamp
- uri
- uuid
Tipi di campo personalizzati
Ogni volta che vuoi rappresentare dati in un modo che Drupal non fornisce, potresti voler creare un nuovo tipo di campo per i tuoi dati.
Supponiamo di avere un oggetto di contenuto che contiene dati sensibili. Il creatore di questo contenuto può consentire a determinati utenti di accedere all’oggetto tramite una password, che è diversa per ogni utente. Se parliamo in termini di tabelle del database, vuoi creare qualcosa di simile:
| entity_id | uid | password | ----------------------------------- | 1 | 1 | 'helloworld' | | 1 | 2 | 'goodbye' |
Come puoi vedere, la nostra entità con ID 1 ha due password diverse per due utenti diversi. Quindi, come possiamo implementarlo in Drupal senza creare manualmente la tabella? Creiamo un nuovo tipo di campo.
Poiché Drupal implementa la logica del campo come Plugin, abbiamo sempre una classe di base da estendere affinché funzioni in Drupal. Per un nuovo tipo di campo devi creare la seguente struttura di cartelle nel tuo modulo:
modules/custom/NOMEMODULO/src/Plugin/Field/FieldType
È un percorso piuttosto lungo e un po’ fastidioso, ma Drupal semplifica il lavoro con tutte le varie funzionalità che possono coesistere nei tuoi moduli.
Per il nostro esempio creiamo il file EntityUserAccessField.php
namespace Drupal\MODULENAME\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
/**
* @FieldType(
* id = "entity_user_access",
* label = @Translation("Entity User Access"),
* description = @Translation("This field stores a reference to a user and a password for this user on the entity."),
* )
*/
class EntityUserAccessField extends FieldItemBase {
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
//ToDo: Implement this.
}
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
//ToDo: Implement this.
}
}
Come puoi vedere, il tipo di campo è molto simile a un’entità di contenuto. In realtà, non c’è differenza tra i due, ma questa è una storia per un altro articolo ;)
Prima di tutto, abbiamo l’annotazione per il nostro tipo di campo:
- @FieldType: richiama la classe di annotazione FieldType dalla libreria di Drupal
- id: è il nome macchina del nostro tipo di campo, così possiamo riutilizzarlo. Assicurati di non sovrascrivere nomi riservati di PHP o simili.
- label: può essere una traduzione leggibile per l’utente del nome macchina.
- description: se l’etichetta non è sufficiente, puoi aggiungere anche una descrizione per il tipo di campo.
In secondo luogo, la nostra classe estende FieldItemBase, il che ci obbliga a implementare due metodi per poter usare correttamente questo tipo di campo:
- propertyDefinitions(): Questo metodo è simile a baseFieldDefinition di un’entità di contenuto (non è la stessa cosa!). Possiamo definire i dati che appaiono nei form delle entità dove viene usato questo tipo di campo.
- schema(): Sulle entità questo metodo è deprecato, ma esiste ancora per i campi. Questo metodo deve implementare la rappresentazione dei dati del campo nel database. Può differire dalle proprietà.
Poiché non è molto ovvio cosa scrivere in questi metodi, aggiungiamo un po’ di codice di esempio.
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties['uid'] = DataDefinition::create('integer')
->setLabel(t('User ID Reference'))
->setDescription(t('The ID of the referenced user.'))
->setSetting('unsigned', TRUE);
$properties['password'] = DataDefinition::create('string')
->setLabel(t('Password'))
->setDescription(t('A password saved in plain text. That is not safe dude!'));
$properties['created'] = DataDefinition::create('timestamp')
->setLabel(t('Created Time'))
->setDescription(t('The time that the entry was created'));
// ToDo: Add more Properties.
return $properties;
}
È anche possibile salvare l’ID utente tramite DataReferenceDefinition, questo potrebbe essere trattato in futuro.
public static function schema(FieldStorageDefinitionInterface $field_definition) {
$columns = array(
'uid' => array(
'description' => 'The ID of the referenced user.',
'type' => 'int',
'unsigned' => TRUE,
),
'password' => array(
'description' => 'A plain text password.',
'type' => 'varchar',
'length' => 255,
),
'created' => array(
'description' => 'A timestamp of when this entry has been created.',
'type' => 'int',
),
// ToDo: Add more columns.
);
$schema = array(
'columns' => $columns,
'indexes' => array(),
'foreign keys' => array(),
);
return $schema;
}
Schema() è necessario affinché Drupal sappia come salvare i nostri dati. Le colonne dello schema devono essere un sottoinsieme delle proprietà definite in propertyDefinitions().
Ora abbiamo un nuovo tipo di campo. Non ha logica su come gestire l’input, ma può essere usato in qualsiasi entità di contenuto come campo. Se vuoi, puoi usarlo come baseField per l’entità:
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
// Some fields above.
$fields['entity_user_access'] = BaseFieldDefinition::create('entity_user_access')
->setLabel(t('Entity User Access'))
->setDescription(t('Specify passwords for any user that want to see this entity.'))
->setCardinality(-1); // Ensures that you can have more than just one member
// Even more fields below.
return $fields;
}
- BaseFieldDefinition::create(): Devi usare il nome macchina del tipo di campo in create()
- setCardinality(-1): Il numero di elementi rappresenta la quantità di dati del campo che un’entità può avere. Ad esempio, se scriviamo 2, solo due utenti potranno accedere all’entità; 3 significa 3 utenti, e così via. -1 rappresenta un numero infinito di utenti.
FieldWidget
Se hai dati personalizzati, potresti avere bisogno di una rappresentazione personalizzata di questi dati. I Widget sono usati per rappresentare come un utente può inserire questi dati personalizzati nei form. Per esempio:
- se in un form serve specificare un intero, ma l’utente può solo spuntare una casella
- se vuoi l’autocompletamento per i tuoi dati
- se l’inserimento della password avviene tramite un’interfaccia grafica speciale
e così via.
Il widget di un campo in Drupal si trova sotto
modules/custom/NOMEMODULO/src/Plugin/Field/FieldWidget
anche questo è un percorso lungo. A questo punto dovrebbe essere chiaro perché Drupal usa questo stile per separare i file .php. È molto facile perdere traccia di quali file appartengano a cosa.
Creiamo il widget del campo in EntityUserAccessWidget.php
<code>namespace Drupal\MODULENAME\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
/**
* Plugin implementation of the 'entity_user_access_w' widget.
*
* @FieldWidget(
* id = "entity_user_access_w",
* label = @Translation("Entity User Access - Widget"),
* description = @Translation("Entity User Access - Widget"),
* field_types = {
* "entity_user_access",
* },
* multiple_values = TRUE,
* )
*/
class EntityUserAccessWidget extends WidgetBase {
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
// ToDo: Implement this.
}
}</code>
Hai già notato? Drupal 8 utilizza questo stile di codice ancora e ancora se vuoi implementare funzioni. C’è un’annotazione e una classe base da estendere. Sì, Drupal può usare questo!
- @FieldWidget: definisce la classe di annotazione
- id: il nome macchina del widget
- field_types: un array di nomi macchina dei tipi di campo che possono usare questo widget
- multiple_values: di default FALSE. Se TRUE, permette di inserire più di un valore nel form dell’entità
Se ora vuoi usare questo widget con il tuo tipo di campo, devi modificare l’annotazione del tipo di campo come segue
// ...
/**
* @FieldType(
* id = "entity_user_access",
* label = @Translation("Entity User Access"),
* description = @Translation("This field stores a reference to a user and a password for this user on the entity."),
* default_widget = "entity_user_access_w",
* )
*/
// ...
Sì, fatto! No, aspetta, non succede ancora nulla, perché dobbiamo implementare formElement() nel nostro widget.
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element['userlist'] = array(
'#type' => 'select',
'#title' => t('User'),
'#description' => t('Select group members from the list.'),
'#options' => array(
0 => t('Anonymous'),
1 => t('Admin'),
2 => t('foobar'),
// This should be implemented in a better way!
),
);
$element['passwordlist'] = array(
'#type' => 'password',
'#title' => t('Password'),
'#description' => t('Select a password for the user'),
);
// Impostare i valori predefiniti per tutti i campi sopra
$childs = Element::children($element);
foreach ($childs as $child) {
$element[$child]['#default_value'] = isset($items[$delta]->{$child}) ? $items[$delta]->{$child} : NULL;
}
return $element;
}
Se ora apri un form con questo widget, vedrai almeno due campi di input. Uno è la selezione dell’utente e l’altro è il campo password. Se vuoi implementare un metodo di salvataggio dei dati, devi implementare metodi di validazione in questo widget o nel form dell’entità. Vedi Drupal 8 Form API per maggiori informazioni.
A questo punto hai fatto gran parte del lavoro per un campo personalizzato. Se non capisci cosa sta succedendo, prova il codice o guarda i moduli core per approfondire.
FieldFormatters
L’ultima cosa che manca è la visualizzazione dei dati nella cosiddetta modalità di visualizzazione dell’entità – tra l’altro, il widget è la modalità di form. Questo avviene spesso quando richiami un’entità tramite yourdrupalpage.com/myentity/1/view
Poiché non c’è molto da dire qui, passiamo direttamente al codice. Sotto modules/custom/NOMEMODULO/src/Plugin/Field/FieldFormatter crea EntityUserAccessFormatter.php
namespace Drupal\MODULENAME\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
/**
* Plugin implementation of the 'entity_user_access_f' formatter.
*
* @FieldFormatter(
* id = "entity_user_access_f",
* label = @Translation("Entity User Access - Formatter"),
* description = @Translation("Entity User Access - Formatter"),
* field_types = {
* "entity_user_access",
* }
* )
*/
class EntityUserAccessFormatter extends FormatterBase {
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = array();
foreach ($items as $delta => $item) {
$elements[$delta] = array(
'uid' => array(
'#markup' => \Drupal\user\Entity\User::load($item->uid)->getUsername(),
),
// Add more content
);
}
return $elements;
}
}
Questo esempio ha un’annotazione molto simile a quella del widget, quindi non c’è molto altro da dire. ViewElements() mostra semplicemente il nome utente dell’ID utente salvato nel nostro tipo di campo. L’implementazione dell’accesso deve essere fatta nell’entità. In questo modo, questa implementazione mostrerà tutti i nomi utenti che hanno una password sull’entità.