Popdan Daniel Adrian
Drupal experience: 1,5 years
From Satu-Mare / Cluj-Napoca
Useful abstraction to group together fields
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
Reusable piece of content
Primitive data type
Can be added to any bundle
Instance of an entity type
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
What we had ?
- fieldable nodes
... non-fieldable users
... non-fieldable comments
... turn users and comments in nodes
=> everything is a node ...
- Field API decoupled from nodes
... generic stdClass objects
... introduced later in Core development
=> unfinished ...
- Entity module to the rescue !!!
- Class based with methods
- Specifically typed objects !!!
- Supported by handlers
- Storage handlers, list handlers, access handlers
- Two different type of entities !!!
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'
Content entity: \Drupal\Core\Entity\EntityListBuilder
Config entity: \Drupal\Core\Entity\ConfigEntityListBuilder
Config/Content entity: \Drupal\Core\Entity\EntityForm
Config entity: \Drupal\Core\Entity\ContentEntityForm
Text
Config/Content entity: \Drupal\Core\Entity\EntityConfirmFormBase
Content entity: \Drupal\content_translation\ContentTranslationHandler
only if 'translatable' is TRUE
Config entity: Configuration Translation module
Config/Content entity: \Drupal\Core\Entity\EntityViewBuilder
Content entity: \Drupal\Core\Entity\EntityAccessControlHandler
admin_permission = "Administer ..."
Content entity: \Drupal\Core\Entity\Sql\SqlContentEntityStorage
Config entity: \Drupal\Core\Config\Entity\ConfigEntityStorage
base_table: The entities base table
data_table: The entities data table
entity keys: The entity keys
Config/Content entity: \Drupal\views\EntityViewsData
links -> "entity.$entity_type_id.$link_template_type"
field_ui_base_route: field settings
bundle_entity_type: the bundle entity type
bundle_of: the base entity type
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",$storage = \Drupal::entityManager()->getStorage('your_entity_type');
// Or if you have a $container variable:
$storage = $container->get('entity.manager')->getStorage('your_entity_type');// 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');$query = \Drupal::entityQueryAggregate('your_entity_type');
// Or:
$query = $query_service->getAggregate('your_entity_type');propertyCondition() or condition() ?
$fids = Drupal::entityQuery('file')
->condition('status', FILE_STATUS_PERMANENT, '<>')
->condition('changed', REQUEST_TIME - $age, '<')
->range(0, 100)
->execute();
$files = $storage->loadMultiple($fids);$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);$entity->access($operation);
$entity->nameOfField->access($operation);Create a 'Contact' entity to add, edit, delete contacts.
Fully fieldable.
The full module can be downloaded from https://www.drupal.org/project/examples
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 {
}/**
* @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",
* )
*/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');
}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');
}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(),
);
}/**
* @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');
}
}
configuration entities are not fieldable
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.
}/**
* @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.
}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
/**
* @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