Skip to content

2. Viewing entities on a page

Viewing an entity on a page requires a route on which the entity’s field values are output on a given path. This can be automated by amending the entity class attributes.

2.1. Install the contributed Entity API module.

Section titled “2.1. Install the contributed Entity API module.”

The contributed Entity API provides various enhancements to the core Entity API. One such enhancement is the ability to more easily provide permissions entity types which we will now use.

  • Run drush pm:enable entity or visit /admin/modules and install the Entity API module

  • Add the following to the dependencies section of event.info.yml:

      - entity:entity
  • Add the following use statements to src/Entity/Event.php:

    use Drupal\entity\EntityAccessControlHandler;
    use Drupal\entity\EntityPermissionProvider;
    use Drupal\entity\Routing\DefaultHtmlRouteProvider;
  • Add the following to the annotation in src/Entity/Event.php:

    handlers: [
      'access' => EntityAccessControlHandler::class,
      'permission_provider' => EntityPermissionProvider::class,
      'route_provider' => [
        'default' => DefaultHtmlRouteProvider::class,
      ],
    ],
    links: [
      'canonical' => "/event/{event}",
    ],
    admin_permission: 'administer event',
    The entire Event.php file at this point
    <?php
    
    namespace Drupal\event\Entity;
    
    use Drupal\Core\Entity\Attribute\ContentEntityType;
    use Drupal\Core\Entity\ContentEntityBase;
    use Drupal\Core\StringTranslation\TranslatableMarkup;
    use Drupal\Core\Entity\EntityPublishedInterface;
    use Drupal\Core\Entity\EntityPublishedTrait;
    use Drupal\Core\Entity\EntityTypeInterface;
    use Drupal\Core\Field\BaseFieldDefinition;
    use Drupal\entity\EntityAccessControlHandler;
    use Drupal\entity\EntityPermissionProvider;
    use Drupal\entity\Routing\DefaultHtmlRouteProvider;
    use Drupal\user\EntityOwnerInterface;
    use Drupal\user\EntityOwnerTrait;
    use Drupal\Core\Datetime\DrupalDateTime;
    use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
    
    /**
    * Defines the event entity class.
    */
    #[ContentEntityType(
    id: 'event',
    label: new TranslatableMarkup('Event'),
    entity_keys: [
        'id' => 'id',
        'uuid' => 'uuid',
        'label' => 'title',
        'owner' => 'author',
        'published' => 'published',
    ],
    handlers: [
        'access' => EntityAccessControlHandler::class,
        'permission_provider' => EntityPermissionProvider::class,
        'route_provider' => [
            'default' => DefaultHtmlRouteProvider::class,
        ],
    ],
    links: [
        'canonical' => "/event/{event}",
    ],
    admin_permission: 'administer event',
    base_table: 'event',
    )]
    class Event extends ContentEntityBase implements EntityOwnerInterface, EntityPublishedInterface {
    
        use EntityOwnerTrait, EntityPublishedTrait;
    
        public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
            // Get the field definitions for 'id' and 'uuid' from the parent.
            $fields = parent::baseFieldDefinitions($entity_type);
    
            $fields['title'] = BaseFieldDefinition::create('string')
            ->setLabel(t('Title'))
            ->setRequired(TRUE);
    
            $fields['date'] = BaseFieldDefinition::create('datetime')
            ->setLabel(t('Date'))
            ->setRequired(TRUE);
    
            $fields['description'] = BaseFieldDefinition::create('text_long')
            ->setLabel(t('Description'));
    
            // Get the field definitions for 'author' and 'published' from the trait.
            $fields += static::ownerBaseFieldDefinitions($entity_type);
            $fields += static::publishedBaseFieldDefinitions($entity_type);
    
            return $fields;
        }
    
        /**
         * @return string
         */
        public function getTitle() {
            return $this->get('title')->value;
        }
    
        /**
         * @param string $title
         *
         * @return $this
         */
        public function setTitle($title) {
            return $this->set('title', $title);
        }
    
       /**
        * @return \Drupal\Core\Datetime\DrupalDateTime
        */
        public function getDate() {
           return $this->get('date')->date;
        }
    
        /**
         * @param \Drupal\Core\Datetime\DrupalDateTime $date
         *
         * @return $this
         */
        public function setDate(DrupalDateTime $date) {
            return $this->set('date', $date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT));
        }
    
        /**
         * @return \Drupal\filter\Render\FilteredMarkup
         */
        public function getDescription() {
            return $this->get('description')->processed;
        }
    
        /**
         * @param string $description
         * @param string $format
         *
         * @return $this
         */
        public function setDescription($description, $format) {
            return $this->set('description', [
                'value' => $description,
                'format' => $format,
            ]);
        }
    }
    More information on the above
    • Entity handlers:
    handlers: [
        ...
    ],

    Entity handlers are objects that take over certain tasks related to entities. Each entity type can declare which handler it wants to use for which task. In many cases - as can be seen above - Drupal core provides generic handlers that can be used as is. In other cases or when more advanced functionality is required, custom handlers can be used instead.

    • Route providers:
    'route_provider' => [
        'default' => DefaultHtmlRouteProvider::class,
    ],

    Instead of declaring routes belonging to entities in a *.routing.yml file like other routes, they can be provided by a handler, as well. This has the benefit of being able to re-use the same route provider for multiple entity types, as is proven by the usage of the generic route provider provided by core.

    • Links:
    links: [
        'canonical' => "/event/{event}",
    ],

    Entity links denote at which paths on the website we can see an entity (or multiple entities) of the given type. They are used by the default route provider to set the path of the generated route. The usage of canonical (instead of view, for example) stems from the specification of link relations in the web by the IANA.

    See Wikipedia: Link relation and IANA: Link relations for more information.

  • Rebuild caches

    Run drush cache:rebuild

  • Verify the route has been generated

    Visit /event/2

    Note that an empty page is shown. However, no field values are shown.

