Creating a configuration entity type

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.

Getting ready

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.

How to do it…

  1. In your module's base directory, create a config directory with a schema subdirectory. In the subdirectory, make a file named mymodule.schema.yml that will hold our configuration entity's schema:
    How to do it…
  2. In your 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.

  3. Create an 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.

  4. Create 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.

  5. Entities use annotation documentation blocks. We will start our annotation block by providing the entity's ID, label, configuration prefix, and configuration export key names:
    <?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.

  6. Next, we will add 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.

  7. Lastly, for our annotation, we need to define routes for our 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.

  8. Create a 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'
  9. We can use the _entity_form property to tell Drupal to look up the class defined in our handlers.
  10. Before we implement our 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
  11. The _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.
  12. Create the 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);
      }
    }
  13. In our list builder handler, we override the buildHeader and builderRow methods so that we can add our configuration entity's properties to the table.
  14. Now we need to create an entity form, as defined in our form handler array, to handle our add and edit functionalities. Create 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'));
      }
    }
  15. We override the 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.
  16. Next, we will provide a 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
  17. This will instruct Drupal to render the entity.announcement.add_form link on the specified routes in the appears_on value.
  18. Your module structure should look like the following screenshot:
    How to do it…
  19. Install your module and review the Configuration page. You can now manage the Site Announcement entries from the Site Announcement link.

How it works

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.

There's more…

Drupal 8 introduces a typed data system that configuration entities, and fields use.

Available data types for schema definitions

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.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset