.../field/lib/Drupal/field/Entity/FieldConfig.php | 7 +++ .../Drupal/field/Entity/FieldInstanceConfig.php | 7 +++ .../lib/Drupal/node/Tests/NodeCacheTagsTest.php | 25 +++++++-- .../Tests/Entity/EntityCacheTagsTestBase.php | 51 +++++++++++++++-- .../Entity/EntityWithUriCacheTagsTestBase.php | 58 +++++++++++++++++++- .../Controller/EntityTestController.php | 46 ++++++++++++++-- .../lib/Drupal/taxonomy/Entity/Vocabulary.php | 4 ++ .../taxonomy/VocabularyStorageController.php | 3 +- 8 files changed, 183 insertions(+), 18 deletions(-) diff --git a/core/modules/field/lib/Drupal/field/Entity/FieldConfig.php b/core/modules/field/lib/Drupal/field/Entity/FieldConfig.php index 503fe14..0cb62ff 100644 --- a/core/modules/field/lib/Drupal/field/Entity/FieldConfig.php +++ b/core/modules/field/lib/Drupal/field/Entity/FieldConfig.php @@ -378,6 +378,13 @@ protected function preSaveUpdated(EntityStorageControllerInterface $storage_cont public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { // Clear the cache. field_cache_clear(); + + // Invalidate the render cache for all affected entities. + $entity_manager = \Drupal::entityManager(); + $entity_type = $this->getTargetEntityTypeId(); + if ($entity_manager->hasController($entity_type, 'view_builder')) { + $entity_manager->getViewBuilder($entity_type)->resetCache(); + } } /** diff --git a/core/modules/field/lib/Drupal/field/Entity/FieldInstanceConfig.php b/core/modules/field/lib/Drupal/field/Entity/FieldInstanceConfig.php index 6024061..c398ffc 100644 --- a/core/modules/field/lib/Drupal/field/Entity/FieldInstanceConfig.php +++ b/core/modules/field/lib/Drupal/field/Entity/FieldInstanceConfig.php @@ -368,6 +368,13 @@ public function preSave(EntityStorageControllerInterface $storage_controller) { public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { // Clear the cache. field_cache_clear(); + + // Invalidate the render cache for all affected entities. + $entity_manager = \Drupal::entityManager(); + $entity_type = $this->getTargetEntityTypeId(); + if ($entity_manager->hasController($entity_type, 'view_builder')) { + $entity_manager->getViewBuilder($entity_type)->resetCache(); + } } /** diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeCacheTagsTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeCacheTagsTest.php index b97e653..514c2d9 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeCacheTagsTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeCacheTagsTest.php @@ -7,6 +7,7 @@ namespace Drupal\node\Tests; +use Drupal\Core\Entity\EntityInterface; use Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase; /** @@ -35,7 +36,7 @@ public function setUp() { // Give anonymous users permission to view nodes, so that we can verify the // cache tags of cached versions of node pages. $user_role = entity_load('user_role', DRUPAL_ANONYMOUS_RID); - $user_role->grantPermission('bypass node access'); + $user_role->grantPermission('acess content'); $user_role->save(); } @@ -43,14 +44,26 @@ public function setUp() { * {@inheritdoc} */ protected static function createEntity() { + // Create a "Camelids" node type. + entity_create('node_type', array( + 'name' => 'Camelids', + 'type' => 'camelids', + ))->save(); + // Create a "Llama" node. - $node = entity_create('node', array( - 'title' => 'Llama', - 'type' => 'article', - )); - $node->save(); + $node = entity_create('node', array('type' => 'camelids')); + $node->setTitle('Llama') + ->setPublished(TRUE) + ->save(); return $node; } + /** + * {@inheritdoc} + */ + protected static function getAdditionalCacheTagsForEntity(EntityInterface $node) { + return array('user:' . $node->getOwnerId()); + } + } diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php index c49b0f3..a83ec32 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php @@ -22,7 +22,7 @@ * * @var array */ - public static $modules = array('entity_reference', 'entity_test'); + public static $modules = array('entity_reference', 'entity_test', 'field_test'); /** * The main entity used for testing. @@ -57,9 +57,41 @@ public function setUp() { $user_role->grantPermission('view test entity'); $user_role->save(); - // Create an entity + // Create an entity. $this->entity = static::createEntity(); + // If this is a fieldable entity, then add a configurable field. We will use + // this configurable field in later tests to ensure that modifications to + // field (instance) configuration invalidate render cache entries. + if ($this->entity->getEntityType()->isFieldable()) { + // add field, so we can modify the Field and FieldInstance entities to + // verify that changes to those indeed clear cache tags + $field_name = drupal_strtolower($this->randomName()); + $this->field = entity_create('field_config', array( + 'name' => 'configurable_field', + 'entity_type' => $this->entity->getEntityTypeId(), + 'type' => 'test_field', + 'settings' => array(), + )); + $this->field->save(); + + $this->instance = entity_create('field_instance_config', array( + 'entity_type' => $this->entity->getEntityTypeId(), + 'bundle' => $this->entity->bundle(), + 'field_name' => 'configurable_field', + 'label' => 'Configurable field', + 'settings' => array(), + )); + $this->instance->save(); + + // Reload the entity now that a new field has been added to it. + $storage_controller = $this->container + ->get('entity.manager') + ->getStorageController($this->entity->getEntityTypeId()); + $storage_controller->resetCache(); + $this->entity = $storage_controller->load($this->entity->id()); + } + // Create a referencing and a non-referencing entity. list( $this->referencing_entity, @@ -185,8 +217,6 @@ protected static function createReferenceTestEntities($referenced_entity) { public function testReferencedEntity() { $entity_type = $this->entity->getEntityTypeId(); $referencing_entity_path = $this->referencing_entity->getSystemPath(); - $referencing_entity_type = $this->referencing_entity->getEntityTypeId(); - $non_referencing_entity_type = $this->non_referencing_entity->getEntityTypeId(); $non_referencing_entity_path = $this->non_referencing_entity->getSystemPath(); $listing_path = 'entity_test/list/' . $entity_type . '_reference/' . $entity_type . '/' . $this->entity->id(); @@ -271,6 +301,19 @@ public function testReferencedEntity() { $this->verifyPageCache($listing_path, 'HIT'); + // Verify that after modifying the non-referencing entity, there is a cache + // miss for only the non-referencing entity, not for the referencing entity, + // nor for the listing of referencing entities. + $this->pass("Test modification of non-referencing entity.", 'Debug'); + $this->non_referencing_entity->save(); + $this->verifyPageCache($referencing_entity_path, 'HIT'); + $this->verifyPageCache($listing_path, 'HIT'); + $this->verifyPageCache($non_referencing_entity_path, 'MISS'); + + // Verify cache hits. + $this->verifyPageCache($non_referencing_entity_path, 'HIT'); + + // Verify that after modifying the entity's full display, there is a cache // miss for both the referencing entity, and the listing of referencing // entities, but not for the non-referencing entity. diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityWithUriCacheTagsTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityWithUriCacheTagsTestBase.php index 7472ce3..a979174 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityWithUriCacheTagsTestBase.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityWithUriCacheTagsTestBase.php @@ -8,6 +8,7 @@ namespace Drupal\system\Tests\Entity; use Drupal\Core\Cache\Cache; +use Drupal\Core\Entity\EntityInterface; use Drupal\system\Tests\Entity\EntityCacheTagsTestBase; /** @@ -16,6 +17,20 @@ abstract class EntityWithUriCacheTagsTestBase extends EntityCacheTagsTestBase { /** + * Returns the additional (non-standard) cache tags for the tested entity. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity to be tested, as created by createEntity(). + * @return array + * An array of the additional cache tags. + * + * @see \Drupal\system\Tests\Entity\EntityCacheTagsTestBase::createEntity() + */ + protected static function getAdditionalCacheTagsForEntity(EntityInterface $entity) { + return array(); + } + + /** * Tests cache tags presence and invalidation of the entity at its URI. * * Tests the following cache tags: @@ -43,7 +58,8 @@ public function testEntityUri() { if (\Drupal::entityManager()->getDefinition($entity_type)->isRenderCacheable()) { $cid = 'entity_view:' . $entity_type . ':' . $this->entity->id() . ':full:stark:r.anonymous'; $cache_entry = \Drupal::cache()->get($cid); - $this->assertIdentical($cache_entry->tags, array($view_cache_tag, $cache_tag)); + $expected_cache_tags = array_merge(array($view_cache_tag, $cache_tag), static::getAdditionalCacheTagsForEntity($this->entity)); + $this->assertIdentical($cache_entry->tags, $expected_cache_tags); } // Verify that after modifying the entity, there is a cache miss. @@ -66,6 +82,46 @@ public function testEntityUri() { $this->verifyPageCache($entity_path, 'HIT'); + $bundle_entity_type = $this->entity->getEntityType()->getBundleEntityType(); + if ($bundle_entity_type !== 'bundle') { + // Verify that after modifying the corresponding bundle entity, there is a + // cache miss. + $this->pass("Test modification of entity's bundle entity.", 'Debug'); + $bundle_entity = entity_load($bundle_entity_type, $this->entity->bundle()); + $bundle_entity->save(); + $this->verifyPageCache($entity_path, 'MISS'); + + // Verify a cache hit. + $this->verifyPageCache($entity_path, 'HIT'); + } + + + if ($this->entity->getEntityType()->isFieldable()) { + // Verify that after modifying a configurable field on the entity, there + // is a cache miss. + $this->pass("Test modification of entity's configurable field.", 'Debug'); + $field_name = $this->entity->getEntityTypeId() . '.configurable_field'; + $field = entity_load('field_config', $field_name); + $field->save(); + $this->verifyPageCache($entity_path, 'MISS'); + + // Verify a cache hit. + $this->verifyPageCache($entity_path, 'HIT'); + + + // Verify that after modifying a configurable field instance on the + // entity, there is a cache miss. + $this->pass("Test modification of entity's configurable field instance.", 'Debug'); + $field_instance_name = $this->entity->getEntityTypeId() . '.' . $this->entity->bundle() . '.configurable_field'; + $field_instance = entity_load('field_instance_config', $field_instance_name); + $field_instance->save(); + $this->verifyPageCache($entity_path, 'MISS'); + + // Verify a cache hit. + $this->verifyPageCache($entity_path, 'HIT'); + } + + // Verify that after invalidating the entity's cache tag directly, there is // a cache miss. $this->pass("Test invalidation of entity's cache tag.", 'Debug'); diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Controller/EntityTestController.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Controller/EntityTestController.php index 30dd485..77b5851 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Controller/EntityTestController.php +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Controller/EntityTestController.php @@ -7,13 +7,42 @@ namespace Drupal\entity_test\Controller; +use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\Query\QueryFactory; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; /** * Controller routines for entity_test routes. */ -class EntityTestController { +class EntityTestController extends ControllerBase { + + /** + * The entity query factory. + * + * @var \Drupal\Core\Entity\Query\QueryFactory + */ + protected $entityQueryFactory; + + /** + * Constructs a new EntityTestController. + * + * @param \Drupal\Core\Entity\Query\QueryFactory + * The entity query factory. + */ + public function __construct(QueryFactory $entity_query_factory) { + $this->entityQueryFactory = $entity_query_factory; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity.query') + ); + } /** * @todo Remove entity_test_add() @@ -52,15 +81,22 @@ public function testAdmin() { */ public function listReferencingEntities($entity_reference_field_name, $referenced_entity_type, $referenced_entity_id) { // Early return if the referenced entity does not exist (or is deleted). - if (entity_load($referenced_entity_type, $referenced_entity_id) === NULL) { + $referenced_entity = $this->entityManager() + ->getStorageController($referenced_entity_type) + ->load($referenced_entity_id); + if ($referenced_entity === NULL) { return array(); } - $query = \Drupal::service('entity.query') + $query = $this->entityQueryFactory ->get('entity_test') ->condition($entity_reference_field_name . '.target_id', $referenced_entity_id); - $entities = entity_load_multiple('entity_test', $query->execute()); - return entity_view_multiple($entities, 'full'); + $entities = $this->entityManager() + ->getStorageController('entity_test') + ->loadMultiple($query->execute()); + return $this->entityManager() + ->getViewBuilder('entity_test') + ->viewMultiple($entities, 'full'); } } diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Vocabulary.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Vocabulary.php index 6a0132a..c7f2f14 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Vocabulary.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Vocabulary.php @@ -129,6 +129,10 @@ public function postSave(EntityStorageControllerInterface $storage_controller, $ // Update bundles. entity_invoke_bundle_hook('rename', 'taxonomy_term', $this->getOriginalId(), $this->id()); } + else { + // Invalidate the render cache for all terms. + \Drupal::entityManager()->getViewBuilder('taxonomy_term')->resetCache(); + } $storage_controller->resetCache($update ? array($this->getOriginalId()) : array()); } diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageController.php index e98c36f..249b0b8 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageController.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageController.php @@ -16,12 +16,11 @@ class VocabularyStorageController extends ConfigStorageController implements VocabularyStorageControllerInterface { /** - * Overrides Drupal\Core\Config\Entity\ConfigStorageController::resetCache(). + * {@inheritdoc} */ public function resetCache(array $ids = NULL) { drupal_static_reset('taxonomy_vocabulary_get_names'); parent::resetCache($ids); - Cache::invalidateTags(array('content' => TRUE)); entity_info_cache_clear(); }