Which fields to display when rendering the entity, as well as how to display them, can be configured as part of the field definitions. Fields are not displayed unless explicitly configured to.

  • Add the following to the $fields['date'] section of the baseFieldDefinitions() method of the Event class before the semicolon:

    ->setDisplayOptions('view', [
      'label' => 'inline',
      'settings' => [
        'format_type' => 'html_date',
      ],
      'weight' => 0,
    ])
    More information on the above
    • Display mode:

      ->setDisplayOptions('view'

      Display options can be set for two different display modes: view and form. Form display options will be set below.

    • Label display:

      'label' => 'inline',

      The field label can be configured to be displayed above the field value (the default), inline in front of the field value or hidden altogether. The respective values of the label setting are above, inline and hidden.

    • Formatter settings:

      'settings' => [
          'format_type' => 'html_date',
      ],

      Each field is displayed using a formatter. The field type declares a default formatter which is used unless a different formatter is chosen by specifying a type key in the display options. Some formatters have settings which can be configured through the settings key in the display options. There is no list of IDs of available field types, but Drupal API: List of classes annotated with FieldFormatter lists all field formatter classes (for all field types) in core. The ID of a given field formatter can be found in its class documentation or by inspecting the @FieldFormatter annotation which also lists the field types that the formatter can be used for. Given a formatter class the available settings can be found by inspecting the keys returned by the class’ defaultSettings() method.

    • Weight:

      'weight' => 0,

      Weights allow the order of fields in the rendered output to be different than their declaration order in the baseFieldDefinitions() method. Fields with heigher weights “sink” to the bottom and are displayed after fields with lower weights.

      Altogether, setting the view display options is comparable to using the Manage display table provided by Field UI module, which also allows configuring the label display, formatter, formatter settings and weight for each field.

  • Add the following to the $fields['description'] section of the baseFieldDefinitions() method of the Event class before the semicolon:

    ->setDisplayOptions('view', [
      'label' => 'hidden',
      'weight' => 10,
    ])
The entire Event.php file at this point
<?php

namespace Drupal\event\Entity;

use Drupal\Core\Entity\Attribute\ContentEntityType;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Entity\EntityPublishedTrait;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\entity\EntityAccessControlHandler;
use Drupal\entity\EntityPermissionProvider;
use Drupal\entity\Routing\DefaultHtmlRouteProvider;
use Drupal\user\EntityOwnerInterface;
use Drupal\user\EntityOwnerTrait;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;

/**
* Defines the event entity class.
*/
#[ContentEntityType(
id: 'event',
label: new TranslatableMarkup('Event'),
entity_keys: [
    'id' => 'id',
    'uuid' => 'uuid',
    'label' => 'title',
    'owner' => 'author',
    'published' => 'published',
],
handlers: [
    'access' => EntityAccessControlHandler::class,
    'permission_provider' => EntityPermissionProvider::class,
    'route_provider' => [
        'default' => DefaultHtmlRouteProvider::class,
    ],
],
links: [
    'canonical' => "/event/{event}",
],
admin_permission: 'administer event',
base_table: 'event',
)]
class Event extends ContentEntityBase implements EntityOwnerInterface, EntityPublishedInterface {

    use EntityOwnerTrait, EntityPublishedTrait;

    public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
        // Get the field definitions for 'id' and 'uuid' from the parent.
        $fields = parent::baseFieldDefinitions($entity_type);

        $fields['title'] = BaseFieldDefinition::create('string')
            ->setLabel(t('Title'))
            ->setRequired(TRUE);

        $fields['date'] = BaseFieldDefinition::create('datetime')
            ->setLabel(t('Date'))
            ->setRequired(TRUE)
            ->setDisplayOptions('view', [
                'label' => 'inline',
                'settings' => [
                    'format_type' => 'html_date',
                ],
                'weight' => 0,
            ]);

        $fields['description'] = BaseFieldDefinition::create('text_long')
            ->setLabel(t('Description'))
            ->setDisplayOptions('view', [
                'label' => 'hidden',
                'weight' => 10,
            ]);

        // Get the field definitions for 'author' and 'published' from the trait.
        $fields += static::ownerBaseFieldDefinitions($entity_type);
        $fields += static::publishedBaseFieldDefinitions($entity_type);

        return $fields;
    }

    /**
     * @return string
     */
    public function getTitle() {
        return $this->get('title')->value;
    }

    /**
     * @param string $title
     *
     * @return $this
     */
    public function setTitle($title) {
        return $this->set('title', $title);
    }

    /**
     * @return \Drupal\Core\Datetime\DrupalDateTime
     */
    public function getDate() {
        return $this->get('date')->date;
    }

    /**
     * @param \Drupal\Core\Datetime\DrupalDateTime $date
     *
     * @return $this
     */
    public function setDate(DrupalDateTime $date) {
        return $this->set('date', $date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT));
    }

    /**
     * @return \Drupal\filter\Render\FilteredMarkup
     */
    public function getDescription() {
        return $this->get('description')->processed;
    }

    /**
     * @param string $description
     * @param string $format
     *
     * @return $this
     */
    public function setDescription($description, $format) {
        return $this->set('description', [
            'value' => $description,
            'format' => $format,
        ]);
    }

}
  • Rebuild caches

    Run drush cache:rebuild

  • Verify that the fields are shown

    As the event title is automatically used as a page title we do not explicitly enable the title field for display.

Note that the output of the entity can be further customized by adding a theme function. This is omitted for brevity.