Skip to content

Commit

Permalink
8.1.5
Browse files Browse the repository at this point in the history
- HSD8-1046 Invalidate entities whos date fields have recently passed (#16)
  • Loading branch information
pookmish authored May 7, 2021
2 parents eef6988 + 9859f35 commit 9a653ca
Show file tree
Hide file tree
Showing 8 changed files with 306 additions and 1 deletion.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Stanford Fields

8.x-1.5
--------------------------------------------------------------------------------
_Release Date: 2021-05-07_

- HSD8-1046 Invalidate entities whos date fields have recently passed (#16) (2e56375)

8.1.4
--------------------------------------------------------------------------------
_Release Date: 2021-03-05_
Expand Down
137 changes: 137 additions & 0 deletions src/Service/FieldCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<?php

namespace Drupal\stanford_fields\Service;

use Drupal\Core\Cache\Cache;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\State\StateInterface;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;

/**
* Cache invalidator based on field values.
*
* @package Drupal\stanford_fields\Service
*/
class FieldCache implements FieldCacheInterface {

/**
* Drupal core field manager service.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $fieldManager;

/**
* Drupal core entity manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;

/**
* Drupal core state service.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;

/**
* FieldCron constructor.
*
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $field_manager
* Drupal core field manager service.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* Drupal core entity manager service.
* @param \Drupal\Core\State\StateInterface $state
* Drupal core state service.
*/
public function __construct(EntityFieldManagerInterface $field_manager, EntityTypeManagerInterface $entity_type_manager, StateInterface $state) {
$this->fieldManager = $field_manager;
$this->entityTypeManager = $entity_type_manager;
$this->state = $state;
}

/**
* {@inheritDoc}
*/
public function invalidateDateFieldsCache() {
$date_field_types = ['datetime', 'daterange', 'smartdate'];
$cache_tags = [];

// Loop through all fields to invalidate entities on date fields.
foreach ($this->fieldManager->getFieldMap() as $entity_type => $fields) {
foreach ($fields as $field_name => $field_info) {

// Only invalidate desired date fields.
if (in_array($field_info['type'], $date_field_types)) {
$tags = $this->getExpiredDateCacheTags($entity_type, $field_name, $field_info['bundles']);
$cache_tags = array_merge($cache_tags, $tags);
}
}
}

Cache::invalidateTags(array_unique($cache_tags));
}

/**
* Use an entity query to check for date fields that have recently passed.
*
* @param string $entity_type
* Entity type machine name.
* @param string $field_name
* Field definition name.
* @param array $bundles
* Array of entity bundle names.
*
* @return int[]
* Keyed array of entity ids.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function getExpiredDateCacheTags($entity_type, $field_name, array $bundles = []) {
$entity_storage = $this->entityTypeManager->getStorage($entity_type);
$bundle_key = $this->entityTypeManager->getDefinition($entity_type)
->getKey('bundle');

$field_storage = $this->entityTypeManager->getStorage('field_storage_config');
$now = new DrupalDateTime();
$last_ran = DrupalDateTime::createFromTimestamp($this->state->get('stanford_fields.dates_cleared', 0));

/** @var \Drupal\field\FieldStorageConfigInterface $field_definition */
$field_definition = $field_storage->load("$entity_type.$field_name");
$field_date_format = $field_definition->getSetting('datetime_type') == 'date' ? DateTimeItemInterface::DATE_STORAGE_FORMAT : DateTimeItemInterface::DATETIME_STORAGE_FORMAT;

// Smartdate fields are formatted differently.
if ($field_definition->getType() == 'smartdate') {
$field_date_format = 'U';
}

// Query all entities for the given date field that is between the last ran
// time and the current time.
$query = $entity_storage->getQuery()
->accessCheck(FALSE)
->exists($field_name)
->condition($field_name, $now->format($field_date_format), '<=')
->condition($field_name, $last_ran->format($field_date_format), '>=');

// Some entity types don't have bundles, so don't add the condition if not
// applicable.
if ($bundle_key) {
$query->condition($bundle_key, $bundles, 'IN');
}
$tags = [];

// If no entity ids were found, no tags should be invalidated.
if ($entity_ids = $query->execute()) {
$entities = $entity_storage->loadMultiple($entity_ids);
foreach ($entities as $entity) {
$tags = array_merge($tags, $entity->getCacheTagsToInvalidate());
}
}
return $tags;
}

}
17 changes: 17 additions & 0 deletions src/Service/FieldCacheInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Drupal\stanford_fields\Service;

/**
* Interface FieldCacheInterface.
*
* @package Drupal\stanford_fields\Service
*/
interface FieldCacheInterface {

/**
* Invalidate entities that have date field values that recently passed by.
*/
public function invalidateDateFieldsCache();

}
2 changes: 1 addition & 1 deletion stanford_fields.info.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ type: module
description: 'Field types, widgets and formatters to enhance Drupal and Contrib.'
core_version_requirement: ^8 || ^9
package: Stanford
version: 8.x-1.4
version: 8.x-1.5
dependencies:
- drupal:field
13 changes: 13 additions & 0 deletions stanford_fields.install
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

/**
* @file
* stanford_fields.install
*/

/**
* Implements hook_uninstall().
*/
function stanford_fields_uninstall() {
\Drupal::state()->delete('stanford_fields.dates_cleared');
}
8 changes: 8 additions & 0 deletions stanford_fields.module
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,11 @@ function stanford_fields_field_storage_add_validate(&$form, FormStateInterface $
$form_state->setError($form['new_storage_wrapper']['field_name'], t('Field name is too long. Please keep this field name under @count characters', ['@count' => $allowed_length]));
}
}

