class FileReferenceResolver
Retrieves file references.
Hierarchy
- class \Drupal\file\FileReferenceResolver
Expanded class hierarchy of FileReferenceResolver
1 file declares its use of FileReferenceResolver
- FileDownloadHook.php in core/
modules/ file/ src/ Hook/ FileDownloadHook.php
File
-
core/
modules/ file/ src/ FileReferenceResolver.php, line 19
Namespace
Drupal\fileView source
class FileReferenceResolver {
/**
* The number of revisions that are queried and returned per entity type.
*/
protected const REVISION_LOOKUP_LIMIT = 50;
/**
* File reference field information, keyed by entity type and bundle.
*
* @var array
*/
protected array $fileFields = [];
/**
* Stores the field column that references the file, keyed by file type.
*
* @var array
*/
protected array $fieldColumns = [];
public function __construct(protected EntityTypeManagerInterface $entityTypeManager, #[Autowire(service: 'cache.memory')] protected MemoryCacheInterface $memoryCache, protected FileUsageInterface $fileUsage) {
}
/**
* Retrieves a list of references to a file.
*
* @param \Drupal\file\FileInterface $file
* A file entity.
*
* @return \Generator<int, \Drupal\file\FileReferenceUsage>
* This yields FileReferenceUsage objects.
*
* @ingroup file
*/
public function getReferences(FileInterface $file) : \Generator {
$cid = 'file_references:' . $file->id();
$cache = $this->memoryCache
->get($cid);
if ($cache) {
return yield from $cache->data;
}
$references = [];
$cacheability_metadata = new CacheableMetadata();
$cacheability_metadata->addCacheTags([
'file_references',
]);
$cacheability_metadata->addCacheableDependency($file);
$revision_reference_count = [];
// Loop over all usages registered by the file module.
$usages_by_file_module = $this->fileUsage
->listUsage($file)['file'] ?? [];
foreach ($usages_by_file_module as $entity_type_id => $entity_ids) {
$entity_type = $this->entityTypeManager
->getDefinition($entity_type_id);
$storage = $this->entityTypeManager
->getStorage($entity_type_id);
$entities = $storage->loadMultiple(array_keys($entity_ids));
$revision_reference_count[$entity_type_id] = 0;
/** @var \Drupal\Core\Entity\FieldableEntityInterface[] $entities */
foreach ($entities as $entity) {
$cacheability_metadata->addCacheableDependency($entity);
// If there are no file reference fields then this is a stale reference
// ignore it.
$file_reference_fields = $this->getFileReferenceFields($entity);
if (!$file_reference_fields) {
continue;
}
$default_revision_match = FALSE;
foreach ($file_reference_fields as $field_name => $field_column) {
if ($this->isFileReferencedByField($entity, $file, $field_name, $field_column)) {
$default_revision_match = TRUE;
$references[] = new FileReferenceUsage($entity->getEntityTypeId(), $field_name, id: $entity->id());
}
}
if (!$default_revision_match && $entity_type->isRevisionable() && $storage instanceof RevisionableStorageInterface) {
// Only attempt to find a limited amount of revisions per entity type,
// very few sites should hit this limit, if they do, they will
// need to override this with a different, if possible optimized
// implementation.
if ($revision_reference_count[$entity_type_id] > static::REVISION_LOOKUP_LIMIT) {
continue;
}
$revision_reference_count[$entity_type_id]++;
// The file reference was not found in any field in the current
// entity, it may be referenced in a non-default revision. At this
// point, the possible fields are known, create a query that fetches
// the most recent revision that references the given file in any of
// those fields.
$revision = $this->getReferencingRevision($entity, $file);
if ($revision === NULL) {
continue;
}
$cacheability_metadata->addCacheableDependency($revision);
foreach ($this->getFileReferenceFields($entity) as $field_name => $field_column) {
if ($this->isFileReferencedByField($revision, $file, $field_name, $field_column)) {
$references[] = new FileReferenceUsage($revision->getEntityTypeId(), $field_name, revisionId: $revision->getRevisionId());
}
}
}
}
}
$this->memoryCache
->set($cid, $references, tags: $cacheability_metadata->getCacheTags());
return yield from $references;
}
/**
* Loads an entity from the passed usage definition.
*
* @param \Drupal\file\FileReferenceUsage $usage
* The file reference usage with either ID or revision ID set.
*
* @return \Drupal\Core\Entity\FieldableEntityInterface
* The entity using the file.
*/
public function loadEntityFromUsage(FileReferenceUsage $usage) : FieldableEntityInterface {
$storage = $this->entityTypeManager
->getStorage($usage->entityTypeId);
if ($usage->id) {
return $storage->load($usage->id);
}
else {
assert($storage instanceof RevisionableStorageInterface);
return $storage->loadRevision($usage->revisionId);
}
}
/**
* Determine whether a field references files stored in {file_managed}.
*
* @param \Drupal\Core\Field\FieldDefinitionInterface $field
* A field definition.
*
* @return string|false
* The field column if the field references {file_managed}.fid, typically
* fid, FALSE if it does not.
*/
protected function findFileReferenceColumns(FieldDefinitionInterface $field) : string|false {
$schema = $field->getFieldStorageDefinition()
->getSchema();
foreach ($schema['foreign keys'] as $data) {
if ($data['table'] == 'file_managed') {
foreach ($data['columns'] as $field_column => $column) {
if ($column == 'fid') {
return $field_column;
}
}
}
}
return FALSE;
}
/**
* Returns file fields for this entity type and bundle.
*
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The entity to return field information for.
*
* @return array<string, string>
* List of field columns keyed by field name.
*/
protected function getFileReferenceFields(FieldableEntityInterface $entity) : array {
$entity_type_id = $entity->getEntityTypeId();
$bundle = $entity->bundle();
if (!isset($this->fileFields[$entity_type_id][$bundle])) {
$this->fileFields[$entity_type_id][$bundle] = [];
// This contains the possible field names.
foreach ($entity->getFieldDefinitions() as $field_name => $field_definition) {
// If this is the first time this field type is seen, check
// whether it references files.
if (!isset($this->fieldColumns[$field_definition->getType()])) {
$this->fieldColumns[$field_definition->getType()] = $this->findFileReferenceColumns($field_definition);
}
// If the field type does reference files then record it.
if ($this->fieldColumns[$field_definition->getType()]) {
$this->fileFields[$entity_type_id][$bundle][$field_name] = $this->fieldColumns[$field_definition->getType()];
}
}
}
return $this->fileFields[$entity_type_id][$bundle];
}
/**
* Returns the most recent revision of a specific entity referencing the file.
*
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The entity to do the query for.
* @param \Drupal\file\FileInterface $file
* The file entity.
*
* @return \Drupal\Core\Entity\FieldableEntityInterface|null
* A revision referencing that file, if there is any.
*/
protected function getReferencingRevision(FieldableEntityInterface $entity, FileInterface $file) : ?FieldableEntityInterface {
$storage = $this->entityTypeManager
->getStorage($entity->getEntityTypeId());
assert($storage instanceof RevisionableStorageInterface);
$entity_type = $this->entityTypeManager
->getDefinition($entity->getEntityTypeId());
$query = $storage->getQuery()
->accessCheck(FALSE)
->allRevisions()
->condition($entity_type->getKey('id'), $entity->id());
$fields_condition = $query->orConditionGroup();
foreach ($this->getFileReferenceFields($entity) as $field_name => $field_column) {
$fields_condition->condition($field_name . '.' . $field_column, $file->id());
}
$results = $query->condition($fields_condition)
->sort($entity_type->getKey('revision'), 'DESC')
->range(0, 1)
->execute();
if ($results) {
return $storage->loadRevision(key($results));
}
return NULL;
}
/**
* Returns whether the file is referenced by the given field.
*
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The entity to check.
* @param \Drupal\file\FileInterface $file
* The file to look for.
* @param string $field_name
* The field name.
* @param string $field_column
* The field column/property.
*
* @return bool
* True if the file is referenced, false if not.
*/
protected function isFileReferencedByField(FieldableEntityInterface $entity, FileInterface $file, string $field_name, string $field_column) : bool {
// Iterate over the field items to find the referenced file and
// field name. We also iterate over all translations because a file
// can be linked to a language other than the default.
foreach ($entity->getTranslationLanguages() as $langcode => $language) {
foreach ($entity->getTranslation($langcode)
->get($field_name) as $item) {
if ($file->id() == $item->{$field_column}) {
return TRUE;
}
}
}
return FALSE;
}
}
Members
| Title Sort descending | Modifiers | Object type | Summary |
|---|---|---|---|
| FileReferenceResolver::$fieldColumns | protected | property | Stores the field column that references the file, keyed by file type. |
| FileReferenceResolver::$fileFields | protected | property | File reference field information, keyed by entity type and bundle. |
| FileReferenceResolver::findFileReferenceColumns | protected | function | Determine whether a field references files stored in {file_managed}. |
| FileReferenceResolver::getFileReferenceFields | protected | function | Returns file fields for this entity type and bundle. |
| FileReferenceResolver::getReferences | public | function | Retrieves a list of references to a file. |
| FileReferenceResolver::getReferencingRevision | protected | function | Returns the most recent revision of a specific entity referencing the file. |
| FileReferenceResolver::isFileReferencedByField | protected | function | Returns whether the file is referenced by the given field. |
| FileReferenceResolver::loadEntityFromUsage | public | function | Loads an entity from the passed usage definition. |
| FileReferenceResolver::REVISION_LOOKUP_LIMIT | protected | constant | The number of revisions that are queried and returned per entity type. |
| FileReferenceResolver::__construct | public | function |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.