Change record status: 
Project: 
Introduced in branch: 
8.x
Description: 

Every entity has got an access() method which determines the access of a certain user (defaults to the current) to a certain operation (usually view/create/update/delete, additional ones can be used.)

use Drupal\node\Entity\Node;
$entity = Node::load(123);
if ($entity->access('view')) {
  print entity_view($entity, 'full');
}

A default entity access controlhandler (Drupal\Core\Entity\EntityAccessControlHandler) is provided that takes care of a static cache for access checks and also invokes a hook named after the entity_type (hook_{$entity_type}_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account)). This follows the same pattern as hook_node_access() in 7.x. If no hook implementation returns something, checkAccess() is invoked.

A custom access control handler for an entity type can be specified using the following annotation syntax. Such an access control handler must implement EntityAccessControlHandlerInterface, which is a given if the default implementation is extended.

It is also possible to specify an admin_permission annotation if the access logic only depends on that single permission, see [#2108305].

/**
 * @EntityType(
 *   controllers = {
 *     "access" = "Drupal\my_module\MyEntityAccessControlHandler"
 *   },
 * )
 */

Typically, those entity access control handlers override the EntityAccessControlHandler::checkAccess method to profit from the default cache and hook support, or they can override the EntityAccessControlHandler::access() to make sure that certain checks can not be overruled by a hook.

Create access is separated and doesn't get an already instantiated entity object but a context array and optionally the entity bundle. This is to avoid costly object initialization if it's not necessary. Use \Drupal::entityManager()->getAccessControlHandler($entity_type)->createAccess($bundle); to check create access.

class MyEntityAccessControlHandler extends EntityAccessControlHandler {

  public function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) {
    if ($langcode != LanguageInterface::LANGCODE_DEFAULT) {
      return $account->hasPermission('view test entity translations');
    }
    return $account->hasPermission('view test entity');
  }

  public function access(EntityInterface $entity, $operation, $langcode = LANGUAGE_DEFAULT, AccountInterface  $account = NULL) {
    if ($account->hasPermission('bypass node access')) {
      return TRUE;
    }
    if (!$account->hasPermission('access content') && $operation == 'view') {
      return FALSE;
    }
    return parent::access($entity, $operation, $langcode, $account);
  }

  protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
    $configured_types = node_permissions_get_configured_types();
    if (isset($configured_types[$entity_bundle])) {
      return $account->hasPermission('create ' . $entity_bundle . ' content');
    }
  }
}

Modules wishing to assert access control over entities that they do not own may use hook_ENTITY_TYPE_create_access(\Drupal\Core\Session\AccountInterface $account, array $context, $entity_bundle) to assert access to create a new entity.

Old access functions like node_access() and term_access() have been removed.

Impacts: 
Module developers
Updates Done (doc team, etc.)
Online documentation: 
Not done
Theming guide: 
Not done
Module developer documentation: 
Not done
Examples project: 
Not done
Coder Review: 
Not done
Coder Upgrade: 
Not done
Other: 
Other updates done