Blog

Wie man eine benutzerdefinierte Entität in Drupal 8 erstellt

Entity API in Drupal 8 ist jetzt in den Kern integriert und so gut organisiert, dass es fast keine Entschuldigung mehr gibt, Datenbanktabellen zu erstellen, die gleichzeitig keine Entitäten sind. Wenn Sie es ernst mit Drupal-Entwicklung meinen, schauen Sie sich den folgenden Artikel an. Entitäten in Drupal sind wirklich cool!

Wenn Sie eine Entität erstellen, erhalten Sie eine Views-Integration kostenlos, Sie können die Entität feldbar machen und dies erlaubt Ihnen sofort, verschiedene Felder hinzuzufügen. Außerdem können Sie die Entität mit EntityDrupal::EntityQuery durchsuchen und vieles mehr.

Ich musste kürzlich eine einfache Entität eines Online-Wörterbuchs erstellen und denke, dass dies eine großartige Gelegenheit ist, Ihnen mitzuteilen, was ich gelernt habe.

Wörterbucheintrag-Entität

Die Entität speichert Übersetzungen von Wörtern aus dem Englischen ins Polnische. Es handelt sich um eine wirklich einfache Entität mit nur 2 Datenfeldern:

  • pl - Feld zur Speicherung des polnischen Wortes
  • en - Feld zur Speicherung des englischen Wortes

Ich werde tatsächlich noch einige Felder hinzufügen, die es wert sind, fast jeder Entität hinzugefügt zu werden:

  • id - eindeutiger Bezeichner
  • uuid - Drupal 8 hat jetzt native Unterstützung zur Erstellung universell eindeutiger Bezeichner
  • user_id - ID des Erstellers der Entität (ein Verweis auf den Drupal-Benutzer)
  • created - ein Zeitstempel, wann die Entität erstellt wurde
  • changed - ein Zeitstempel, wann die Entität zuletzt aktualisiert wurde

Lassen Sie uns ein 'Wörterbuch' Modul erstellen

In /sites/modules/custom habe ich einen 'dictionary'-Ordner erstellt, mit den folgenden Anfangsdateien:

dictionary.info.yml

name: dictionary
type: module
description: Dictionary
core: 8.x
package: Application
<?php
/**
* @file
* Enthält \Drupal\content_entity_example\Entity\ContentEntityExample.
*/

namespace Drupal\dictionary\Entity;

use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\user\UserInterface;
use Drupal\Core\Entity\EntityChangedTrait;

