ENTITIES IN

 

Popdan Daniel Adrian

Drupal experience: 1,5 years

From Satu-Mare / Cluj-Napoca

About me

 

Useful abstraction to group together fields

  • Node
  • Comment
  • User
  • Taxonomy

Entity type

 

Implementation of an entity type to which fields can be attached

Can be considered as subtypes of an entity type

Can add different fields for each bundle

Bundles

 

Reusable piece of content

Primitive data type

Can be added to any bundle

Fields

 

Instance of an entity type

Entity

 

An entity type is a base class

A bundle is an extended class

A field is a class member, property, variable or field instance

An entity is an object or instance of a base or extended class

Putting this in Object-Oriented Design/Programming terms...

 

What we had ?

- fieldable nodes

... non-fieldable users

... non-fieldable comments

Drupal 6

 

... turn users and comments in nodes

=> everything is a node ...

Solution ?

Drupal 7

 

 

- Field API decoupled from nodes

... generic stdClass objects

... introduced later in Core development

=> unfinished ...

- Entity module to the rescue !!!

Drupal 7

 

- Class based with methods

- Specifically typed objects !!!

- Supported by handlers

- Storage handlers, list handlers, access handlers

- Two different type of entities !!!

Drupal 8

Content + Config

 

 

Choose a unique machine name for your entity type

Define an interface for your entity extending \Drupal\Core\Config\Entity\ConfigEntityInterface or \Drupal\Core\Entity\ContentEntityInterface

Define a class for your entity extending ConfigEntityBase or \Drupal\Core\Entity\ContentEntityBase and implementing your interface

Add @ConfigEntityType or @ContentEntityType annotation to your class

Add 'id', 'label' and if has bundles 'bundle_label'

Defining an entity type

List builder

Entity annotation

 

Text

Add and edit forms or default form

 

Config/Content entity: \Drupal\Core\Entity\EntityConfirmFormBase

Delete form

 

Content entity: \Drupal\content_translation\ContentTranslationHandler

only if 'translatable' is TRUE

Config entity: Configuration Translation module

Translation

 

Config/Content entity: \Drupal\Core\Entity\EntityViewBuilder

View builder

 

Content entity: \Drupal\Core\Entity\EntityAccessControlHandler

admin_permission = "Administer ..."

  • override checkAccess()
  • override createAccess()
  • do not override access()

Access

Storage

 

base_table: The entities base table

data_table: The entities data table

entity keys: The entity keys

Tables and keys

 

Config/Content entity: \Drupal\views\EntityViewsData

Views data

 

links -> "entity.$entity_type_id.$link_template_type"

  • canonical
  • delete-form
  • edit-form
  • delete-form

Links

 

field_ui_base_route: field settings

bundle_entity_type: the bundle entity type

bundle_of: the base entity type

Fields and bundles

Entity routes

entity.block.edit_form:
  path: '/admin/structure/block/manage/{block}'
  defaults:
    _entity_form: 'block.default'
    _title: 'Configure block'
  requirements:
    _entity_access: 'block.update'

...

