class TrackingHelper in Search API 8
Provides datasource-independent item change tracking functionality.
Hierarchy
- class \Drupal\search_api\Utility\TrackingHelper implements TrackingHelperInterface
Expanded class hierarchy of TrackingHelper
1 file declares its use of TrackingHelper
- ReferencedEntitiesReindexingTest.php in tests/
src/ Kernel/ Datasource/ ReferencedEntitiesReindexingTest.php
1 string reference to 'TrackingHelper'
1 service uses TrackingHelper
File
- src/
Utility/ TrackingHelper.php, line 28
Namespace
Drupal\search_api\UtilityView source
class TrackingHelper implements TrackingHelperInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The event dispatcher.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* The fields helper.
*
* @var \Drupal\search_api\Utility\FieldsHelperInterface
*/
protected $fieldsHelper;
/**
* The cache backend.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cache;
/**
* Constructs a new class instance.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entity type manager.
* @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
* The language manager.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
* The event dispatcher.
* @param \Drupal\search_api\Utility\FieldsHelperInterface $fieldsHelper
* The fields helper.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
* The cache backend.
*/
public function __construct(EntityTypeManagerInterface $entityTypeManager, LanguageManagerInterface $languageManager, EventDispatcherInterface $eventDispatcher, FieldsHelperInterface $fieldsHelper, CacheBackendInterface $cache) {
$this->languageManager = $languageManager;
$this->entityTypeManager = $entityTypeManager;
$this->eventDispatcher = $eventDispatcher;
$this->fieldsHelper = $fieldsHelper;
$this->cache = $cache;
}
/**
* {@inheritdoc}
*/
public function trackReferencedEntityUpdate(EntityInterface $entity, bool $deleted = FALSE) {
/** @var \Drupal\search_api\IndexInterface[] $indexes */
$indexes = [];
try {
$indexes = $this->entityTypeManager
->getStorage('search_api_index')
->loadMultiple();
} catch (InvalidPluginDefinitionException $e) {
// Can't really happen, but play it safe to appease static code analysis.
} catch (PluginNotFoundException $e) {
// Can't really happen, but play it safe to appease static code analysis.
}
// Original entity, if available.
$original = $deleted ? NULL : $entity->original ?? NULL;
foreach ($indexes as $index) {
// Map of foreign entity relations. Will get lazily populated as soon as
// we actually need it.
$map = NULL;
foreach ($index
->getDatasources() as $datasource_id => $datasource) {
if (!$datasource
->canContainEntityReferences()) {
continue;
}
if ($map === NULL) {
$map = $this
->getForeignEntityRelationsMap($index);
// If there are no foreign entities in the index, no need to continue.
if (!$map) {
break 1;
}
}
$item_ids = $datasource
->getAffectedItemsForEntityChange($entity, $map, $original);
if (!empty($item_ids)) {
$index
->trackItemsUpdated($datasource_id, $item_ids);
}
}
}
}
/**
* Analyzes the index fields and constructs a map of entity references.
*
* This map tries to record all ways that entities' values are indirectly
* indexed by the given index. (That is, what items' indexed contents might be
* affected by a given entity being updated or deleted.)
*
* @param \Drupal\search_api\IndexInterface $index
* The index for which to create the map.
*
* @return array[]
* A (numerically keyed) array of foreign relationship mappings. Each
* sub-array represents a single known relationship. Such sub-arrays will
* have the following structure:
* - datasource: (string) The ID of the datasource which contains this
* relationship.
* - entity_type: (string) The entity type that is referenced from the
* index.
* - bundles: (string[]) An optional array of particular entity bundles that
* are referred to from the index. An empty array here means that the
* index refers to all the bundles.
* - property_path_to_foreign_entity: (string) Property path where the index
* refers to this entity.
* - field_name: (string) Name of the field on the referenced entity that is
* indexed in the search index.
*/
protected function getForeignEntityRelationsMap(IndexInterface $index) : array {
$cid = "search_api:{$index->id()}:foreign_entities_relations_map";
$cache = $this->cache
->get($cid);
if ($cache) {
return $cache->data;
}
$cacheability = new CacheableMetadata();
$cacheability
->addCacheableDependency($index);
$data = [];
foreach ($index
->getFields() as $field) {
try {
$datasource = $field
->getDatasource();
} catch (SearchApiException $e) {
continue;
}
if (!$datasource) {
continue;
}
$relation_info = [
'datasource' => $datasource
->getPluginId(),
'entity_type' => NULL,
'bundles' => NULL,
'property_path_to_foreign_entity' => NULL,
];
$seen_path_chunks = [];
$property_definitions = $datasource
->getPropertyDefinitions();
$field_property = Utility::splitPropertyPath($field
->getPropertyPath(), FALSE);
for (; $field_property[0]; $field_property = Utility::splitPropertyPath($field_property[1], FALSE)) {
$property_definition = $this->fieldsHelper
->retrieveNestedProperty($property_definitions, $field_property[0]);
if (!$property_definition) {
// Seems like we could not map it from the property path to some Typed
// Data definition. In the absence of a better alternative, let's
// simply disregard this field.
break;
}
$seen_path_chunks[] = $field_property[0];
if ($property_definition instanceof FieldItemDataDefinitionInterface && $property_definition
->getFieldDefinition()
->isComputed()) {
// We cannot really deal with computed fields since we have no
// knowledge about their internal logic. Thus we cannot process
// this field any further.
break;
}
if ($relation_info['entity_type'] && $property_definition instanceof FieldItemDataDefinitionInterface) {
// Parent is an entity. Hence this level is fields of the entity.
$cacheability
->addCacheableDependency($property_definition
->getFieldDefinition());
$data[] = $relation_info + [
'field_name' => $property_definition
->getFieldDefinition()
->getName(),
];
}
$entity_reference = $this
->isEntityReferenceDataDefinition($property_definition, $cacheability);
if ($entity_reference) {
// Unfortunately, the nested "entity" property for entity reference
// fields comes without a bundles restriction, so we need to copy the
// bundles information from the level above (on the field itself), if
// any.
if ($relation_info['entity_type'] === $entity_reference['entity_type'] && empty($entity_reference['bundles']) && !empty($relation_info['bundles']) && $field_property[0] === 'entity') {
$entity_reference['bundles'] = $relation_info['bundles'];
}
$relation_info = $entity_reference;
$relation_info['property_path_to_foreign_entity'] = implode(IndexInterface::PROPERTY_PATH_SEPARATOR, $seen_path_chunks);
$relation_info['datasource'] = $datasource
->getPluginId();
}
if ($property_definition instanceof ComplexDataDefinitionInterface) {
$property_definitions = $this->fieldsHelper
->getNestedProperties($property_definition);
}
else {
// This item no longer has "nested" properties in its Typed Data
// definition. Thus we cannot examine it any further than the current
// point.
break;
}
}
}
// Let other modules alter this information, potentially adding more
// relationships.
$event = new MappingForeignRelationshipsEvent($index, $data, $cacheability);
$this->eventDispatcher
->dispatch(SearchApiEvents::MAPPING_FOREIGN_RELATIONSHIPS, $event);
$this->cache
->set($cid, $data, $cacheability
->getCacheMaxAge(), $cacheability
->getCacheTags());
return $data;
}
/**
* Determines whether the given property is a reference to an entity.
*
* @param \Drupal\Core\TypedData\DataDefinitionInterface $property_definition
* The property to test.
* @param \Drupal\Core\Cache\RefinableCacheableDependencyInterface $cacheability
* A cache metadata object to track any caching information necessary in
* this method call.
*
* @return array
* This method will return an empty array if $property is not an entity
* reference. Otherwise it will return an associative array with the
* following structure:
* - entity_type: (string) The entity type to which $property refers.
* - bundles: (array) A list of bundles to which $property refers. In case
* specific bundles cannot be determined or the $property points to all
* the bundles, this key will contain an empty array.
*/
protected function isEntityReferenceDataDefinition(DataDefinitionInterface $property_definition, RefinableCacheableDependencyInterface $cacheability) : array {
$return = [];
if ($property_definition instanceof FieldItemDataDefinitionInterface && $property_definition
->getFieldDefinition()
->getType() === 'entity_reference') {
$field = $property_definition
->getFieldDefinition();
$cacheability
->addCacheableDependency($field);
$return['entity_type'] = $field
->getSetting('target_type');
$field_settings = $field
->getSetting('handler_settings');
$return['bundles'] = $field_settings['target_bundles'] ?? [];
}
elseif ($property_definition instanceof EntityDataDefinitionInterface) {
$return['entity_type'] = $property_definition
->getEntityTypeId();
$return['bundles'] = $property_definition
->getBundles() ?: [];
}
return $return;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
TrackingHelper:: |
protected | property | The cache backend. | |
TrackingHelper:: |
protected | property | The entity type manager. | |
TrackingHelper:: |
protected | property | The event dispatcher. | |
TrackingHelper:: |
protected | property | The fields helper. | |
TrackingHelper:: |
protected | property | The language manager. | |
TrackingHelper:: |
protected | function | Analyzes the index fields and constructs a map of entity references. | |
TrackingHelper:: |
protected | function | Determines whether the given property is a reference to an entity. | |
TrackingHelper:: |
public | function |
Reacts to an entity being updated or deleted. Overrides TrackingHelperInterface:: |
|
TrackingHelper:: |
public | function | Constructs a new class instance. |