/**
* Definiert die ContentEntityExample-Entität.
*
* @ingroup dictionary
*
*
* @ContentEntityType(
* id = "dictionary_term",
* label = @Translation("Dictionary Term entity"),
* handlers = {
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
* "list_builder" = "Drupal\dictionary\Entity\Controller\TermListBuilder",
* "form" = {
* "add" = "Drupal\dictionary\Form\TermForm",
* "edit" = "Drupal\dictionary\Form\TermForm",
* "delete" = "Drupal\dictionary\Form\TermDeleteForm",
* },
* "access" = "Drupal\dictionary\TermAccessControlHandler",
* },
* list_cache_contexts = { "user" },
* base_table = "dictionary_term",
* admin_permission = "administer dictionary_term entity",
* entity_keys = {
* "id" = "id",
* "uuid" = "uuid",
* "user_id" = "user_id",
* "created" = "created",
* "changed" = "changed",
* "pl" = "pl",
* "en" = "en",
* },
* links = {
* "canonical" = "/dictionary_term/{dictionary_term}",
* "edit-form" = "/dictionary_term/{dictionary_term}/edit",
* "delete-form" = "/dictionary_term/{dictionary_term}/delete",
* "collection" = "/dictionary_term/list"
* },
* field_ui_base_route = "entity.dictionary.term_settings",
* )
*/
class Term extends ContentEntityBase {

use EntityChangedTrait;

/**
* {@inheritdoc}
*
* Wenn eine neue Entitätsinstanz hinzugefügt wird, setzen Sie den user_id-Entitätsverweis auf
* den aktuellen Benutzer als Ersteller der Instanz.
*/
public static function preCreate(EntityStorageInterface $storage_controller, array &$values) {
parent::preCreate($storage_controller, $values);
// Standardautor ist der aktuelle Benutzer.
$values += array(
'user_id' => \Drupal::currentUser()->id(),
);
}

/**
* {@inheritdoc}
*
* Definieren Sie hier die Feldeigenschaften.
*
* Feldname, Typ und Größe bestimmen die Tabellenstruktur.
*
* Darüber hinaus können wir definieren, wie das Feld und sein Inhalt
* in der GUI manipuliert werden können. Das Verhalten der verwendeten Widgets kann hier bestimmt werden.
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {

// Standardfeld, verwendet als eindeutig, wenn primärer Index.
$fields['id'] = BaseFieldDefinition::create('integer')
->setLabel(t('ID'))
->setDescription(t('Die ID der Term-Entität.'))
->setReadOnly(TRUE);

// Standardfeld, eindeutig außerhalb des aktuellen Projekts.
$fields['uuid'] = BaseFieldDefinition::create('uuid')
->setLabel(t('UUID'))
->setDescription(t('Die UUID der Kontakt-Entität.'))
->setReadOnly(TRUE);

// Namensfeld für den Kontakt.
// Wir setzen Anzeigeoptionen sowohl für die Ansicht als auch das Formular.
// Benutzer mit den richtigen Privilegien können die Anzeige und
edit-Konstruktion ändern.
$fields['pl'] = BaseFieldDefinition::create('string')
->setLabel(t('Polnisch'))
->setDescription(t('Polnische Version.'))
->setSettings(array(
'default_value' => '',
'max_length' => 255,
'text_processing' => 0,
))
->setDisplayOptions('view', array(
'label' => 'above',
'type' => 'string',
'weight' => -6,
))
->setDisplayOptions('form', array(
'type' => 'string_textfield',
'weight' => -6,
))
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE);

$fields['en'] = BaseFieldDefinition::create('string')
->setLabel(t('Englisch'))
->setDescription(t('Englische Version.'))
->setSettings(array(
'default_value' => '',
'max_length' => 255,
'text_processing' => 0,
))
->setDisplayOptions('view', array(
'label' => 'above',
'type' => 'string',
'weight' => -4,
))
->setDisplayOptions('form', array(
'type' => 'string_textfield',
'weight' => -4,
))
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE);

// Eigentümerfeld des Kontakts.
// Entitätsreferenzfeld, hält den Verweis auf das Benutzerobjekt.
// Die Ansicht zeigt das Namensfeld des Benutzers an.
// Im Formular erscheint ein Autocomplete-Feld für den Benutzernamen.
$fields['user_id'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Benutzername'))
->setDescription(t('Der Name des zugehörigen Benutzers.'))
->setSetting('target_type', 'user')
->setSetting('handler', 'default')
->setDisplayOptions('view', array(
'label' => 'above',
'type' => 'author',
'weight' => -3,
))
->setDisplayOptions('form', array(
'type' => 'entity_reference_autocomplete',
'settings' => array(
'match_operator' => 'CONTAINS',
'size' => 60,
'placeholder' => '',
),
'weight' => -3,
))
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE);

$fields['created'] = BaseFieldDefinition::create('created')
->setLabel(t('Erstellt'))
->setDescription(t('Der Zeitpunkt, zu dem die Entität erstellt wurde.'));

$fields['changed'] = BaseFieldDefinition::create('changed')
->setLabel(t('Geändert'))
->setDescription(t('Der Zeitpunkt, zu dem die Entität zuletzt bearbeitet wurde.'));

return $fields;
}

}

Sie können sehen, dass die Term-Klasse der Entität ContentEntityBase erweitert, welche die Basisklasse ist, die zum Erstellen von Entitäten in Drupal verwendet wird. Ein wichtiger Punkt sind die Anmerkungen im Kommentarblock oberhalb der Klasse. Viele wichtige Definitionen werden hier festgelegt. Beachten Sie insbesondere:

  • id - eindeutiger Bezeichner der Entität im System
  • handlers - Links zu allen Controllern
  • base_table - der Name der Tabelle, die für diese Entität verwendet wird. Sie müssen das Schema nicht separat erstellen, es wird aus den Felddefinitionen gelesen

Das war's. Ihre Entität ist bereit. Die weitere Arbeit besteht darin, alle Ansichts- und Bearbeitungs- sowie Listenbildschirme und Formulare zum Laufen zu bringen.
Richten Sie zuerst die Routing- und Berechtigungseinstellungen ein.

dictionary.routing.yml

# Diese Datei bringt alles zusammen. Sehr praktisch!

# Der Routenname kann an mehreren Stellen verwendet werden (Links, Weiterleitungen, lokale Aktionen usw.)
entity.dictionary_term.canonical:
path: '/dictionary_term/{dictionary_term}'
defaults:
# Ruft den View-Controller auf, der in der Annotation der dictionary_term-Entität definiert ist
_entity_view: 'dictionary_term'
_title: 'dictionary_term Content'
requirements:
# Ruft den Zugangs-Controller der Entität auf, $operation 'view'
_entity_access: 'dictionary_term.view'

entity.dictionary_term.collection:
path: '/dictionary_term/list'
defaults:
# Ruft den List-Controller auf, der in der Annotation der dictionary_term-Entität definiert ist.
_entity_list: 'dictionary_term'
_title: 'dictionary_term List'
requirements:
# Überprüft direkt auf Berechtigung.
_permission: 'view dictionary_term entity'

entity.dictionary.term_add:
path: '/dictionary_term/add'
defaults:
# Ruft den form.add-Controller auf, der in der dictionary_term-Entität definiert ist.
_entity_form: dictionary_term.add
_title: 'Add dictionary_term'
requirements:
_entity_create_access: 'dictionary_term'

entity.dictionary_term.edit_form:
path: '/dictionary_term/{dictionary_term}/edit'
defaults:
# Ruft den form.edit-Controller auf, der in der dictionary_term-Entität definiert ist.
_entity_form: dictionary_term.edit
_title: 'Edit dictionary_term'
requirements:
_entity_access: 'dictionary_term.edit'

entity.dictionary_term.delete_form:
path: '/dictionary_term/{dictionary_term}/delete'
defaults:
# Ruft den form.delete-Controller auf, der in der dictionary_term-Entität definiert ist.
_entity_form: dictionary_term.delete
_title: 'Delete dictionary_term'
requirements:
_entity_access: 'dictionary_term.delete'

entity.dictionary.term_settings:
path: 'admin/structure/dictionary_term_settings'
defaults:
_form: '\Drupal\dictionary\Form\TermSettingsForm'
_title: 'dictionary_term Settings'
requirements:
_permission: 'administer dictionary_term entity'

 

dictionary.persmissions.yml

'delete dictionary_term entity':
title: Delete term entity content.
'add dictionary_term entity':
title: Add term entity content
'view dictionary_term entity':
title: View term entity content
'edit dictionary_term entity':
title: Edit term entity content
'administer dictionary_term entity':
title: Administer term settings

 

Üblicherweise enthalten Entitäten auch hilfreiche lokale Links und Aufgaben, die nützlich sind, wie die /edit Aufgabe.

dictionary.links.tasks.yml

Lokale Aufgaben hinzufügen

# Definieren der 'lokalen' Links für das Modul

entity.dictionary_term.settings_tab:
route_name: dictionary.term_settings
title: Settings
base_route: dictionary.term_settings

entity.dictionary_term.view:
route_name: entity.dictionary_term.canonical
base_route: entity.dictionary_term.canonical
title: View

entity.dictionary_term.page_edit:
route_name: entity.dictionary_term.edit_form
base_route: entity.dictionary_term.canonical
title: Edit

entity.dictionary_term.delete_confirm:
route_name: entity.dictionary_term.delete_form
base_route: entity.dictionary_term.canonical
title: Delete
weight: 10

 

dictionary.links.action.yml

Einen Hinzufüge-Lokal-Link zu den Listenterminen hinzufügen

dictionary._term_add:
# Welche Route wird vom Link aufgerufen
route_name: entity.dictionary.term_add
title: 'Add term'

# Wo wird der Link erscheinen, definiert durch den Routenname.
appears_on:
- entity.dictionary_term.collection
- entity.dictionary_term.canonical

 

Jetzt, da Menüs und Aufgaben erstellt sind, lassen Sie uns die Seite erstellen, die unsere Entitäten sowie die Hinzufüge-, Bearbeitungs- und Löschformulare auflistet.

/src/Entity/Con­troller/TermList­Builder.php

Von allen Controllern werden wir den Controller für die Entitätenliste implementieren. Schließlich möchten wir die Liste auf sinnvolle Weise mit beiden, dem englischen und dem polnischen Wort, nebeneinander in einer Tabelle sehen.

<?php

/**
* @file
* Enthält \Drupal\dictionaryEntity\Controller\TermListBuilder.
*/

namespace Drupal\dictionary\Entity\Controller;

use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityListBuilder;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Stellt einen List-Controller für dictionary_term-Entität bereit.
*
* @ingroup dictionary
*/
class TermListBuilder extends EntityListBuilder {

/**
* Der URL-Generator.
*
* @var \Drupal\Core\Routing\UrlGeneratorInterface
*/
protected $urlGenerator;

/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('entity.manager')->getStorage($entity_type->id()),
$container->get('url_generator')
);
}