handlers = {
  "form" = {
    "default" = "Drupal\block\BlockForm",

Entity manager

$storage = \Drupal::entityManager()->getStorage('your_entity_type');

// Or if you have a $container variable:
$storage = $container->get('entity.manager')->getStorage('your_entity_type');

Entity query

// Simple query:
$query = \Drupal::entityQuery('your_entity_type');

// Or, if you have a $container variable:
$query_service = $container->get('entity.query');
$query = $query_service->get('your_entity_type');

Entity Query

$query = \Drupal::entityQueryAggregate('your_entity_type');

// Or:
$query = $query_service->getAggregate('your_entity_type');

 

propertyCondition() or condition() ?

Drupal 7

 

  • only condition()

Drupal 8

$fids = Drupal::entityQuery('file')
  ->condition('status', FILE_STATUS_PERMANENT, '<>')
  ->condition('changed', REQUEST_TIME - $age, '<')
  ->range(0, 100)
  ->execute();
$files = $storage->loadMultiple($fids);

Rendering an entity

$view_builder = \Drupal::entityManager()->getViewBuilder('your_entity_type');
// Or if you have a $container variable:
$view_builder = $container->get('entity.manager')->getViewBuilder('your_entity_type');

// You can omit the language ID if the default language is being used.
$build = $view_builder->view($entity, 'view_mode_name', $language->getId());

// $build is a render array.
$rendered = drupal_render($build);

Access checking on entities

$entity->access($operation);
$entity->nameOfField->access($operation);

Creating a content entity

 

Create a 'Contact' entity to add, edit, delete contacts.

Fully fieldable.

The full module can be downloaded from https://www.drupal.org/project/examples

Problem description

Contact Interface

namespace Drupal\content_entity_example;

use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\user\EntityOwnerInterface;

/**
* Provides an interface defining a Contact entity.
* @ingroup content_entity_example
*/
interface ContactInterface extends ContentEntityInterface, EntityOwnerInterface {

}

Contact Annotations

/**
 * @ContentEntityType(
 *   id = "content_entity_example_contact",
 *   label = @Translation("Contact entity"),
 *   handlers = {
 *     "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
 *     "list_builder" = "Drupal\content_entity_example\Entity\Controller\ContactListBuilder",
 *     "form" = {
 *       "add" = "Drupal\content_entity_example\Form\ContactForm",
 *       "edit" = "Drupal\content_entity_example\Form\ContactForm",
 *       "delete" = "Drupal\content_entity_example\Form\ContactDeleteForm",
 *     },
 *     "access" = "Drupal\content_entity_example\ContactAccessControlHandler",
 *   },
 *   base_table = "contact",
 *   admin_permission = "administer content_entity_example entity",
 *   fieldable = TRUE,
 *   entity_keys = {
 *     "id" = "id",
 *     "label" = "name",
 *     "uuid" = "uuid"
 *   },
 *   links = {
 *     "canonical" = "content_entity_example_contact/{content_entity_example_contact}",
 *     "edit-form" = "/content_entity_example_contact/{content_entity_example_contact}/edit",
 *     "delete-form" = "/contact/{content_entity_example_contact}/delete",
 *     "collection" = "/content_entity_example_contact/list"
 *   },
 *   field_ui_base_route = "content_entity_example.contact_settings",
 * )
 */

HOOK_ENTITY_TYPE_BUILD

function hook_entity_type_build(array &$entity_types) {
  /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
  // Add a form for a custom node form without overriding the default
  // node form. To override the default node form, use hook_entity_type_alter().
  $entity_types['node']->setFormClass('mymodule_foo', 'Drupal\mymodule\NodeFooForm');
}

HOOK_ENTITY_TYPE_ALTER

function hook_entity_type_alter(array &$entity_types) {
  /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
  // Set the controller class for nodes to an alternate implementation of the
  // Drupal\Core\Entity\EntityStorageInterface interface.
  $entity_types['node']->setStorageClass('Drupal\mymodule\MyCustomNodeStorage');
}

CONTACT

class Contact extends ContentEntityBase implements ContactInterface {

  /**
   * {@inheritdoc}
   *
   * When a new entity instance is added, set the user_id entity reference to
   * the current user as the creator of the instance.
   */
  public static function preCreate(EntityStorageInterface $storage_controller, array &$values) {
    parent::preCreate($storage_controller, $values);
    $values += array(
      'user_id' => \Drupal::currentUser()->id(),
    );
  }

ACCESS HANDLER

/**
 * @file
 * Contains \Drupal\content_entity_example\ContactAccessControlHandler
 */

namespace Drupal\content_entity_example;

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;

/**
 * Access controller for the comment entity.
 *
 * @see \Drupal\comment\Entity\Comment.
 */
class ContactAccessControlHandler extends EntityAccessControlHandler {

  /**
   * {@inheritdoc}
   *
   * Link the activities to the permissions. checkAccess is called with the
   * $operation as defined in the routing.yml file.
   */
  protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) {
    switch ($operation) {
      case 'view':
        return AccessResult::allowedIfHasPermission($account, 'view contact entity');

      case 'edit':
        return AccessResult::allowedIfHasPermission($account, 'edit contact entity');

      case 'delete':
        return AccessResult::allowedIfHasPermission($account, 'delete contact entity');
    }

    return AccessResult::allowed();
  }

  /**
   * {@inheritdoc}
   *
   * Separate from the checkAccess because the entity does not yet exist, it
   * will be created during the 'add' process.
    */
  protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
    return AccessResult::allowedIfHasPermission($account, 'add contact entity');
  }

}

Configurations in Drupal 7

Configurations in Drupal 8

 

 

  • Content types
  • Views
  • Taxonomy vocabularies
  • Contact forms
  • Image styles

configuration entities are not fieldable

Configurations Entities

Creating a configuration entity

Example interface

namespace Drupal\example;

use Drupal\Core\Config\Entity\ConfigEntityInterface;

/**
 * Provides an interface defining a Example entity.
 */
interface ExampleInterface extends ConfigEntityInterface {
  // Add get/set methods for your configuration properties here.
}

Example class

/**
  * @file
  * Contains \Drupal\example\Entity\Example.
  */

 namespace Drupal\example\Entity;

 use Drupal\Core\Config\Entity\ConfigEntityBase;
 use Drupal\example\ExampleInterface;

 /**
  * Defines the Example entity.
  *
  * @ConfigEntityType(
  *   id = "example",
  *   label = @Translation("Example"),
  *   handlers = {
  *     "list_builder" = "Drupal\example\Controller\ExampleListBuilder",
  *     "form" = {
  *       "add" = "Drupal\example\Form\ExampleForm",
  *       "edit" = "Drupal\example\Form\ExampleForm",
  *       "delete" = "Drupal\example\Form\ExampleDeleteForm"
  *     }
  *   },
  *   config_prefix = "example",
  *   admin_permission = "administer site configuration",
  *   entity_keys = {
  *     "id" = "id",
  *     "label" = "label",
  *   },
  *   links = {
  *     "edit-form" = "/admin/config/system/example/{example}",
  *     "delete-form" = "/admin/config/system/example/{example}/delete"
  *   }
  * )
  */
 class Example extends ConfigEntityBase implements ExampleInterface {

   /**
    * The Example ID.
    *
    * @var string
    */
   public $id;

   /**
    * The Example label.
    *
    * @var string
    */
   public $label;

   // Your specific configuration property get/set methods go here,
   // implementing the interface.
 }

Configuration schema

example.example.*:
  type: config_entity
  label: 'Example config'
  mapping:
   id:
    type: string
    label: 'ID'
   label:
     type: label
     label: 'Label'
     

example/config/schema/example.schema.yml

Entity Form Class

/**
 * @file
 * Contains \Drupal\example\Form\ExampleForm.
 */

namespace Drupal\example\Form;

use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityForm;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Entity\Query\QueryFactory;
use Drupal\Core\Form\FormStateInterface;

class ExampleForm extends EntityForm {

  /**
   * @param \Drupal\Core\Entity\Query\QueryFactory $entity_query
   * The entity query.
   */
  public function __construct(QueryFactory $entity_query) {
    $this->entityQuery = $entity_query;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('entity.query')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function form(array $form, FormStateInterface $form_state) {
    $form = parent::form($form, $form_state);

    $example = $this->entity;

    $form['label'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('Label'),
      '#maxlength' => 255,
      '#default_value' => $example->label(),
      '#description' => $this->t("Label for the Example."),
      '#required' => TRUE,
    );
    $form['id'] = array(
      '#type' => 'machine_name',
      '#default_value' => $example->id(),
      '#machine_name' => array(
        'exists' => array($this, 'exist'),
      ),
      '#disabled' => !$example->isNew(),
    );

    // You will need additional form elements for your custom properties.

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function save(array $form, FormStateInterface $form_state) {
    $example = $this->entity;
    $status = $example->save();

    if ($status) {
      drupal_set_message($this->t('Saved the %label Example.', array('%label' => $example->label())));
    }
    else {
      drupal_set_message($this->t('The %label Example was not saved.', array('%label' => $example->label())));
    }

    $form_state->setRedirect('example.list');
  }

  /**
   * Check if a config with this entity exists.
   *
   * @param int $id
   *   The entity id.
   *
   * @param boolean
   *   'TRUE' if exists, 'FALSE' otherwise.
   */
  public function exist($id) {
    $entity = $this->entityQuery->get('example')
      ->condition('id', $id)
      ->execute();
    return (bool) $entity;
  }

}
             

example/src/Form/ExampleForm.php

Entities in Drupal 8

By Popdan Daniel

Entities in Drupal 8

Working with entities in Drupal 8

  • 1,479