Commit 2bfb9b04 authored by catch's avatar catch

Issue #1615236 by Berdir, aspilicious: Merge entity controller interfaces,...

Issue #1615236 by Berdir, aspilicious: Merge entity controller interfaces, document and add default entity class definition.
parent c1f05ae4
......@@ -7,16 +7,16 @@
namespace Drupal\Core\File;
use Drupal\entity\EntityDatabaseStorageController;
use Drupal\entity\DatabaseStorageController;
use Drupal\entity\EntityInterface;
/**
* File storage controller for files.
*/
class FileStorageController extends EntityDatabaseStorageController {
class FileStorageController extends DatabaseStorageController {
/**
* Overrides Drupal\entity\EntityDatabaseStorageController::create().
* Overrides Drupal\entity\DatabaseStorageController::create().
*/
public function create(array $values) {
// Automatically detect filename if not set.
......@@ -32,7 +32,7 @@ public function create(array $values) {
}
/**
* Overrides Drupal\entity\EntityDatabaseStorageController::presave().
* Overrides Drupal\entity\DatabaseStorageController::presave().
*/
protected function preSave(EntityInterface $entity) {
$entity->timestamp = REQUEST_TIME;
......@@ -47,7 +47,7 @@ protected function preSave(EntityInterface $entity) {
}
/**
* Overrides Drupal\entity\EntityDatabaseStorageController::preDelete().
* Overrides Drupal\entity\DatabaseStorageController::preDelete().
*/
public function preDelete($entities) {
foreach ($entities as $entity) {
......
......@@ -8,18 +8,18 @@
namespace Drupal\comment;
use Drupal\entity\EntityInterface;
use Drupal\entity\EntityDatabaseStorageController;
use Drupal\entity\DatabaseStorageController;
/**
* Defines the controller class for comments.
*
* This extends the Drupal\entity\EntityDatabaseStorageController class, adding
* This extends the Drupal\entity\DatabaseStorageController class, adding
* required special handling for comment entities.
*/
class CommentStorageController extends EntityDatabaseStorageController {
class CommentStorageController extends DatabaseStorageController {
/**
* Overrides Drupal\entity\EntityDatabaseStorageController::buildQuery().
* Overrides Drupal\entity\DatabaseStorageController::buildQuery().
*/
protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
$query = parent::buildQuery($ids, $conditions, $revision_id);
......@@ -33,7 +33,7 @@ protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE)
}
/**
* Overrides Drupal\entity\EntityDatabaseStorageController::attachLoad().
* Overrides Drupal\entity\DatabaseStorageController::attachLoad().
*/
protected function attachLoad(&$comments, $revision_id = FALSE) {
// Set up standard comment properties.
......@@ -47,7 +47,7 @@ protected function attachLoad(&$comments, $revision_id = FALSE) {
}
/**
* Overrides Drupal\entity\EntityDatabaseStorageController::preSave().
* Overrides Drupal\entity\DatabaseStorageController::preSave().
*
* @see comment_int_to_alphadecimal()
* @see comment_increment_alphadecimal()
......@@ -129,7 +129,7 @@ protected function preSave(EntityInterface $comment) {
}
/**
* Overrides Drupal\entity\EntityDatabaseStorageController::postSave().
* Overrides Drupal\entity\DatabaseStorageController::postSave().
*/
protected function postSave(EntityInterface $comment, $update) {
// Update the {node_comment_statistics} table prior to executing the hook.
......@@ -140,7 +140,7 @@ protected function postSave(EntityInterface $comment, $update) {
}
/**
* Overrides Drupal\entity\EntityDatabaseStorageController::postDelete().
* Overrides Drupal\entity\DatabaseStorageController::postDelete().
*/
protected function postDelete($comments) {
// Delete the comments' replies.
......
......@@ -21,20 +21,24 @@
* An array whose keys are entity type names and whose values identify
* properties of those types that the system needs to know about:
* - label: The human-readable name of the type.
* - entity class: The name of the entity class, defaults to
* Drupal\entity\Entity. The entity class must implement EntityInterface.
* - controller class: The name of the class that is used to load the objects.
* The class has to implement the Drupal\entity\EntityControllerInterface
* interface. Leave blank to use the Drupal\entity\EntityController
* implementation.
* - base table: (used by Drupal\entity\EntityController) The name of the
* entity type's base table.
* - static cache: (used by Drupal\entity\EntityController) FALSE to disable
* static caching of entities during a page request. Defaults to TRUE.
* The class has to implement the
* Drupal\entity\EntityStorageControllerInterface interface. Leave blank
* to use the Drupal\entity\DatabaseStorageController implementation.
* - base table: (used by Drupal\entity\DatabaseStorageController) The
* name of the entity type's base table.
* - static cache: (used by Drupal\entity\DatabaseStorageController)
* FALSE to disable static caching of entities during a page request.
* Defaults to TRUE.
* - field cache: (used by Field API loading and saving of field data) FALSE
* to disable Field API's persistent cache of field data. Only recommended
* if a higher level persistent cache is available for the entity type.
* Defaults to TRUE.
* - load hook: The name of the hook which should be invoked by
* Drupal\entity\EntityController::attachLoad(), for example 'node_load'.
* Drupal\entity\DatabaseStorageController::attachLoad(), for example
* 'node_load'.
* - uri callback: A function taking an entity as argument and returning the
* uri elements of the entity, e.g. 'path' and 'options'. The actual entity
* uri can be constructed by passing these elements to url().
......@@ -131,7 +135,8 @@ function hook_entity_info() {
$return = array(
'node' => array(
'label' => t('Node'),
'controller class' => 'NodeController',
'entity class' => 'Drupal\node\Node',
'controller class' => 'Drupal\node\NodeStorageController',
'base table' => 'node',
'revision table' => 'node_revision',
'uri callback' => 'node_uri',
......@@ -211,8 +216,8 @@ function hook_entity_info() {
*/
function hook_entity_info_alter(&$entity_info) {
// Set the controller class for nodes to an alternate implementation of the
// Drupal\entity\EntityController interface.
$entity_info['node']['controller class'] = 'Drupal\mymodule\MyCustomNodeController';
// Drupal\entity\EntityStorageControllerInterface interface.
$entity_info['node']['controller class'] = 'Drupal\mymodule\MyCustomNodeStorageController';
}
/**
......
......@@ -68,7 +68,8 @@ function entity_get_info($entity_type = NULL) {
foreach ($entity_info as $name => $data) {
$entity_info[$name] += array(
'fieldable' => FALSE,
'controller class' => 'Drupal\entity\EntityController',
'entity class' => 'Drupal\entity\Entity',
'controller class' => 'Drupal\entity\DatabaseStorageController',
'static cache' => TRUE,
'field cache' => TRUE,
'load hook' => $name . '_load',
......@@ -92,7 +93,7 @@ function entity_get_info($entity_type = NULL) {
$entity_info[$name]['bundles'] = array($name => array('label' => $entity_info[$name]['label']));
}
// Prepare entity schema fields SQL info for
// Drupal\entity\EntityControllerInterface::buildQuery().
// Drupal\entity\DatabaseStorageControllerInterface::buildQuery().
if (isset($entity_info[$name]['base table'])) {
$entity_info[$name]['schema_fields_sql']['base table'] = drupal_schema_fields_sql($entity_info[$name]['base table']);
if (isset($entity_info[$name]['revision table'])) {
......@@ -206,8 +207,8 @@ function entity_create_stub_entity($entity_type, $ids) {
*
* @see hook_entity_info()
* @see entity_load_multiple()
* @see Drupal\entity\EntityControllerInterface
* @see Drupal\entity\EntityController
* @see Drupal\entity\EntityStorageControllerInterface
* @see Drupal\entity\DatabaseStorageController
* @see Drupal\entity\EntityFieldQuery
*/
function entity_load($entity_type, $id, $reset = FALSE) {
......@@ -223,13 +224,14 @@ function entity_load($entity_type, $id, $reset = FALSE) {
* database access if loaded again during the same page request.
*
* The actual loading is done through a class that has to implement the
* Drupal\entity\EntityControllerInterface interface. By default,
* Drupal\entity\EntityController is used. Entity types can specify
* that a different class should be used by setting the 'controller class' key
* in hook_entity_info(). These classes can either implement the
* Drupal\entity\EntityControllerInterface interface, or, most
* commonly, extend the Drupal\entity\EntityController class. See
* node_entity_info() and the NodeController in node.module as an example.
* Drupal\entity\EntityStorageControllerInterface interface. By default,
* Drupal\entity\DatabaseStorageController is used. Entity types can
* specify that a different class should be used by setting the
* 'controller class' key in hook_entity_info(). These classes can either
* implement the Drupal\entity\EntityStorageControllerInterface interface, or,
* most commonly, extend the Drupal\entity\DatabaseStorageController
* class. See node_entity_info() and the NodeStorageController in node.module as
* an example.
*
* @param string $entity_type
* The entity type to load, e.g. node or user.
......@@ -249,8 +251,8 @@ function entity_load($entity_type, $id, $reset = FALSE) {
* @todo Remove $conditions in Drupal 8.
*
* @see hook_entity_info()
* @see Drupal\entity\EntityControllerInterface
* @see Drupal\entity\EntityController
* @see Drupal\entity\EntityStorageControllerInterface
* @see Drupal\entity\DatabaseStorageController
* @see Drupal\entity\EntityFieldQuery
*/
function entity_load_multiple($entity_type, $ids = FALSE, $conditions = array(), $reset = FALSE) {
......
......@@ -2,7 +2,7 @@
/**
* @file
* Definition of Drupal\entity\EntityController.
* Definition of Drupal\entity\DatabaseStorageController.
*/
namespace Drupal\entity;
......@@ -12,12 +12,12 @@
/**
* Defines a base entity controller class.
*
* Default implementation of Drupal\entity\EntityControllerInterface.
* Default implementation of Drupal\entity\DatabaseStorageControllerInterface.
*
* This class can be used as-is by most simple entity types. Entity types
* requiring special handling can extend the class.
*/
class EntityController implements EntityControllerInterface {
class DatabaseStorageController implements EntityStorageControllerInterface {
/**
* Static cache of entities.
......@@ -45,7 +45,7 @@ class EntityController implements EntityControllerInterface {
/**
* Additional arguments to pass to hook_TYPE_load().
*
* Set before calling Drupal\entity\EntityController::attachLoad().
* Set before calling Drupal\entity\DatabaseStorageController::attachLoad().
*
* @var array
*/
......@@ -84,7 +84,7 @@ class EntityController implements EntityControllerInterface {
protected $cache;
/**
* Implements Drupal\entity\EntityController::__construct().
* Implements Drupal\entity\EntityStorageControllerInterface::__construct().
*
* Sets basic variables.
*/
......@@ -109,7 +109,7 @@ public function __construct($entityType) {
}
/**
* Implements Drupal\entity\EntityControllerInterface::resetCache().
* Implements Drupal\entity\EntityStorageControllerInterface::resetCache().
*/
public function resetCache(array $ids = NULL) {
if (isset($ids)) {
......@@ -123,7 +123,7 @@ public function resetCache(array $ids = NULL) {
}
/**
* Implements Drupal\entity\EntityControllerInterface::load().
* Implements Drupal\entity\EntityStorageControllerInterface::load().
*/
public function load($ids = array(), $conditions = array()) {
$entities = array();
......@@ -356,4 +356,144 @@ protected function cacheGet($ids, $conditions = array()) {
protected function cacheSet($entities) {
$this->entityCache += $entities;
}
/**
* Implements Drupal\entity\EntityStorageControllerInterface::create().
*/
public function create(array $values) {
$class = isset($this->entityInfo['entity class']) ? $this->entityInfo['entity class'] : 'Drupal\entity\Entity';
return new $class($values, $this->entityType);
}
/**
* Implements Drupal\entity\EntityStorageControllerInterface::delete().
*/
public function delete($ids) {
$entities = $ids ? $this->load($ids) : FALSE;
if (!$entities) {
// If no IDs or invalid IDs were passed, do nothing.
return;
}
$transaction = db_transaction();
try {
$this->preDelete($entities);
foreach ($entities as $id => $entity) {
$this->invokeHook('predelete', $entity);
}
$ids = array_keys($entities);
db_delete($this->entityInfo['base table'])
->condition($this->idKey, $ids, 'IN')
->execute();
// Reset the cache as soon as the changes have been applied.
$this->resetCache($ids);
$this->postDelete($entities);
foreach ($entities as $id => $entity) {
$this->invokeHook('delete', $entity);
}
// Ignore slave server temporarily.
db_ignore_slave();
}
catch (Exception $e) {
$transaction->rollback();
watchdog_exception($this->entityType, $e);
throw new EntityStorageException($e->getMessage, $e->getCode, $e);
}
}
/**
* Implements Drupal\entity\EntityStorageControllerInterface::save().
*/
public function save(EntityInterface $entity) {
$transaction = db_transaction();
try {
// Load the stored entity, if any.
if (!$entity->isNew() && !isset($entity->original)) {
$entity->original = entity_load_unchanged($this->entityType, $entity->id());
}
$this->preSave($entity);
$this->invokeHook('presave', $entity);
if (!$entity->isNew()) {
$return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey);
$this->resetCache(array($entity->{$this->idKey}));
$this->postSave($entity, TRUE);
$this->invokeHook('update', $entity);
}
else {
$return = drupal_write_record($this->entityInfo['base table'], $entity);
// Reset general caches, but keep caches specific to certain entities.
$this->resetCache(array());
$entity->enforceIsNew(FALSE);
$this->postSave($entity, FALSE);
$this->invokeHook('insert', $entity);
}
// Ignore slave server temporarily.
db_ignore_slave();
unset($entity->original);
return $return;
}
catch (Exception $e) {
$transaction->rollback();
watchdog_exception($this->entityType, $e);
throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
}
}
/**
* Acts on an entity before the presave hook is invoked.
*
* Used before the entity is saved and before invoking the presave hook.
*/
protected function preSave(EntityInterface $entity) { }
/**
* Acts on a saved entity before the insert or update hook is invoked.
*
* Used after the entity is saved, but before invoking the insert or update
* hook.
*
* @param $update
* (bool) TRUE if the entity has been updated, or FALSE if it has been
* inserted.
*/
protected function postSave(EntityInterface $entity, $update) { }
/**
* Acts on entities before they are deleted.
*
* Used before the entities are deleted and before invoking the delete hook.
*/
protected function preDelete($entities) { }
/**
* Acts on deleted entities before the delete hook is invoked.
*
* Used after the entities are deleted but before invoking the delete hook.
*/
protected function postDelete($entities) { }
/**
* Invokes a hook on behalf of the entity.
*
* @param $hook
* One of 'presave', 'insert', 'update', 'predelete', or 'delete'.
* @param $entity
* The entity object.
*/
protected function invokeHook($hook, EntityInterface $entity) {
if (!empty($this->entityInfo['fieldable']) && function_exists($function = 'field_attach_' . $hook)) {
$function($this->entityType, $entity);
}
// Invoke the hook.
module_invoke_all($this->entityType . '_' . $hook, $entity);
// Invoke the respective entity-level hook.
module_invoke_all('entity_' . $hook, $entity, $this->entityType);
}
}
<?php
/**
* @file
* Definition of Drupal\entity\EntityControllerInterface.
*/
namespace Drupal\entity;
/**
* Defines a common interface for entity controller classes.
*
* All entity controller classes specified via the 'controller class' key
* returned by hook_entity_info() or hook_entity_info_alter() have to implement
* this interface.
*
* Most simple, SQL-based entity controllers will do better by extending
* Drupal\entity\EntityController instead of implementing this interface
* directly.
*/
interface EntityControllerInterface {
/**
* Constructs a new Drupal\entity\EntityControllerInterface object.
*
* @param $entityType
* The entity type for which the instance is created.
*/
public function __construct($entityType);
/**
* Resets the internal, static entity cache.
*
* @param $ids
* (optional) If specified, the cache is reset for the entities with the
* given ids only.
*/
public function resetCache(array $ids = NULL);
/**
* Loads one or more entities.
*
* @param $ids
* An array of entity IDs, or FALSE to load all entities.
* @param $conditions
* An array of conditions in the form 'field' => $value.
*
* @return
* An array of entity objects indexed by their ids.
*/
public function load($ids = array(), $conditions = array());
}
<?php
/**
* @file
* Definition of Drupal\entity\EntityDatabaseStorageController.
*/
namespace Drupal\entity;
/**
* Implements the entity storage controller interface for the database.
*/
class EntityDatabaseStorageController extends EntityController implements EntityStorageControllerInterface {
/**
* Implements Drupal\entity\EntityStorageControllerInterface::create().
*/
public function create(array $values) {
$class = isset($this->entityInfo['entity class']) ? $this->entityInfo['entity class'] : 'Drupal\entity\Entity';
return new $class($values, $this->entityType);
}
/**
* Implements Drupal\entity\EntityStorageControllerInterface::delete().
*/
public function delete($ids) {
$entities = $ids ? $this->load($ids) : FALSE;
if (!$entities) {
// If no IDs or invalid IDs were passed, do nothing.
return;
}
$transaction = db_transaction();
try {
$this->preDelete($entities);
foreach ($entities as $id => $entity) {
$this->invokeHook('predelete', $entity);
}
$ids = array_keys($entities);
db_delete($this->entityInfo['base table'])
->condition($this->idKey, $ids, 'IN')
->execute();
// Reset the cache as soon as the changes have been applied.
$this->resetCache($ids);
$this->postDelete($entities);
foreach ($entities as $id => $entity) {
$this->invokeHook('delete', $entity);
}
// Ignore slave server temporarily.
db_ignore_slave();
}
catch (Exception $e) {
$transaction->rollback();
watchdog_exception($this->entityType, $e);
throw new EntityStorageException($e->getMessage, $e->getCode, $e);
}
}
/**
* Implements Drupal\entity\EntityStorageControllerInterface::save().
*/
public function save(EntityInterface $entity) {
$transaction = db_transaction();
try {
// Load the stored entity, if any.
if (!$entity->isNew() && !isset($entity->original)) {
$entity->original = entity_load_unchanged($this->entityType, $entity->id());
}
$this->preSave($entity);
$this->invokeHook('presave', $entity);
if (!$entity->isNew()) {
$return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey);
$this->resetCache(array($entity->{$this->idKey}));
$this->postSave($entity, TRUE);
$this->invokeHook('update', $entity);
}
else {
$return = drupal_write_record($this->entityInfo['base table'], $entity);
// Reset general caches, but keep caches specific to certain entities.
$this->resetCache(array());
$entity->enforceIsNew(FALSE);
$this->postSave($entity, FALSE);
$this->invokeHook('insert', $entity);
}
// Ignore slave server temporarily.
db_ignore_slave();
unset($entity->original);
return $return;
}
catch (Exception $e) {
$transaction->rollback();
watchdog_exception($this->entityType, $e);
throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
}
}
/**
* Acts on an entity before the presave hook is invoked.
*
* Used before the entity is saved and before invoking the presave hook.
*/
protected function preSave(EntityInterface $entity) { }
/**
* Acts on a saved entity before the insert or update hook is invoked.
*
* Used after the entity is saved, but before invoking the insert or update
* hook.
*
* @param $update
* (bool) TRUE if the entity has been updated, or FALSE if it has been
* inserted.
*/
protected function postSave(EntityInterface $entity, $update) { }
/**
* Acts on entities before they are deleted.
*
* Used before the entities are deleted and before invoking the delete hook.
*/
protected function preDelete($entities) { }
/**
* Acts on deleted entities before the delete hook is invoked.
*
* Used after the entities are deleted but before invoking the delete hook.
*/
protected function postDelete($entities) { }
/**
* Invokes a hook on behalf of the entity.
*
* @param $hook
* One of 'presave', 'insert', 'update', 'predelete', or 'delete'.
* @param $entity
* The entity object.
*/
protected function invokeHook($hook, EntityInterface $entity) {
if (!empty($this->entityInfo['fieldable']) && function_exists($function = 'field_attach_' . $hook)) {
$function($this->entityType, $entity);
}
// Invoke the hook.
module_invoke_all($this->entityType . '_' . $hook, $entity);
// Invoke the respective entity-level hook.
module_invoke_all('entity_' . $hook, $entity, $this->entityType);
}
}
......@@ -8,9 +8,47 @@
namespace Drupal\entity;
/**
* Defines a common interface for entity storage controllers.
* Defines a common interface for entity controller classes.
*
* All entity controller classes specified via the 'controller class' key
* returned by hook_entity_info() or hook_entity_info_alter() have to implement
* this interface.
*
* Most simple, SQL-based entity controllers will do better by extending
* Drupal\entity\DatabaseStorageController instead of implementing this
* interface directly.
*/
interface EntityStorageControllerInterface extends EntityControllerInterface {
interface EntityStorageControllerInterface {
/**
* Constructs a new Drupal\entity\EntityStorageControllerInterface object.
*
* @param $entityType
* The entity type for which the instance is created.
*/
public function __construct($entityType);
/**
* Resets the internal, static entity cache.
*
* @param $ids
* (optional) If specified, the cache is reset for the entities with the
* given ids only.
*/
public function resetCache(array $ids = NULL);
/**
* Loads one or more entities.
*
* @param $ids
* An array of entity IDs, or FALSE to load all entities.
* @param $conditions
* An array of conditions in the form 'field' => $value.
*
* @return
* An array of entity objects indexed by their ids.
*/