/**
* Konstruiert ein neues DictionaryTermListBuilder-Objekt.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* Der Entitätstyp.
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* Die Entitäts-Speicherklasse.
* @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
* Der URL-Generator.
*/
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, UrlGeneratorInterface $url_generator) {
parent::__construct($entity_type, $storage);
$this->urlGenerator = $url_generator;
}

/**
* {@inheritdoc}
*
* Wir überschreiben ::render(), damit wir unseren eigenen Inhalt über der Tabelle hinzufügen können.
* parent::render() ist, wo EntityListBuilder die Tabelle erstellt,
* indem unsere buildHeader()- und buildRow()-Implementierungen verwendet werden.
*/
public function render() {
$build['description'] = array(
'#markup' => $this->t('Content Entity Example implements a DictionaryTerms model. These are fieldable entities. You can manage the fields on the <a href="@adminlink">Term admin page</a>.', array(
'@adminlink' => $this->urlGenerator->generateFromRoute('entity.dictionary.term_settings'),
)),
);
$build['table'] = parent::render();
return $build;
}

/**
* {@inheritdoc}
*
* Erstellung der Kopfzeilen- und Inhaltszeilen für die dictionary_term Liste.
*
* Der Aufruf der parent::buildHeader() fügt eine Spalte für die möglichen Aktionen hinzu
* und integriert die 'edit' und 'delete' Links, die für den Entitätstyp definiert sind.
*/
public function buildHeader() {
$header['id'] = $this->t('TermID');
$header['pl'] = $this->t('Polnisch');
$header['en'] = $this->t('Englisch');
return $header + parent::buildHeader();
}

