View source
<?php
namespace Drupal\graphql\TypeResolver;
use Drupal\Core\Access\AccessibleInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;
use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
use Drupal\Core\TypedData\DataDefinitionInterface;
use Drupal\Core\TypedData\TypedDataManagerInterface;
use Drupal\graphql\NullType;
use Drupal\graphql\TypeResolverInterface;
use Drupal\graphql\Utility\StringHelper;
use Fubhy\GraphQL\Language\Node;
use Fubhy\GraphQL\Type\Definition\Types\EnumType;
use Fubhy\GraphQL\Type\Definition\Types\InterfaceType;
use Fubhy\GraphQL\Type\Definition\Types\ListModifier;
use Fubhy\GraphQL\Type\Definition\Types\NonNullModifier;
use Fubhy\GraphQL\Type\Definition\Types\ObjectType;
use Fubhy\GraphQL\Type\Definition\Types\Type;
class ContentEntityTypeResolver extends TypedDataTypeResolver {
protected $typedDataManager;
protected $entityManager;
protected $cachedTypes = [];
public function __construct(TypeResolverInterface $typeResolver, EntityManagerInterface $entityManager, TypedDataManagerInterface $typedDataManager) {
parent::__construct($typeResolver);
$this->entityManager = $entityManager;
$this->typedDataManager = $typedDataManager;
}
public function applies($type) {
if ($type instanceof EntityDataDefinitionInterface) {
$entityTypeId = $type
->getEntityTypeId();
$entityType = $this->entityManager
->getDefinition($entityTypeId);
return $entityType
->isSubclassOf('\\Drupal\\Core\\Entity\\ContentEntityInterface');
}
return FALSE;
}
public function resolveRecursive($type) {
$entityTypeId = $type
->getEntityTypeId();
$bundleInfo = $this->entityManager
->getBundleInfo($entityTypeId);
$bundleKeys = array_keys($bundleInfo);
$availableBundles = array_diff($bundleKeys, [
$entityTypeId,
]);
$constraintBundles = $type
->getBundles();
$constraintBundles = $constraintBundles ? array_diff($bundleKeys, $constraintBundles) : [];
$constraintBundles = array_intersect($constraintBundles, $availableBundles);
$constraintBundle = count($constraintBundles) === 1 ? reset($constraintBundles) : NULL;
$cacheKey = isset($constraintBundle) ? $constraintBundle : $entityTypeId;
if (array_key_exists($entityTypeId, $this->cachedTypes)) {
return $this->cachedTypes[$entityTypeId][$cacheKey];
}
return function () use ($entityTypeId, $cacheKey) {
if (array_key_exists($entityTypeId, $this->cachedTypes)) {
return $this->cachedTypes[$entityTypeId][$cacheKey];
}
$staticCache =& $this->cachedTypes[$entityTypeId];
$staticCache = [];
$fieldMap = $this
->getEntityTypeFieldMap($entityTypeId);
$baseFields = $fieldMap['base'];
$entityTypeName = StringHelper::formatTypeName("entity:{$entityTypeId}");
if (!empty($fieldMap['bundles'])) {
$objectTypeResolver = [
__CLASS__,
'getObjectTypeFromData',
];
$staticCache[$entityTypeId] = new InterfaceType($entityTypeName, $baseFields, $objectTypeResolver);
$typeInterfaces = [
$staticCache[$entityTypeId],
];
foreach ($fieldMap['bundles'] as $bundleKey => $bundleFields) {
$bundleName = StringHelper::formatTypeName("entity:{$entityTypeId}:{$bundleKey}");
$staticCache[$bundleKey] = new ObjectType($bundleName, $bundleFields + $baseFields, $typeInterfaces);
}
}
else {
$staticCache[$entityTypeId] = !empty($baseFields) ? new ObjectType($entityTypeName, $baseFields) : new NullType();
}
return $staticCache[$cacheKey];
};
}
protected function getEntityTypeFieldMap($entityTypeId) {
$baseDefinition = $this->typedDataManager
->createDataDefinition("entity:{$entityTypeId}");
$baseFields = $this
->resolveFields($baseDefinition);
$baseFieldNames = StringHelper::formatPropertyNameList(array_keys($baseFields));
$fieldMap['base'] = array_filter(array_combine($baseFieldNames, $baseFields));
$bundleInfo = $this->entityManager
->getBundleInfo($entityTypeId);
$bundleKeys = array_keys($bundleInfo);
$availableBundles = array_diff($bundleKeys, [
$entityTypeId,
]);
foreach ($availableBundles as $bundleKey) {
$bundleDefinition = $this->typedDataManager
->createDataDefinition("entity:{$entityTypeId}:{$bundleKey}");
$bundleFields = $this
->resolveFields($bundleDefinition);
$bundleFields = array_diff_key($bundleFields, $baseFields);
$bundleFieldNames = StringHelper::formatPropertyNameList(array_keys($bundleFields), $baseFieldNames);
$fieldMap['bundles'][$bundleKey] = array_filter(array_combine($bundleFieldNames, $bundleFields));
}
return $fieldMap;
}
protected function resolveFields(ComplexDataDefinitionInterface $type) {
$propertyDefinitions = $type
->getPropertyDefinitions();
$entityTypeId = $type
->getEntityTypeId();
$defaultFields = [];
if (empty($type
->getBundles())) {
$enumName = StringHelper::formatTypeName("entity:view:modes:{$entityTypeId}");
$enumValues = $this->entityManager
->getViewModes($entityTypeId);
$defaultFields['rendered:output'] = [
'type' => Type::stringType(),
'args' => !empty($enumValues) ? [
'viewMode' => [
'type' => new EnumType($enumName, $enumValues),
],
] : [],
'resolve' => [
__CLASS__,
'getRenderedOutput',
],
'resolveData' => [
'type' => $entityTypeId,
],
];
}
$typeFields = array_reduce(array_keys($propertyDefinitions), function ($previous, $propertyKey) use ($propertyDefinitions) {
$propertyDefinition = $propertyDefinitions[$propertyKey];
$sanitizedPropertyKey = $propertyKey;
if ($propertyDefinition instanceof FieldDefinitionInterface && strpos($propertyKey, 'field_') === 0) {
$sanitizedPropertyKey = substr($propertyKey, 6);
}
if ($resolvedProperty = $this
->resolveFieldFromProperty($propertyKey, $propertyDefinition)) {
return $previous + [
$sanitizedPropertyKey => $resolvedProperty,
];
}
return $previous;
}, $defaultFields);
return $typeFields;
}
protected function resolveFieldFromProperty($propertyKey, DataDefinitionInterface $propertyDefinition) {
if (!$propertyDefinition instanceof FieldDefinitionInterface) {
return parent::resolveFieldFromProperty($propertyKey, $propertyDefinition);
}
$storageDefinition = $propertyDefinition
->getFieldStorageDefinition();
$isRequired = $propertyDefinition
->isRequired();
$skipList = $storageDefinition
->getCardinality() === 1;
$itemDefinition = $propertyDefinition
->getItemDefinition();
$subProperties = $itemDefinition
->getPropertyDefinitions();
$mainPropertyName = $itemDefinition
->getMainPropertyName();
$mainProperty = $itemDefinition
->getPropertyDefinition($mainPropertyName);
$skipSubSelection = count($subProperties) === 1 && $mainPropertyName && $mainPropertyName === key($subProperties);
if (!$skipList && !$skipSubSelection) {
return parent::resolveFieldFromProperty($propertyKey, $propertyDefinition);
}
$propertyDefinition = $skipList ? $itemDefinition : $propertyDefinition;
$propertyDefinition = $skipSubSelection ? $mainProperty : $propertyDefinition;
$finalResolver = $this
->getPropertyResolverFunction($propertyDefinition);
if (!($propertyType = $this->typeResolver
->resolveRecursive($propertyDefinition))) {
return NULL;
}
$propertyType = $skipList ? $propertyType : new ListModifier($propertyType);
$propertyType = $isRequired ? new NonNullModifier($propertyType) : $propertyType;
return [
'type' => $propertyType,
'resolve' => [
__CLASS__,
'getFieldValueSimplified',
],
'resolveData' => [
'skipList' => $skipList,
'skipSubSelection' => $skipSubSelection,
'property' => $propertyKey,
'subProperty' => $mainPropertyName,
'finalResolver' => $finalResolver,
],
];
}
public static function getObjectTypeFromData(EntityAdapter $data) {
if (!($entity = $data
->getValue())) {
return NULL;
}
$currentLanguage = \Drupal::service('language_manager')
->getCurrentLanguage();
$loadedSchema = \Drupal::service('graphql.schema_loader')
->loadSchema($currentLanguage);
$typeMap = $loadedSchema
->getTypeMap();
$entityTypeId = $entity
->getEntityType()
->id();
$bundleKey = $entity
->bundle();
$typeIdentifier = 'entity:' . ($bundleKey !== $entityTypeId ? "{$entityTypeId}:{$bundleKey}" : $entityTypeId);
$typeName = StringHelper::formatTypeName($typeIdentifier);
return isset($typeMap[$typeName]) ? $typeMap[$typeName] : NULL;
}
public static function getFieldValueSimplified(EntityAdapter $data, $a, $b, $c, $d, $e, $f, $config) {
$skipList = $config['skipList'];
$skipSubSelection = $config['skipSubSelection'];
$property = $config['property'];
$subProperty = $config['subProperty'];
$finalResolver = $config['finalResolver'];
$data = $data
->get($property);
if ($data instanceof AccessibleInterface && !$data
->access('view')) {
return NULL;
}
$data = $skipList ? [
$data
->get(0),
] : iterator_to_array($data);
$args = [
$a,
$b,
$c,
$d,
$e,
$f,
[
'property' => $subProperty,
],
];
$data = $skipSubSelection ? array_map(function ($item) use ($finalResolver, $args) {
return call_user_func_array($finalResolver, array_merge([
$item,
], $args));
}, $data) : $data;
return $skipList ? reset($data) : $data;
}
public static function getRenderedOutput(EntityAdapter $data, $args, $b, $c, $d, $e, $f, $config) {
if ($data instanceof AccessibleInterface && !$data
->access('view')) {
return NULL;
}
$viewBuilder = \Drupal::entityManager()
->getViewBuilder($config['type']);
$renderer = \Drupal::service('renderer');
$output = $viewBuilder
->view($data
->getValue(), $args['viewMode'] ?: NULL);
return $renderer
->render($output);
}
}