jsonapi_cross_bundles-8.x-1.0-alpha3/src/ResourceType/CrossBundleResourceTypeRepository.php
src/ResourceType/CrossBundleResourceTypeRepository.php
<?php
namespace Drupal\jsonapi_cross_bundles\ResourceType;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\jsonapi\ResourceType\ResourceType;
use Drupal\jsonapi\ResourceType\ResourceTypeRelationship;
use Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface;
use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException;
/**
* Provides a decorated resource type repository for cross bundle collections.
*/
class CrossBundleResourceTypeRepository implements ResourceTypeRepositoryInterface {
/**
* The wrapped service.
*
* @var \Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface
*/
private $inner;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The cache backend.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cache;
/**
* The shim.
*
* @var \Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface|\Drupal\jsonapi_cross_bundles\ResourceType\ResourceTypeRepositoryShim
*/
protected $shim;
/**
* CrossBundleResourceTypeRepository constructor.
*
* @param \Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface $inner
* The wrapped service.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
* The cache backend.
* @param \Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface|\Drupal\jsonapi_cross_bundles\ResourceType\ResourceTypeRepositoryShim $shim
* The shim.
*/
public function __construct(ResourceTypeRepositoryInterface $inner, EntityTypeManagerInterface $entity_type_manager, CacheBackendInterface $cache, ResourceTypeRepositoryInterface $shim) {
$this->inner = $inner;
$this->entityTypeManager = $entity_type_manager;
$this->cache = $cache;
$this->shim = $shim;
}
/**
* {@inheritdoc}
*/
public function getByTypeName($type_name) {
// Ensure this repository code has a cache to set the cache key.
$this->all();
return $this->inner->getByTypeName($type_name);
}
/**
* {@inheritdoc}
*/
public function get($entity_type_id, $bundle) {
// Ensure this repository code has a cache to set the cache key.
$this->all();
return $this->inner->get($entity_type_id, $bundle);
}
/**
* {@inheritdoc}
*/
public function all() {
$decorated_resource_types = [];
$cached = $this->cache->get('jsonapi.resource_types', FALSE);
if ($cached === FALSE) {
$initial_resource_types = $this->inner->all();
$grouped_by_entity_type_id = array_reduce($initial_resource_types, function ($grouped, ResourceType $resource_type) {
return array_merge_recursive($grouped, [
$resource_type->getEntityTypeId() => [$resource_type],
]);
}, []);
$cross_bundle_resource_types = [];
$entity_types = $this->entityTypeManager->getDefinitions();
foreach ($grouped_by_entity_type_id as $entity_type_id => $resource_types) {
$entity_type = $entity_types[$entity_type_id];
if (!$entity_type->getKey('bundle')) {
continue;
}
// If none of the bundle-specific resource types is locatable, the
// cross-bundle resource type should not be locatable.
$at_least_one_is_locatable = array_reduce($resource_types, function ($at_least_one_is_locatable, ResourceType $resource_type) {
return $at_least_one_is_locatable ?: $resource_type->isLocatable();
}, FALSE);
$field_mapping_superset = static::getFieldMappingSuperset(array_map(function (ResourceType $resource_type) use ($entity_type) {
return $this->shim->getFields(
$this->shim->getAllFieldNames($entity_type, $resource_type->getBundle()),
$entity_type,
$resource_type->getBundle()
);
}, $resource_types));
$cross_bundle_resource_type = new CrossBundlesResourceType(
$entity_type_id,
$entity_type_id,
$entity_type->getClass(),
$entity_type->isInternal(),
$at_least_one_is_locatable,
FALSE,
FALSE,
$field_mapping_superset
);
$cross_bundle_resource_type->setBundleResourceTypes($resource_types);
$cross_bundle_resource_type->setRelatableResourceTypes($this->getRelatableResourceTypesSuperset($resource_types, $field_mapping_superset, $initial_resource_types));
$cross_bundle_resource_types[$cross_bundle_resource_type->getTypeName()] = $cross_bundle_resource_type;
}
$decorated_resource_types = array_merge($initial_resource_types, $cross_bundle_resource_types);
$this->cache->set('jsonapi.resource_types', $decorated_resource_types, Cache::PERMANENT, ['jsonapi_resource_types']);
}
return $cached ? $cached->data : $decorated_resource_types;
}
/**
* Computes a superset of field mappings.
*
* @param \Drupal\jsonapi\ResourceType\ResourceTypeField[] $field_mappings
* An array of field mappings for each of the bundle-specific
* resource types that a cross-bundle resource type will aggregate.
*
* @return array
* The field mapping superset.
*
* @see \Drupal\jsonapi\ResourceType\ResourceTypeRepository::getFieldMapping()
*/
public static function getFieldMappingSuperset(array $field_mappings) {
/** @var \Drupal\jsonapi\ResourceType\ResourceTypeField[] $superset */
$superset = [];
foreach ($field_mappings as $field_mapping) {
// Merge the different field mappings into a single superset of all
// possible field mappings for a single entity type.
$undefined_field_names = [];
/** @var \Drupal\jsonapi\ResourceType\ResourceTypeField[] $superset */
$superset = array_reduce(array_keys($field_mapping), function ($superset, $internal_field_name) use ($field_mapping, &$undefined_field_names) {
if (in_array($internal_field_name, $undefined_field_names, TRUE)) {
return $superset;
}
// Check whether the field name already has a mapping.
if (isset($superset[$internal_field_name])) {
// Check to see if the field name is mapped or not. A boolean means
// the field is not mapped and is either enabled or disabled.
if (is_bool($superset[$internal_field_name])) {
// The previous mapping is just enabled/disabled. If the new mapping
// is a string, use it. If the it is enabled, use it. If it is
// disabled, use the previous field mapping in case it is enabled.
$superset[$internal_field_name] = $field_mapping[$internal_field_name] ?: $superset[$internal_field_name];
}
// @note we cannot do strict checking since these are objects.
elseif ($superset[$internal_field_name] != $field_mapping[$internal_field_name]) {
// There are must be more than one possible field mapping, so leave
// that behavior undefined.
$undefined_field_names[] = $internal_field_name;
unset($superset[$internal_field_name]);
}
}
else {
// This is a previous unseen mapping, so use the new mapping.
$resource_type_field = $field_mapping[$internal_field_name];
$superset[$internal_field_name] = $resource_type_field;
}
return $superset;
}, $superset);
}
return $superset;
}
/**
* Computes the superset of relatable resource types.
*
* @param array $resource_types
* An array of resource types to be aggregated.
* @param array $field_mapping
* The cross-bundle resource type field mapping.
* @param array $initial_resource_types
* The initial resource types.
*
* @return array
* A superset of relatable resource types, keyed by public field name.
*
* @see \Drupal\jsonapi\ResourceType\ResourceTypeRepository::getRelatableResourceTypesFromFieldDefinition()
*/
public function getRelatableResourceTypesSuperset(array $resource_types, array $field_mapping, array $initial_resource_types) {
$relatable_resource_types = [];
foreach ($resource_types as $resource_type) {
$relatable_resource_types[] = $this->shim->calculateRelatableResourceTypes($resource_type, $initial_resource_types);
}
$relatable_resource_types = array_merge([], ...$relatable_resource_types);
$mapped_resource_types = array_filter(array_map(function ($item) {
return $item instanceof ResourceTypeRelationship ? $item->getPublicName() : NULL;
}, $field_mapping));
return array_intersect_key($relatable_resource_types, array_flip($mapped_resource_types));
}
}