/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
/* @var $entity \Drupal\dictionary\Entity\Term */
$row['id'] = $entity->id();
$row['pl'] = $entity->pl->value;
$row['en'] = $entity->en->value;
return $row + parent::buildRow($entity);
}

}

Sie können die buildHeader- und buildRow-Funktionen sehen, die die Spalten für unsere Tabelle festlegen.

Add/Edit-Formular - src/Form/TermForm.php

<?php
/**
* @file
* Enthält Drupal\dictionary\Form\TermForm.
*/

namespace Drupal\dictionary\Form;

use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Form\FormStateInterface;

/**
* Formular-Controller für die edit-Formulare der content_entity_example Entität.
*
* @ingroup content_entity_example
*/
class TermForm extends ContentEntityForm {

/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
/* @var $entity \Drupal\dictionary\Entity\Term */
$form = parent::buildForm($form, $form_state);
return $form;
}

/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
// Weiterleitung zur Term-Liste nach dem Speichern.
$form_state->setRedirect('entity.dictionary_term.collection');
$entity = $this->getEntity();
$entity->save();
}

}

 

Löschen-Formular - src/Form/TermForm.php

<?php

/**
* @file
* Enthält \Drupal\dictionary\Form\TermDeleteForm.
*/

namespace Drupal\dictionary\Form;

use Drupal\Core\Entity\ContentEntityConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;

/**
* Formular für das Löschen einer content_entity_example Entität.
*
* @ingroup dictionary
*/
class TermDeleteForm extends ContentEntityConfirmFormBase {

/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->t('Sind Sie sicher, dass Sie die Entität %name löschen möchten?', array('%name' => $this->entity->label()));
}

/**
* {@inheritdoc}
*
* Wenn der Löschbefehl storniert wird, Rückkehr zur Kontaktliste.
*/
public function getCancelUrl() {
return new Url('entity.dictionary_term.collection');
}

/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Löschen');
}

/**
* {@inheritdoc}
*
* Löschen der Entität und Protokollierung des Ereignisses. logger() ersetzt die Watchdog-Funktion.
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$entity = $this->getEntity();
$entity->delete();

$this->logger('dictionary')->notice('deleted %title.', array(
'%title' => $this->entity->label(),
));
// Weiterleitung zur Term-Liste nach dem Löschen.
$form_state->setRedirect('entity.dictionary_term.collection');
}

}

 

src/Form/TermSettingsForm.php

Zu guter Letzt bleibt das Einstellungsformular, das uns erlaubt, die Entitätseinstellungen zu verwalten. Unser Formular wird hier leer bleiben. Ich benötige hier wirklich keine Einstellungen, könnte aber zusätzliche Formulareinstellungen verwalten.

<?php
/**
 * @file
 * Enthält \Drupal\dictionary\Form\TermSettingsForm.
 */

namespace Drupal\dictionary\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;

/**
 * Klasse ContentEntityExampleSettingsForm.
 *
 * @package Drupal\dictionary\Form
 *
 * @ingroup dictionary
 */
class TermSettingsForm extends FormBase {
  /**
   * Gibt einen eindeutigen String zurück, der das Formular identifiziert.
   *
   * @return string
   *   Der eindeutige String, der das Formular identifiziert.
   */
  public function getFormId() {
    return 'dictionary_term_settings';
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    // Leere Implementierung der abstrakten Submit-Klasse.
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $form['dictionary_term_settings']['#markup'] = 'Einstellungsformular für Wörterbucheinträge. Verwalten Sie hier die Feldeinstellungen.';
    return $form;
  }

}

 

Und das war's fürs Erste. Ihre Entität ist erstellt. Sie können sie verwenden.
 
Vollständiger Code aus diesem Beispiel ist hier
 
 
3. Best practices for software development teams