/**
* Implements hook_cron().
*/
function stanford_fields_cron() {
\Drupal::service('stanford_fields.field_cache')->invalidateDateFieldsCache();
\Drupal::state()->set('stanford_fields.dates_cleared', time() - 5);
}
4 changes: 4 additions & 0 deletions stanford_fields.services.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
services:
stanford_fields.field_cache:
class: 'Drupal\stanford_fields\Service\FieldCache'
arguments: ['@entity_field.manager', '@entity_type.manager', '@state']
120 changes: 120 additions & 0 deletions tests/src/Kernel/Service/FieldCacheTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php

namespace Drupal\Tests\stanford_fields\Kernel\Service;

use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\KernelTestBase;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;

/**
* Class FieldCacheTest
*
* @package Drupal\Tests\stanford_fields\Kernel\Service
*/
class FieldCacheTest extends KernelTestBase {

/**
* {@inheritDoc}
*/
protected static $modules = [
'system',
'stanford_fields',
'node',
'user',
'datetime',
'field',
];

/**
* Array of cache tags scheduled for invalidation.
*
* @var array
*/
protected $invalidatedTags = [];

/**
* Date field format to save the date as.
*
* @var string
*/
protected $dateFieldFormat = DateTimeItemInterface::DATE_STORAGE_FORMAT;

/**
* {@inheritDoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('node');
$this->installSchema('node', ['node_access']);

$cache_invalidator = $this->createMock(CacheTagsInvalidatorInterface::class);
$cache_invalidator->method('invalidateTags')
->will($this->returnCallback([$this, 'invalidateTagsCallback']));

NodeType::create(['type' => 'page', 'name' => 'Page'])->save();
// Create a comment field attached to a host 'entity_test' entity.
FieldStorageConfig::create([
'entity_type' => 'node',
'type' => 'datetime',
'field_name' => 'field_date',
])->save();
FieldConfig::create([
'entity_type' => 'node',
'bundle' => 'page',
'field_name' => 'field_date',
])->save();
\Drupal::getContainer()->set('cache_tags.invalidator', $cache_invalidator);
}

/**
* Test that the field value will trigger an invalidation.
*/
public function testDateInvalidation() {
\Drupal::service('stanford_fields.field_cache')
->invalidateDateFieldsCache();
$this->assertEmpty($this->invalidatedTags);

$node = Node::create([
'type' => 'page',
'title' => 'foo',
'field_date' => date($this->dateFieldFormat, time() + 60 * 60 * 24 * 3),
]);
$node->save();

$this->invalidatedTags = [];
\Drupal::service('stanford_fields.field_cache')
->invalidateDateFieldsCache();
$this->assertEmpty($this->invalidatedTags);

$node->set('field_date', date($this->dateFieldFormat, time() - 60 * 60 * 24 * 3))
->save();
$this->invalidatedTags = [];
\Drupal::service('stanford_fields.field_cache')
->invalidateDateFieldsCache();
$this->assertTrue(in_array('node:' . $node->id(), $this->invalidatedTags));
}

/**
* Run the same tests as above, but with a date time storage.
*/
public function testDateTimeInvalidation() {
$this->dateFieldFormat = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
FieldStorageConfig::load('node.field_date')
->setSetting('datetime_type', 'datetime')
->save();
$this->testDateInvalidation();
}

/**
* Cache invalidation callback.
*/
public function invalidateTagsCallback($tags) {
$this->invalidatedTags = $tags;
}

}

0 comments on commit 9a653ca

Please sign in to comment.