Drupal 8 harnesses the entity API for configuration to provide configuration validation and extended functionality. Using the underlying entity structure, the configuration has a proper Create, Read, Update, Delete (CRUD) process that can be managed. Configuration entities are not fieldable. All the attributes of a configuration entity are defined in its configuration schema definition.
Most common configuration entities interact with Drupal core's config_object
type, as discussed in Chapter 4, Extending Drupal, and Chapter 9, Configuration Management – Deploying in Drupal 8, to store and manage a site's configuration. There are other uses of configuration entities, such as menus, view displays, form displays, contact forms, tours, and many more, which are all configuration entities.
In this recipe, we will create a new configuration entity type called SiteAnnouncement
. This will provide a simple configuration entity that allows you to create, edit, and delete simple messages that can be displayed on the site for important announcements.
You will need a custom module to place code into in order to implement a configuration entity type. Create an src
directory for your classes.
config
directory with a schema
subdirectory. In the subdirectory, make a file named mymodule.schema.yml
that will hold our configuration entity's schema:mymodule.schema.yml
, add a definition to mymodule.announcement.*
to provide our label and message storage:# Schema for the configuration files of the Site Announcement. mymodule.announcement.*: type: config_entity label: 'Site announcement' mapping: id: type: string label: 'ID' label: type: label label: 'Label' message: type: text label: 'Text'
We define the configuration entity's namespace as an announcement, which we will provide to Drupal in the entity's annotation block. We tell Drupal that this is a config_entity
and provide a label for the schema.
Using the mapping array, we provide the attributes that make up our entity and the data that will be stored.
Entity
directory in your module's src
folder. First, we will create an interface for our entity by making a SiteAnnouncementInterface.php
file. The SiteAnnouncementInterface
will extend the DrupalCoreConfigEntityConfigEntityInterface
:<?php /** * @file Contains DrupalmymoduleEntitySiteAnnouncementInterface. */ namespace DrupalmymoduleEntity; use DrupalCoreConfigEntityConfigEntityInterface; interface SiteAnnouncementInterface extends ConfigEntityInterface { /** * Gets the message value. * * @return string */ public function getMessage(); }
This will be implemented by our entity and will be provided the method requirements. It is best practice to provide an interface for entities. This allows you to provide the required methods if another developer extends your entity or if you are doing advanced testing and need to mock an object. We also provide a method for returning our custom attribute.
SiteAnnouncement.php
in your Entity
directory in src
. This file will contain the SiteAnnouncement
class, which extends DrupalCoreConfigEntityConfigEntityBase
and implements our entity's interface:<?php /** * @file Contains DrupalmymoduleEntitySiteAnnouncement */ namespace DrupalmymoduleEntity; use DrupalCoreConfigEntityConfigEntityBase; class SiteAnnouncement extends ConfigEntityBase implements SiteAnnouncementInterface { /** * The announcement's message. * * @var string */ protected $message; /** * {@inheritdoc|} */ public function getMessage() { return $this->message; } }
We added the message
property defined in our schema as a class property. Our method defined in the entity's interface is used to return that value and interact with our configuration entity.
<?php /** * @file Contains DrupalmymoduleEntitySiteAnnouncement */ namespace DrupalmymoduleEntity; use DrupalCoreConfigEntityConfigEntityBase; /** * @ConfigEntityType( * id ="announcement", * label = @Translation("Site Announcement"), * config_prefix = "announcement", * entity_keys = { * "id" = "id", * "label" = "label" * }, * config_export = { * "id", * "label", * "message", * } * ) */ class SiteAnnouncement extends ConfigEntityBase implements SiteAnnouncementInterface { /** * The announcement's message. * * @var string */ protected $message; /** * {@inheritdoc} */ public function getMessage() { return $this->message; } }
The annotation document block tells Drupal that this is an instance of the ConfigEntityType
plugin. The id
is the internal machine name identifier for the entity type and the label
is the human-readable version. The config_prefix
matches with how we defined our schema with mymodule.announcement
. The entity keys definition tells Drupal which attributes represent our identifiers and labels.
When specifying config_export
, we are telling the configuration management system what properties are to be exportable when exporting our entity.
handlers
to our entity. We will define the class that will display the available entity entries and the forms to work with our entity:/** * @ConfigEntityType( * id ="announcement", * label = @Translation("Site Announcement"), * handlers = { * "list_builder" = "DrupalmymoduleSiteAnnouncementListBuilder", * "form" = { * "default" = "DrupalmymoduleSiteAnnouncementForm", * "add" = "DrupalmymoduleSiteAnnouncementForm", * "edit" = "DrupalmymoduleSiteAnnouncementForm", * "delete" = "DrupalCoreEntityEntityDeleteForm" * } * }, * config_prefix = "announcement", * entity_keys = { * "id" = "id", * "label" = "label" * }, * config_export = { * "id", * "label", * "message", * } * ) */
The handlers
array specifies classes that provide the interaction functionality with our entity. The list_builder
class will be created to show you a table of our entities. The form array provides classes for forms to be used when creating, editing, or deleting our configuration entity.
delete
, edit
, and collection
(list) pages. Drupal will automatically build the routes based on our annotation:/** * @ConfigEntityType( * id ="announcement", * label = @Translation("Site Announcement"), * handlers = { * "list_builder" = "DrupalmymoduleSiteAnnouncementListBuilder", * "form" = { * "default" = "DrupalmymoduleSiteAnnouncementForm", * "add" = "DrupalmymoduleSiteAnnouncementForm", * "edit" = "DrupalmymoduleSiteAnnouncementForm", * "delete" = "DrupalCoreEntityEntityDeleteForm" * } * }, * config_prefix = "announcement", * entity_keys = { * "id" = "id", * "label" = "label" * }, * links = { * "delete-form" = "/admin/config/system/site-announcements/manage/{announcement}/delete", * "edit-form" = "/admin/config/system/site-announcements/manage/{announcement}", * "collection" = "/admin/config/system/site-announcements", * }, * config_export = { * "id", * "label", * "message", * } * ) */
There is a routing service for entities that will automatically provide Drupal a route with the proper controllers based on this annotation. The add form route is not yet supported and needs to be manually added.
mymodule.routing.yml
in your module's root directory to manually provide a route to add a Site-announcement
entity:entity.announcement.add_form: path: '/admin/config/system/site-announcements/add' defaults: _entity_form: 'announcement.add' _title: 'Add announcement' requirements: _permission: 'administer content'
_entity_form
property to tell Drupal to look up the class defined in our handlers.list_builder
handler, we also need to add the route in mymodule.routing.yml
for our collection link definition, as this is not auto generated by route providers:entity.announcement.collection: path: '/admin/config/system/site-announcements' defaults: _entity_list: 'announcement' _title: 'Site Announcements' requirements: _permission: 'administer content
_entity_list
key will tell the route to use our list_builder
handler to build the page. We will reuse the administer content
permission provided by the Node module.SiteAnnouncementListBuilder
class defined in our list_builder
handler by making a SiteAnnouncementListBuilder.php
and extending the DrupalCoreConfigEntityConfigEntityListBuilder
:<?php /** * @file * Contains DrupalmymoduleSiteAnnouncementListBuilder. */ namespace Drupalmymodule; use DrupalCoreConfigEntityConfigEntityListBuilder; use DrupalmymoduleEntitySiteAnnouncementInterface; class SiteAnnouncementListBuilder extends ConfigEntityListBuilder { /** * {@inheritdoc} */ public function buildHeader() { $header['label'] = t('Label'); return $header + parent::buildHeader(); } /** * {@inheritdoc} */ public function buildRow(SiteAnnouncementInterface $entity) { $row['label'] = $entity->label(); return $row + parent::buildRow($entity); } }
buildHeader
and builderRow
methods so that we can add our configuration entity's properties to the table.SiteAnnouncementForm.php
in the src
directory to provide the SiteAnnouncementForm
class that extends the DrupalCoreEntityEntityForm
class:<?php namespace Drupalmymodule; use DrupalComponentUtilityUnicode; use DrupalCoreEntityEntityForm; use DrupalCoreFormFormStateInterface; use DrupalCoreLanguageLanguageInterface; class SiteAnnouncementForm extends EntityForm { /** * {@inheritdoc} */ public function form(array $form, FormStateInterface $form_state) { $form = parent::form($form, $form_state); /** @var DrupalmymoduleEntitySiteAnnouncementInterface $entity */ $entity = $this->entity; $form['label'] = [ '#type' => 'textfield', '#title' => t('Label'), '#required' => TRUE, '#default_value' => $entity->label(), ]; $form['message'] = [ '#type' => 'textarea', '#title' => t('Message'), '#required' => TRUE, '#default_value' => $entity->getMessage(), ]; return $form; } /** * {@inheritdoc} */ public function save(array $form, FormStateInterface $form_state) { $entity = $this->entity; $is_new = !$entity->getOriginalId(); if ($is_new) { // Configuration entities need an ID manually set. $machine_name = Drupal::transliteration() ->transliterate($entity->label(), LanguageInterface::LANGCODE_DEFAULT, '_'); $entity->set('id', Unicode::strtolower($machine_name)); drupal_set_message(t('The %label announcement has been created.', array('%label' => $entity->label()))); } else { drupal_set_message(t('Updated the %label announcement.', array('%label' => $entity->label()))); } $entity->save(); // Redirect to edit form so we can populate colors. $form_state->setRedirectUrl($this->entity->toUrl('collection')); } }
form
method to add Form API elements to our label
and message
properties. We also override the save
method to provide user messages about the changes that are made. We utilize the entity's toUrl
method to provide a redirect back to the collection
(list) page. We use the transliteration service to generate a machine name based on the label for our entity's identifier.mymodule.links.action.yml
file in your module's directory. This will allow us to define action links on a route. We will be adding an Add announcement
link to our entity's add form on its collection route:announcement.add: route_name: entity.announcement.add_form title: 'Add announcement' appears_on: - entity.announcement.collection
entity.announcement.add_form
link on the specified routes in the appears_on
value.Site Announcement
entries from the Site Announcement link.When creating a configuration schema definition, one of the first properties used for the configuration namespace is type
. This value can be config_object
or config_entity
. When the type is config_entity
, the definition will be used to create a database table rather than structure the serialized data for the config
table.
Entities are powered by the plugin system in Drupal, which means there is a plugin manager. The default DrupalCoreEntityEntityTypeManager
provides discovery and handling of entities. The ConfigEntityType
class for the entity type's plugin class will force the setting of the uuid
and langcode
in the entity_keys
definition. The storage handler for configuration entities defaults to DrupalCoreConfigEntityConfigEntityStorage
. The ConfigEntityStorage
class interacts with the configuration management system to load, save, and delete custom configuration entities.
Drupal 8 introduces a typed data system that configuration entities, and fields use.
Drupal core provides its own configuration information. There is a core.data_types.schema.yml
file located at core/config/schema
. These are the base types of data that core provides and can be used when making configuration schema. The file contains YAML definitions of data types and the class which represents them:
boolean: label: 'Boolean' class: 'DrupalCoreTypedDataPluginDataTypeBooleanData' email: label: 'Email' class: 'DrupalCoreTypedDataPluginDataTypeEmail' string: label: 'String' class: 'DrupalCoreTypedDataPluginDataTypeStringData'
When a configuration schema definition specifies an attribute that has an e-mail for its type, that value is then handled by the DrupalCoreTypedDataPluginDataTypeEmail
class. Data types are a form of plugins and each plugin's annotation specifies constraints for validation. This is built around the Symfony Validator component.