xero_sync-8.x-1.x-dev/src/XeroSyncItemFinder.php
src/XeroSyncItemFinder.php
<?php
namespace Drupal\xero_sync;
use Drupal\xero_sync\Plugin\XeroSync\ItemFinder\ItemFinderInterface;
use Drupal\xero_sync\Annotation\XeroSyncItemFinder as XeroSyncItemFinderAnnotation;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
/**
* Manages discovery, instantiation and chained execution of itemFinder plugins.
*
* @see plugin_api
*/
class XeroSyncItemFinder extends DefaultPluginManager implements XeroSyncItemFinderInterface {
/**
* A static cache of found items.
*
* This takes the form of an array keyed by entity types.
* Each value is itself an array, keyed by entity id, with the item as value.
*
* @var array
*/
protected static $cache;
/**
* Constructs a new XeroSyncItemFinder object.
*
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* The cache backend.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
parent::__construct('Plugin/XeroSync/ItemFinder', $namespaces, $module_handler, ItemFinderInterface::class, XeroSyncItemFinderAnnotation::class);
$this->alterInfo('xero_sync_item_finder_info');
$this->setCacheBackend($cache_backend, 'xero_sync_item_finder_plugins');
}
/**
* {@inheritdoc}
*/
public function processDefinition(&$definition, $plugin_id) {
parent::processDefinition($definition, $plugin_id);
foreach (['id', 'create', 'priority', 'entity_types'] as $required_property) {
if (!isset($definition[$required_property])) {
throw new PluginException(sprintf('The item finder plugin "%s" must define the "%s" property in its annotation.', $plugin_id, $required_property));
}
}
}
/**
* {@inheritdoc}
*/
public function getItem(EntityInterface $entity, $create = FALSE) {
// Get the item from the static cache if it is there.
$type = $entity->getEntityTypeId();
$cached = $this->getItemFromCache($type, $entity->id(), $create);
if ($cached === FALSE) {
// The cache says that item is unfindable.
return NULL;
}
elseif (!is_null($cached)) {
// There is a cached item.
return $cached;
}
// The cache was not informative, so try to find item.
$definitions = $this->getMatchingDefinitions($entity, $create);
$item = $this->getItemFromDefinitions($definitions, $entity);
// Cache the result for reuse.
self::$cache[$type][$entity->id()]['item'] = $item;
self::$cache[$type][$entity->id()]['create'] = $create;
return $item;
}
/**
* {@inheritdoc}
*/
public function clearCache() {
self::$cache = [];
}
/**
* Gets a Xero item from the static cache.
*
* @param string $type
* The entity type.
* @param string|int $id
* The entity id.
* @param bool $create
* Whether create new items is being considered.
*
* @return \Drupal\xero\TypedData\XeroComplexItemInterface|bool|null
* A cached item, or false if finding has been tried without success, or
* null if the cache has no result stored.
*/
protected function getItemFromCache($type, $id, $create) {
$typeCache = self::$cache[$type] ?? NULL;
$entityCache = $typeCache && isset($typeCache[$id]) ? $typeCache[$id] : NULL;
$item = $entityCache ? $entityCache['item'] : NULL;
// If there is no cache, return null to be uninformative.
if (empty($entityCache)) {
return NULL;
}
// If there is a cache and an item, return it.
if ($item) {
return $item;
}
// If creation is now being considered, and hasn't previously been tried,
// return null to indicate the cache results are inconclusive.
if ($create && !$entityCache['create']) {
return NULL;
}
// Return false to indicate the cache shows that no item can be identified.
return FALSE;
}
/**
* Instantiate and execute plugins successively, until an item is found.
*
* @param array $definitions
* An array of plugin definitions.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to find an item for.
*
* @return \Drupal\xero\TypedData\XeroComplexItemInterface|null
* A typed data representation of the Xero item, or NULL if not found.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
*/
protected function getItemFromDefinitions(array $definitions, EntityInterface $entity) {
$item = NULL;
foreach ($definitions as $definition) {
$plugin = $this->createInstance($definition['id'], ['entity' => $entity]);
$item = $plugin->getItem();
if ($item) {
$item->synced = $item->synced ?? FALSE;
break;
}
}
return $item;
}
/**
* Gets a subset of plugin definitions.
*
* They must target the specified entity, create only if allowed, and be
* sorted by priority.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to find an item for.
* @param bool $create
* Whether to use plugins that may create new items if they cannot find
* existing items.
*/
protected function getMatchingDefinitions(EntityInterface $entity, $create = FALSE) {
$definitions = $this->getDefinitions();
$type = $entity->getEntityTypeId();
foreach ($definitions as $id => $definition) {
// Filter out plugins that don't target the current entity type.
$types = $definition['entity_types'];
if (!in_array($type, $types) && !empty($types)) {
unset($definitions[$id]);
continue;
}
// Filter out plugins that may create items, unless definitely allowed.
if (!$create && $definition['create']) {
unset($definitions[$id]);
}
}
// Sort the plugins by priority, descending order.
$priorities = array_column($definitions, 'priority');
array_multisort($priorities, SORT_NUMERIC, SORT_DESC, $definitions);
return $definitions;
}
}
