From b5f0b325eb03ea25f899984fbdebd62c659ba903 Mon Sep 17 00:00:00 2001 From: Peter Philipp Date: Fri, 12 Jul 2024 09:21:07 +0200 Subject: [PATCH 01/23] chore: Improve mock data handling --- .../FieldHelper/DataObjectFieldHelper.php | 74 +++++++++++++------ 1 file changed, 51 insertions(+), 23 deletions(-) diff --git a/src/GraphQL/FieldHelper/DataObjectFieldHelper.php b/src/GraphQL/FieldHelper/DataObjectFieldHelper.php index e8c5efb52..45bb87395 100644 --- a/src/GraphQL/FieldHelper/DataObjectFieldHelper.php +++ b/src/GraphQL/FieldHelper/DataObjectFieldHelper.php @@ -28,9 +28,18 @@ use Pimcore\Model\DataObject\Fieldcollection\Data\AbstractData; use Pimcore\Model\DataObject\Localizedfield; use Pimcore\Model\DataObject\Objectbrick\Definition; +use Pimcore\Model\Factory; class DataObjectFieldHelper extends AbstractFieldHelper { + protected Factory $modelFactory; + + public function __construct(\Pimcore\Model\Factory $modelFactory) + { + parent::__construct(); + $this->modelFactory = $modelFactory; + } + /** * @param array $nodeDef * @param ClassDefinition|\Pimcore\Model\DataObject\Fieldcollection\Definition $class @@ -70,8 +79,8 @@ public function getQueryFieldConfigFromConfig($nodeDef, $class, $container = nul 'key' => $key, 'config' => [ 'name' => $key, - 'type' => Type::int() - ] + 'type' => Type::int(), + ], ]; case 'filename': case 'fullpath': @@ -80,8 +89,8 @@ public function getQueryFieldConfigFromConfig($nodeDef, $class, $container = nul 'key' => $key, 'config' => [ 'name' => $key, - 'type' => Type::string() - ] + 'type' => Type::string(), + ], ]; case 'published': return [ @@ -89,7 +98,7 @@ public function getQueryFieldConfigFromConfig($nodeDef, $class, $container = nul 'config' => [ 'name' => $key, 'type' => Type::boolean(), - ] + ], ]; default: return null; @@ -269,7 +278,7 @@ public function getMutationFieldConfigFromConfig($nodeDef, $class) 'arg' => ['type' => Type::string()], 'processor' => function ($object, $newValue, $args) { $object->setKey($newValue); - } + }, ]; case 'published': @@ -278,7 +287,7 @@ public function getMutationFieldConfigFromConfig($nodeDef, $class) 'arg' => ['type' => Type::boolean()], 'processor' => function ($object, $newValue, $args) { $object->setPublished($newValue); - } + }, ]; default: return null; @@ -370,10 +379,39 @@ public function doExtractData(FieldNode $ast, &$data, $container, $args, $contex $isLocalizedField = false; $containerDefinition = null; - if ($container instanceof Concrete) { - $containerDefinition = $container->getClass(); - } elseif ($container instanceof AbstractData || $container instanceof \Pimcore\Model\DataObject\Objectbrick\Data\AbstractData) { - $containerDefinition = $container->getDefinition(); + // This reflection object is used to determine if the getter can be used. + // $container isn't used directly in order to allow specialized handling + // of mock objects and other placeholders which act transparently but + // don't implement the getters themselves. + $methodCheckClass = new \ReflectionClass($container); + $skipMethodCallCheck = false; + // Adjust meta data for data handling on type of the data container. + switch (true) { + case $container instanceof Concrete: + $containerDefinition = $container->getClass(); + break; + + case $container instanceof AbstractData: + case $container instanceof \Pimcore\Model\DataObject\Objectbrick\Data\AbstractData: + $containerDefinition = $container->getDefinition(); + break; + + // All default indexers implement o_classId - access it directly to + // load class definition and with it use the model loader to fetch + // the actual implementing class for further reflection. + case $container instanceof DefaultMockup: + if (($mockClassId = $container->getParam('o_classId'))) { + $containerDefinition = ClassDefinition::getById($mockClassId); + // Unfortunately there's no API for this so we re-implement + // what \Pimcore\Model\DataObject\AbstractObject::getById() + // does. + $baseClassName = 'Pimcore\\Model\\DataObject\\' . ucfirst($containerDefinition->getName()); + $className = $this->modelFactory->getClassNameFor($baseClassName); + $methodCheckClass = new \ReflectionClass($className); + } else { + $skipMethodCallCheck = true; + } + break; } if ($containerDefinition) { @@ -383,7 +421,8 @@ public function doExtractData(FieldNode $ast, &$data, $container, $args, $contex $isLocalizedField = true; } } - if (method_exists($container, $getter)) { + + if (($methodCheckClass->hasMethod($getter) && $methodCheckClass->getMethod($getter)->isPublic()) || $skipMethodCallCheck) { if ($isLocalizedField) { // defer it $data[$astName] = function ($source, $args, $context, ResolveInfo $info) use ( @@ -395,17 +434,6 @@ public function doExtractData(FieldNode $ast, &$data, $container, $args, $contex } else { $data[$astName] = $container->$getter(); } - } else { - // we could also have a Mockup objects from Elastic which not supports the "method_exists" - // in this case we just try to get the data directly - if ($container instanceof DefaultMockup) { - try { - // we don't have to take care about localization because this is already handled in elastic - $data[$astName] = $container->$getter(); - } catch (\Exception $e) { - Logger::info('Could not get data from Datahub/DataObjectFieldHelper with message: ' . $e->getMessage()); - } - } } } From 10c4abd620f6ad61eeac9e9d93b42eb1ac347e10 Mon Sep 17 00:00:00 2001 From: Peter Philipp Date: Fri, 19 Jul 2024 11:44:37 +0200 Subject: [PATCH 02/23] chore: Forward element instance to the element resolver to allow further usage of mock objects avoiding db loads. --- src/GraphQL/FieldHelper/AbstractFieldHelper.php | 6 +++++- src/GraphQL/Resolver/Element.php | 11 ++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/GraphQL/FieldHelper/AbstractFieldHelper.php b/src/GraphQL/FieldHelper/AbstractFieldHelper.php index a342cee49..59c56b98c 100644 --- a/src/GraphQL/FieldHelper/AbstractFieldHelper.php +++ b/src/GraphQL/FieldHelper/AbstractFieldHelper.php @@ -23,6 +23,7 @@ use GraphQL\Language\AST\SelectionSetNode; use GraphQL\Type\Definition\ResolveInfo; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ServiceTrait; +use Pimcore\Bundle\EcommerceFrameworkBundle\Model\DefaultMockup; use Pimcore\Model\Element\ElementInterface; abstract class AbstractFieldHelper @@ -118,10 +119,13 @@ public function getArguments(FieldNode $ast) */ public function extractData(&$data, $container, $args, $context = [], ResolveInfo $resolveInfo = null) { - if ($container instanceof ElementInterface) { + if ($container instanceof ElementInterface || $container instanceof DefaultMockup) { // we have to at least add the ID and pass it around even if not requested because we need it internally // to resolve fields of linked elements (such as asset image and so on) $data['id'] = $container->getId(); + // Register the element for downstream resolvers to avoid object + // loads. + $data[ElementInterface::class] = $container; } $resolveInfoArray = (array)$resolveInfo; diff --git a/src/GraphQL/Resolver/Element.php b/src/GraphQL/Resolver/Element.php index e8335a08b..4be4b9c37 100644 --- a/src/GraphQL/Resolver/Element.php +++ b/src/GraphQL/Resolver/Element.php @@ -57,7 +57,7 @@ public function __construct(string $elementType, Service $graphQlService) */ public function resolveTag($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { - $element = ElementService::getElementById($this->elementType, $value['id']); + $element = $value[ElementInterface::class] ?? ElementService::getElementById($this->elementType, $value['id']); if ($element) { $result = $this->getTags('document', $element->getId()); @@ -82,7 +82,7 @@ public function resolveTag($value = null, $args = [], $context = [], ResolveInfo public function resolveProperties($value = null, array $args = [], array $context = [], ResolveInfo $resolveInfo = null) { $elementId = $value['id']; - $element = ElementService::getElementById($this->elementType, $elementId); + $element = $value[ElementInterface::class] ?? ElementService::getElementById($this->elementType, $elementId); if (!$element) { throw new ClientSafeException('element ' . $this->elementType . ' ' . $elementId . ' not found'); @@ -116,7 +116,7 @@ public function resolveProperties($value = null, array $args = [], array $contex */ public function resolveParent($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { - $element = ElementService::getElementById($this->elementType, $value['id']); + $element = $value[ElementInterface::class] ?? ElementService::getElementById($this->elementType, $value['id']); if ($element) { $parent = $element->getParent(); if ($parent) { @@ -139,7 +139,8 @@ public function resolveParent($value = null, $args = [], $context = [], ResolveI */ public function resolveChildren($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { - $element = ElementService::getElementById($this->elementType, $value['id']); + $element = $value[ElementInterface::class] ?? ElementService::getElementById($this->elementType, $value['id']); + if ($element) { $arguments = $this->composeArguments($args); @@ -161,7 +162,7 @@ public function resolveChildren($value = null, $args = [], $context = [], Resolv */ public function resolveSiblings($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { - $element = ElementService::getElementById($this->elementType, $value['id']); + $element = $value[ElementInterface::class] ?? ElementService::getElementById($this->elementType, $value['id']); if ($element) { $arguments = $this->composeArguments($args); From 945842d3e6c060ea9d156a2e9d08bd0485df694f Mon Sep 17 00:00:00 2001 From: Peter Philipp Date: Fri, 19 Jul 2024 14:14:11 +0200 Subject: [PATCH 03/23] chore: Generalize the element loading / sharing by introducing a trait. --- .../FieldHelper/AbstractFieldHelper.php | 8 +-- src/GraphQL/Resolver/AssetType.php | 6 +- src/GraphQL/Resolver/Base.php | 6 +- src/GraphQL/Resolver/DataObject.php | 6 +- src/GraphQL/Resolver/Element.php | 14 ++--- src/GraphQL/Traits/ElementLoaderTrait.php | 58 +++++++++++++++++++ 6 files changed, 77 insertions(+), 21 deletions(-) create mode 100644 src/GraphQL/Traits/ElementLoaderTrait.php diff --git a/src/GraphQL/FieldHelper/AbstractFieldHelper.php b/src/GraphQL/FieldHelper/AbstractFieldHelper.php index 59c56b98c..33812bf32 100644 --- a/src/GraphQL/FieldHelper/AbstractFieldHelper.php +++ b/src/GraphQL/FieldHelper/AbstractFieldHelper.php @@ -22,13 +22,14 @@ use GraphQL\Language\AST\NodeList; use GraphQL\Language\AST\SelectionSetNode; use GraphQL\Type\Definition\ResolveInfo; +use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ElementLoaderTrait; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ServiceTrait; use Pimcore\Bundle\EcommerceFrameworkBundle\Model\DefaultMockup; use Pimcore\Model\Element\ElementInterface; abstract class AbstractFieldHelper { - use ServiceTrait; + use ServiceTrait, ElementLoaderTrait; public function __construct() { @@ -122,10 +123,7 @@ public function extractData(&$data, $container, $args, $context = [], ResolveInf if ($container instanceof ElementInterface || $container instanceof DefaultMockup) { // we have to at least add the ID and pass it around even if not requested because we need it internally // to resolve fields of linked elements (such as asset image and so on) - $data['id'] = $container->getId(); - // Register the element for downstream resolvers to avoid object - // loads. - $data[ElementInterface::class] = $container; + $data = $this->setDataElement($data, $container); } $resolveInfoArray = (array)$resolveInfo; diff --git a/src/GraphQL/Resolver/AssetType.php b/src/GraphQL/Resolver/AssetType.php index 3502d9437..e49880d8e 100644 --- a/src/GraphQL/Resolver/AssetType.php +++ b/src/GraphQL/Resolver/AssetType.php @@ -20,6 +20,7 @@ use Pimcore\Bundle\DataHubBundle\GraphQL\BaseDescriptor; use Pimcore\Bundle\DataHubBundle\GraphQL\ElementDescriptor; use Pimcore\Bundle\DataHubBundle\GraphQL\Service; +use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ElementLoaderTrait; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ElementTagTrait; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ServiceTrait; use Pimcore\Bundle\DataHubBundle\WorkspaceHelper; @@ -27,7 +28,7 @@ class AssetType { - use ServiceTrait, ElementTagTrait; + use ServiceTrait, ElementTagTrait, ElementLoaderTrait; /** * @param ElementDescriptor|null $value @@ -410,8 +411,7 @@ protected function getAssetFromValue($value, $context) if (!$value instanceof ElementDescriptor) { return null; } - - $asset = Asset::getById($value['id']); + $asset = $this->loadDataElement($value, 'asset'); if (!WorkspaceHelper::checkPermission($asset, 'read')) { return null; diff --git a/src/GraphQL/Resolver/Base.php b/src/GraphQL/Resolver/Base.php index e68216c8f..af3215d10 100644 --- a/src/GraphQL/Resolver/Base.php +++ b/src/GraphQL/Resolver/Base.php @@ -19,13 +19,13 @@ use GraphQL\Executor\Promise\Adapter\SyncPromise; use GraphQL\Language\AST\FieldNode; use GraphQL\Type\Definition\ResolveInfo; +use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ElementLoaderTrait; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ServiceTrait; -use Pimcore\Model\DataObject\AbstractObject; use Pimcore\Model\DataObject\ClassDefinition; class Base { - use ServiceTrait; + use ServiceTrait, ElementLoaderTrait; /** @var string */ protected $typeName; @@ -87,7 +87,7 @@ public function resolve($value = null, $args = [], $context = [], ResolveInfo $r /** @var \Pimcore\Bundle\DataHubBundle\GraphQL\Query\Operator\AbstractOperator $operatorImpl */ $operatorImpl = $this->getGraphQlService()->buildQueryOperator($this->typeName, $this->attributes); - $element = AbstractObject::getById($value['id']); + $element = $this->loadDataElement($value, 'object'); $valueFromOperator = $operatorImpl->getLabeledValue($element, $resolveInfo); $value = $valueFromOperator->value ?? null; diff --git a/src/GraphQL/Resolver/DataObject.php b/src/GraphQL/Resolver/DataObject.php index d009a78f6..e3126c82d 100644 --- a/src/GraphQL/Resolver/DataObject.php +++ b/src/GraphQL/Resolver/DataObject.php @@ -42,7 +42,7 @@ public function __construct(Service $graphQlService) */ public function resolveTag($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { - $object = \Pimcore\Model\DataObject::getById($value['id']); + $object = $this->loadDataElement($value, 'object'); if ($object) { $result = $this->getTags('object', $object->getId()); @@ -68,7 +68,7 @@ public function resolveIndex($value = null, $args = [], $context = [], ResolveIn return null; } - $object = \Pimcore\Model\DataObject::getById($value['id']); + $object = $this->loadDataElement($value, 'object'); if (!$object instanceof AbstractObject) { return null; @@ -91,7 +91,7 @@ public function resolveChildrenSortBy($value = null, $args = [], $context = [], return null; } - $object = \Pimcore\Model\DataObject::getById($value['id']); + $object = $this->loadDataElement($value, 'object'); if (!$object instanceof \Pimcore\Model\DataObject) { return null; diff --git a/src/GraphQL/Resolver/Element.php b/src/GraphQL/Resolver/Element.php index 4be4b9c37..a843cb7b3 100644 --- a/src/GraphQL/Resolver/Element.php +++ b/src/GraphQL/Resolver/Element.php @@ -21,6 +21,7 @@ use Pimcore\Bundle\DataHubBundle\GraphQL\Exception\ClientSafeException; use Pimcore\Bundle\DataHubBundle\GraphQL\FieldHelper\AbstractFieldHelper; use Pimcore\Bundle\DataHubBundle\GraphQL\Service; +use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ElementLoaderTrait; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ElementTagTrait; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ServiceTrait; use Pimcore\Bundle\DataHubBundle\WorkspaceHelper; @@ -29,12 +30,11 @@ use Pimcore\Model\DataObject\AbstractObject; use Pimcore\Model\Document; use Pimcore\Model\Element\ElementInterface; -use Pimcore\Model\Element\Service as ElementService; use Pimcore\Model\Property; class Element { - use ServiceTrait, ElementTagTrait; + use ServiceTrait, ElementTagTrait, ElementLoaderTrait; /** @var string */ protected $elementType; @@ -57,7 +57,7 @@ public function __construct(string $elementType, Service $graphQlService) */ public function resolveTag($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { - $element = $value[ElementInterface::class] ?? ElementService::getElementById($this->elementType, $value['id']); + $element = $this->loadDataElement($value, $this->elementType); if ($element) { $result = $this->getTags('document', $element->getId()); @@ -82,7 +82,7 @@ public function resolveTag($value = null, $args = [], $context = [], ResolveInfo public function resolveProperties($value = null, array $args = [], array $context = [], ResolveInfo $resolveInfo = null) { $elementId = $value['id']; - $element = $value[ElementInterface::class] ?? ElementService::getElementById($this->elementType, $elementId); + $element = $this->loadDataElement($value, $this->elementType); if (!$element) { throw new ClientSafeException('element ' . $this->elementType . ' ' . $elementId . ' not found'); @@ -116,7 +116,7 @@ public function resolveProperties($value = null, array $args = [], array $contex */ public function resolveParent($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { - $element = $value[ElementInterface::class] ?? ElementService::getElementById($this->elementType, $value['id']); + $element = $this->loadDataElement($value, $this->elementType); if ($element) { $parent = $element->getParent(); if ($parent) { @@ -139,7 +139,7 @@ public function resolveParent($value = null, $args = [], $context = [], ResolveI */ public function resolveChildren($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { - $element = $value[ElementInterface::class] ?? ElementService::getElementById($this->elementType, $value['id']); + $element = $this->loadDataElement($value, $this->elementType); if ($element) { $arguments = $this->composeArguments($args); @@ -162,7 +162,7 @@ public function resolveChildren($value = null, $args = [], $context = [], Resolv */ public function resolveSiblings($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { - $element = $value[ElementInterface::class] ?? ElementService::getElementById($this->elementType, $value['id']); + $element = $this->loadDataElement($value, $this->elementType); if ($element) { $arguments = $this->composeArguments($args); diff --git a/src/GraphQL/Traits/ElementLoaderTrait.php b/src/GraphQL/Traits/ElementLoaderTrait.php new file mode 100644 index 000000000..e57694367 --- /dev/null +++ b/src/GraphQL/Traits/ElementLoaderTrait.php @@ -0,0 +1,58 @@ +getId(); + $data[ElementInterface::class . '_type'] = $element->getType(); + $data[ElementInterface::class . '_instance'] = $element; + + return $data; + } + + /** + * + * @return ElementInterface + */ + protected function loadDataElement(&$data, $type) + { + if (!isset($data[ElementInterface::class . '_instance'])) { + $data[ElementInterface::class . '_type'] = $type; + $data[ElementInterface::class . '_instance'] = ElementService::getElementById($data['id'], $type); + } + + return $data[ElementInterface::class . '_instance']; + } +} From 0ef50769cc7154a70ba93e9731501626953fbb7f Mon Sep 17 00:00:00 2001 From: Peter Philipp Date: Tue, 13 Aug 2024 17:28:04 +0200 Subject: [PATCH 04/23] chore: Add support for mockup objects in relations. --- .../Helper/Objects.php | 12 ++++++++++-- src/GraphQL/ElementDescriptor.php | 9 +++++++-- src/GraphQL/Service.php | 9 +++++++-- src/GraphQL/Traits/ElementLoaderTrait.php | 9 +++++++++ 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Objects.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Objects.php index f59a89309..92e6a7502 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Objects.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Objects.php @@ -23,6 +23,7 @@ use Pimcore\Bundle\DataHubBundle\GraphQL\Service; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ServiceTrait; use Pimcore\Bundle\DataHubBundle\WorkspaceHelper; +use Pimcore\Bundle\EcommerceFrameworkBundle\Model\DefaultMockup; use Pimcore\Model\DataObject\ClassDefinition; use Pimcore\Model\DataObject\ClassDefinition\Data; use Pimcore\Model\Element\AbstractElement; @@ -76,9 +77,16 @@ public function resolve($value = null, $args = [], $context = [], ResolveInfo $r $relations = Service::resolveValue($value, $this->fieldDefinition, $this->attribute, $args); if ($relations) { $result = []; - /** @var $relation AbstractElement */ foreach ($relations as $relation) { - if (!WorkspaceHelper::checkPermission($relation, 'read')) { + /** @var $relation AbstractElement */ + // Explicitly set the type of the mockup object because + // these don't have the matching class instance for + // auto-detect. + $type = null; + if ($relation instanceof DefaultMockup) { + $type = $relation->getType(); + } + if (!WorkspaceHelper::checkPermission($relation, 'read', $type)) { continue; } diff --git a/src/GraphQL/ElementDescriptor.php b/src/GraphQL/ElementDescriptor.php index 4e95f678b..9259a0138 100644 --- a/src/GraphQL/ElementDescriptor.php +++ b/src/GraphQL/ElementDescriptor.php @@ -15,6 +15,7 @@ namespace Pimcore\Bundle\DataHubBundle\GraphQL; +use Pimcore\Bundle\EcommerceFrameworkBundle\Model\DefaultMockup; use Pimcore\Model\Asset; use Pimcore\Model\DataObject\Concrete; use Pimcore\Model\Document; @@ -25,7 +26,7 @@ class ElementDescriptor extends BaseDescriptor /** * @param ElementInterface|null $element */ - public function __construct(ElementInterface $element = null) + public function __construct(ElementInterface|DefaultMockup $element = null) { parent::__construct(); if ($element) { @@ -33,7 +34,11 @@ public function __construct(ElementInterface $element = null) $this->offsetSet('__elementType', \Pimcore\Model\Element\Service::getElementType($element)); $this->offsetSet('__elementSubtype', $element instanceof Concrete ? $element->getClass()->getName() : $element->getType()); - if ($element instanceof Concrete) { + if ($element instanceof DefaultMockup) { + $subtype = $element->getClass()->getName(); + $this->offsetSet('__elementType', 'object'); + $this->offsetSet('__elementSubtype', $subtype); + } elseif ($element instanceof Concrete) { $subtype = $element->getClass()->getName(); $this->offsetSet('__elementType', 'object'); diff --git a/src/GraphQL/Service.php b/src/GraphQL/Service.php index 7978370d5..3bd6fad59 100644 --- a/src/GraphQL/Service.php +++ b/src/GraphQL/Service.php @@ -38,7 +38,9 @@ use Pimcore\Bundle\DataHubBundle\GraphQL\FieldHelper\DocumentFieldHelper; use Pimcore\Bundle\DataHubBundle\GraphQL\Query\Operator\Factory\OperatorFactoryInterface; use Pimcore\Bundle\DataHubBundle\GraphQL\Query\Value\DefaultValue; +use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ElementLoaderTrait; use Pimcore\Bundle\DataHubBundle\PimcoreDataHubBundle; +use Pimcore\Bundle\EcommerceFrameworkBundle\Model\DefaultMockup; use Pimcore\Cache\RuntimeCache; use Pimcore\DataObject\GridColumnConfig\ConfigElementInterface; use Pimcore\Localization\LocaleServiceInterface; @@ -57,6 +59,8 @@ class Service { + use ElementLoaderTrait; + /*** * @var ContainerInterface */ @@ -944,8 +948,7 @@ public static function setValue($object, $attribute, $callback) public static function resolveValue(BaseDescriptor $descriptor, Data $fieldDefinition, $attribute, $args = []) { $getter = 'get' . ucfirst($fieldDefinition->getName()); - $objectId = $descriptor['id']; - $object = Concrete::getById($objectId); + $object = self::staticLoadDataElement($descriptor, 'object'); if (!$object) { return null; } @@ -1183,6 +1186,8 @@ public function extractData($data, $target, $args = [], $context = [], ResolveIn $fieldHelper = $this->getAssetFieldHelper(); } elseif ($target instanceof AbstractObject) { $fieldHelper = $this->getObjectFieldHelper(); + } elseif ($target instanceof DefaultMockup) { + $fieldHelper = $this->getObjectFieldHelper(); } if ($fieldHelper) { diff --git a/src/GraphQL/Traits/ElementLoaderTrait.php b/src/GraphQL/Traits/ElementLoaderTrait.php index e57694367..bbef7a414 100644 --- a/src/GraphQL/Traits/ElementLoaderTrait.php +++ b/src/GraphQL/Traits/ElementLoaderTrait.php @@ -47,6 +47,15 @@ protected function setDataElement($data, ElementInterface | DefaultMockup $eleme * @return ElementInterface */ protected function loadDataElement(&$data, $type) + { + return self::staticLoadDataElement($data, $type); + } + + /** + * + * @return ElementInterface + */ + protected static function staticLoadDataElement(&$data, $type) { if (!isset($data[ElementInterface::class . '_instance'])) { $data[ElementInterface::class . '_type'] = $type; From 6e5d57e35d40d7dc45f09eec85a6b2037db70a29 Mon Sep 17 00:00:00 2001 From: das-peter Date: Tue, 13 Aug 2024 15:28:29 +0000 Subject: [PATCH 05/23] Apply php-cs-fixer changes --- src/GraphQL/ElementDescriptor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphQL/ElementDescriptor.php b/src/GraphQL/ElementDescriptor.php index 9259a0138..08ac4b8ba 100644 --- a/src/GraphQL/ElementDescriptor.php +++ b/src/GraphQL/ElementDescriptor.php @@ -26,7 +26,7 @@ class ElementDescriptor extends BaseDescriptor /** * @param ElementInterface|null $element */ - public function __construct(ElementInterface|DefaultMockup $element = null) + public function __construct(ElementInterface | DefaultMockup $element = null) { parent::__construct(); if ($element) { From f1682e6a0395eddd163fdb54f87645945cafa29d Mon Sep 17 00:00:00 2001 From: Peter Philipp Date: Wed, 14 Aug 2024 17:40:51 +0200 Subject: [PATCH 06/23] fix: Type for checkPermission() has to be string. --- .../DataObjectQueryFieldConfigGenerator/Helper/Objects.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Objects.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Objects.php index 92e6a7502..fd3415b6f 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Objects.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Objects.php @@ -82,7 +82,7 @@ public function resolve($value = null, $args = [], $context = [], ResolveInfo $r // Explicitly set the type of the mockup object because // these don't have the matching class instance for // auto-detect. - $type = null; + $type = ''; if ($relation instanceof DefaultMockup) { $type = $relation->getType(); } From 1b3ca7d414b799b8bd7bea46a6e57f050710cd7e Mon Sep 17 00:00:00 2001 From: Peter Philipp Date: Wed, 14 Aug 2024 18:24:06 +0200 Subject: [PATCH 07/23] fix: Fix call signature in ElementLoaderTrait.php --- src/GraphQL/Traits/ElementLoaderTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphQL/Traits/ElementLoaderTrait.php b/src/GraphQL/Traits/ElementLoaderTrait.php index bbef7a414..4516dec67 100644 --- a/src/GraphQL/Traits/ElementLoaderTrait.php +++ b/src/GraphQL/Traits/ElementLoaderTrait.php @@ -59,7 +59,7 @@ protected static function staticLoadDataElement(&$data, $type) { if (!isset($data[ElementInterface::class . '_instance'])) { $data[ElementInterface::class . '_type'] = $type; - $data[ElementInterface::class . '_instance'] = ElementService::getElementById($data['id'], $type); + $data[ElementInterface::class . '_instance'] = ElementService::getElementById($type, $data['id']); } return $data[ElementInterface::class . '_instance']; From 0848190007ddf418efedb201b0237b72f481287d Mon Sep 17 00:00:00 2001 From: Peter Philipp Date: Thu, 15 Aug 2024 11:26:05 +0200 Subject: [PATCH 08/23] chore: More adjustments to work with DefaultMockup. --- .../Helper/Href.php | 6 +- .../Helper/Multihref.php | 4 +- .../Helper/MultihrefMetadata.php | 4 +- .../Helper/Objects.php | 5 +- .../Helper/ObjectsMetadata.php | 4 +- .../FieldHelper/DataObjectFieldHelper.php | 51 +--------- src/GraphQL/RelationHelper.php | 5 +- src/GraphQL/Service.php | 99 +++++++++++++++++-- 8 files changed, 108 insertions(+), 70 deletions(-) diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Href.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Href.php index e04471514..a74d3153d 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Href.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Href.php @@ -21,6 +21,7 @@ use Pimcore\Bundle\DataHubBundle\GraphQL\Service; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ServiceTrait; use Pimcore\Bundle\DataHubBundle\WorkspaceHelper; +use Pimcore\Bundle\EcommerceFrameworkBundle\Model\DefaultMockup; use Pimcore\Model\DataObject\ClassDefinition; use Pimcore\Model\DataObject\ClassDefinition\Data; use Pimcore\Model\Element\ElementInterface; @@ -74,8 +75,9 @@ public function resolve($value = null, $args = [], $context = [], ResolveInfo $r if ($value instanceof BaseDescriptor) { $relation = \Pimcore\Bundle\DataHubBundle\GraphQL\Service::resolveValue($value, $this->fieldDefinition, $this->attribute, $args); - if ($relation instanceof ElementInterface) { - if (!WorkspaceHelper::checkPermission($relation, 'read')) { + if ($relation instanceof ElementInterface || $relation instanceof DefaultMockup) { + $type = ($relation instanceof DefaultMockup) ? 'object' : ''; + if (!WorkspaceHelper::checkPermission($relation, 'read', $type)) { return null; } diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Multihref.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Multihref.php index 32aec4123..9abb9064b 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Multihref.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Multihref.php @@ -21,6 +21,7 @@ use Pimcore\Bundle\DataHubBundle\GraphQL\Service; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ServiceTrait; use Pimcore\Bundle\DataHubBundle\WorkspaceHelper; +use Pimcore\Bundle\EcommerceFrameworkBundle\Model\DefaultMockup; use Pimcore\Model\DataObject\ClassDefinition; use Pimcore\Model\Element\AbstractElement; @@ -74,7 +75,8 @@ public function resolve($value = null, $args = [], $context = [], ResolveInfo $r if ($relations) { /** @var AbstractElement $relation */ foreach ($relations as $relation) { - if (!WorkspaceHelper::checkPermission($relation, 'read')) { + $type = ($relation instanceof DefaultMockup) ? 'object' : ''; + if (!WorkspaceHelper::checkPermission($relation, 'read', $type)) { continue; } diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/MultihrefMetadata.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/MultihrefMetadata.php index 590a0a54e..b74d1aed8 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/MultihrefMetadata.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/MultihrefMetadata.php @@ -21,6 +21,7 @@ use Pimcore\Bundle\DataHubBundle\GraphQL\Service; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ServiceTrait; use Pimcore\Bundle\DataHubBundle\WorkspaceHelper; +use Pimcore\Bundle\EcommerceFrameworkBundle\Model\DefaultMockup; use Pimcore\Model\DataObject\ClassDefinition; use Pimcore\Model\DataObject\ClassDefinition\Data; use Pimcore\Model\DataObject\Data\ElementMetadata; @@ -76,7 +77,8 @@ public function resolve($value = null, $args = [], $context = [], ResolveInfo $r /** @var ElementMetadata $relation */ foreach ($relations as $relation) { $element = $relation->getElement(); - if (!WorkspaceHelper::checkPermission($element, 'read')) { + $type = ($element instanceof DefaultMockup) ? 'object' : ''; + if (!WorkspaceHelper::checkPermission($element, 'read', $type)) { continue; } diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Objects.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Objects.php index fd3415b6f..be4ec3d7a 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Objects.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Objects.php @@ -82,10 +82,7 @@ public function resolve($value = null, $args = [], $context = [], ResolveInfo $r // Explicitly set the type of the mockup object because // these don't have the matching class instance for // auto-detect. - $type = ''; - if ($relation instanceof DefaultMockup) { - $type = $relation->getType(); - } + $type = ($relation instanceof DefaultMockup) ? 'object' : ''; if (!WorkspaceHelper::checkPermission($relation, 'read', $type)) { continue; } diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/ObjectsMetadata.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/ObjectsMetadata.php index 1ac5b36b2..fd2e5118c 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/ObjectsMetadata.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/ObjectsMetadata.php @@ -21,6 +21,7 @@ use Pimcore\Bundle\DataHubBundle\GraphQL\Service; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ServiceTrait; use Pimcore\Bundle\DataHubBundle\WorkspaceHelper; +use Pimcore\Bundle\EcommerceFrameworkBundle\Model\DefaultMockup; use Pimcore\Model\DataObject\ClassDefinition; use Pimcore\Model\DataObject\Data\ObjectMetadata; @@ -75,7 +76,8 @@ public function resolve($value = null, $args = [], $context = [], ResolveInfo $r /** @var ObjectMetadata $relation */ foreach ($relations as $relation) { $element = $relation->getElement(); - if (!WorkspaceHelper::checkPermission($element, 'read')) { + $type = ($element instanceof DefaultMockup) ? 'object' : ''; + if (!WorkspaceHelper::checkPermission($element, 'read', $type)) { continue; } diff --git a/src/GraphQL/FieldHelper/DataObjectFieldHelper.php b/src/GraphQL/FieldHelper/DataObjectFieldHelper.php index 45bb87395..0b34691d7 100644 --- a/src/GraphQL/FieldHelper/DataObjectFieldHelper.php +++ b/src/GraphQL/FieldHelper/DataObjectFieldHelper.php @@ -370,59 +370,12 @@ public function doExtractData(FieldNode $ast, &$data, $container, $args, $contex if ($this->skipField($container, $astName)) { return; } - // example for http://webonyx.github.io/graphql-php/error-handling/ // throw new MySafeException("fieldhelper", "TBD customized error message"); $getter = 'get' . ucfirst($astName); - - $isLocalizedField = false; - $containerDefinition = null; - - // This reflection object is used to determine if the getter can be used. - // $container isn't used directly in order to allow specialized handling - // of mock objects and other placeholders which act transparently but - // don't implement the getters themselves. - $methodCheckClass = new \ReflectionClass($container); - $skipMethodCallCheck = false; - // Adjust meta data for data handling on type of the data container. - switch (true) { - case $container instanceof Concrete: - $containerDefinition = $container->getClass(); - break; - - case $container instanceof AbstractData: - case $container instanceof \Pimcore\Model\DataObject\Objectbrick\Data\AbstractData: - $containerDefinition = $container->getDefinition(); - break; - - // All default indexers implement o_classId - access it directly to - // load class definition and with it use the model loader to fetch - // the actual implementing class for further reflection. - case $container instanceof DefaultMockup: - if (($mockClassId = $container->getParam('o_classId'))) { - $containerDefinition = ClassDefinition::getById($mockClassId); - // Unfortunately there's no API for this so we re-implement - // what \Pimcore\Model\DataObject\AbstractObject::getById() - // does. - $baseClassName = 'Pimcore\\Model\\DataObject\\' . ucfirst($containerDefinition->getName()); - $className = $this->modelFactory->getClassNameFor($baseClassName); - $methodCheckClass = new \ReflectionClass($className); - } else { - $skipMethodCallCheck = true; - } - break; - } - - if ($containerDefinition) { - /** @var Data\Localizedfields|null $lfDefs */ - $lfDefs = $containerDefinition->getFieldDefinition('localizedfields'); - if ($lfDefs && $lfDefs->getFieldDefinition($astName)) { - $isLocalizedField = true; - } - } - - if (($methodCheckClass->hasMethod($getter) && $methodCheckClass->getMethod($getter)->isPublic()) || $skipMethodCallCheck) { + if ($this->getGraphQlService()::checkContainerMethodExists($container, $getter)) { + $isLocalizedField = $this->getGraphQlService()::isLocalizedField($container, $astName); if ($isLocalizedField) { // defer it $data[$astName] = function ($source, $args, $context, ResolveInfo $info) use ( diff --git a/src/GraphQL/RelationHelper.php b/src/GraphQL/RelationHelper.php index 9c724ad90..8ba9f8986 100644 --- a/src/GraphQL/RelationHelper.php +++ b/src/GraphQL/RelationHelper.php @@ -16,12 +16,13 @@ namespace Pimcore\Bundle\DataHubBundle\GraphQL; use GraphQL\Type\Definition\ResolveInfo; +use Pimcore\Bundle\EcommerceFrameworkBundle\Model\DefaultMockup; use Pimcore\Model\Element\ElementInterface; class RelationHelper { /** - * @param ElementInterface $relation + * @param ElementInterface|DefaultMockup $relation * @param Service $graphQlService * @param array $args * @param array $context @@ -29,7 +30,7 @@ class RelationHelper * * @return ElementDescriptor */ - public static function processRelation(ElementInterface $relation, Service $graphQlService, $args, $context, ResolveInfo $resolveInfo) + public static function processRelation(ElementInterface|DefaultMockup $relation, Service $graphQlService, $args, $context, ResolveInfo $resolveInfo) { $data = new ElementDescriptor($relation); $graphQlService->extractData($data, $relation, $args, $context, $resolveInfo); diff --git a/src/GraphQL/Service.php b/src/GraphQL/Service.php index 3bd6fad59..ef2ca7935 100644 --- a/src/GraphQL/Service.php +++ b/src/GraphQL/Service.php @@ -930,7 +930,7 @@ public static function setValue($object, $attribute, $callback) return $result; } - } elseif (method_exists($container, $setter)) { + } elseif (static::checkContainerMethodExists($container, $setter)) { $result = $callback($container, $setter); } @@ -1077,7 +1077,7 @@ public static function resolveValue(BaseDescriptor $descriptor, Data $fieldDefin return $value; } - } elseif (method_exists($container, $getter)) { + } elseif (static::checkContainerMethodExists($container, $getter)) { $isLocalizedField = self::isLocalizedField($container, $fieldDefinition->getName()); if ($isLocalizedField) { $result = $container->$getter($args['language'] ?? null); @@ -1124,15 +1124,9 @@ public static function resolveCachedValue($value, $resolveInfo = null) * * @return bool */ - private static function isLocalizedField($container, $fieldName): bool + public static function isLocalizedField($container, $fieldName): bool { - $containerDefinition = null; - - if ($container instanceof Concrete) { - $containerDefinition = $container->getClass(); - } elseif ($container instanceof AbstractData) { - $containerDefinition = $container->getDefinition(); - } + $containerDefinition = static::getContainerClassDefinition($container); if ($containerDefinition) { /** @var Data\Localizedfields|null $lfDefs */ @@ -1214,4 +1208,89 @@ public function querySchemaEnabled(string $type) return $enabled; } + + /** + * Checks if a container has a given method. + * + * This works with DefaultMockup objects too that's why it's so overly + * complex. DefaultMockup objects will use the class definition of the + * mocked object to determine which model class has to be checked for the + * method. + * + * @param object $container + * @param string $method + * + * @return bool + * @throws \ReflectionException + */ + public static function checkContainerMethodExists(object $container, string $method): bool + { + // This reflection object is used to determine if the getter can be used. + // $container isn't used directly in order to allow specialized handling + // of mock objects and other placeholders which act transparently but + // don't implement the getters themselves. + $methodCheckClass = new \ReflectionClass($container); + $skipMethodCallCheck = false; + + // Adjust meta data for data handling on type of the data container. + $containerDefinition = static::getContainerClassDefinition($container); + if ($container instanceof DefaultMockup) { + if ($containerDefinition) { + // Unfortunately there's no API for this so we re-implement + // what \Pimcore\Model\DataObject\AbstractObject::getById() + // does. + $baseClassName = 'Pimcore\\Model\\DataObject\\' . ucfirst($containerDefinition->getName()); + // @TODO figure out a nicer way to handle this. Really naughty + // to call kernel directly - but static doesn't have DI. + /** @var self $service */ + $service = \Pimcore::getKernel()->getContainer()->get(static::class); + $className = $service->getModelFactory()->getClassNameFor($baseClassName); + $methodCheckClass = new \ReflectionClass($className); + } else { + $skipMethodCallCheck = true; + } + } + if ( + ( + $methodCheckClass->hasMethod($method) + && $methodCheckClass->getMethod($method)->isPublic() + ) + || $skipMethodCallCheck + ) { + return true; + } + return false; + } + + /** + * Returns the ClassDefinition of a container - works with DefaultMockup + * objects too. + * + * @param object $container + * + * @return ClassDefinition|null + * @throws \Exception + */ + public static function getContainerClassDefinition(object $container): ?ClassDefinition + { + // Adjust meta data for data handling on type of the data container. + switch (true) { + case $container instanceof Concrete: + return $container->getClass(); + + case $container instanceof \Pimcore\Model\DataObject\Fieldcollection\Data\AbstractData: + case $container instanceof \Pimcore\Model\DataObject\Objectbrick\Data\AbstractData: + return $container->getDefinition(); + + // All default indexers implement o_classId - access it directly to + // load class definition and with it use the model loader to fetch + // the actual implementing class for further reflection. + case $container instanceof DefaultMockup: + if (($mockClassId = $container->getParam('o_classId'))) { + return ClassDefinition::getById($mockClassId); + } + break; + } + return null; + } } From 544ec2065dbc25f1eb828f2e738de929789eea7c Mon Sep 17 00:00:00 2001 From: das-peter Date: Thu, 15 Aug 2024 09:26:30 +0000 Subject: [PATCH 09/23] Apply php-cs-fixer changes --- src/GraphQL/FieldHelper/DataObjectFieldHelper.php | 2 -- src/GraphQL/RelationHelper.php | 2 +- src/GraphQL/Service.php | 4 ++++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/GraphQL/FieldHelper/DataObjectFieldHelper.php b/src/GraphQL/FieldHelper/DataObjectFieldHelper.php index 0b34691d7..8845d887a 100644 --- a/src/GraphQL/FieldHelper/DataObjectFieldHelper.php +++ b/src/GraphQL/FieldHelper/DataObjectFieldHelper.php @@ -19,13 +19,11 @@ use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\Type; use Pimcore\Bundle\DataHubBundle\GraphQL\Exception\ClientSafeException; -use Pimcore\Bundle\EcommerceFrameworkBundle\Model\DefaultMockup; use Pimcore\File; use Pimcore\Logger; use Pimcore\Model\DataObject\ClassDefinition; use Pimcore\Model\DataObject\ClassDefinition\Data; use Pimcore\Model\DataObject\Concrete; -use Pimcore\Model\DataObject\Fieldcollection\Data\AbstractData; use Pimcore\Model\DataObject\Localizedfield; use Pimcore\Model\DataObject\Objectbrick\Definition; use Pimcore\Model\Factory; diff --git a/src/GraphQL/RelationHelper.php b/src/GraphQL/RelationHelper.php index 8ba9f8986..76d384b9e 100644 --- a/src/GraphQL/RelationHelper.php +++ b/src/GraphQL/RelationHelper.php @@ -30,7 +30,7 @@ class RelationHelper * * @return ElementDescriptor */ - public static function processRelation(ElementInterface|DefaultMockup $relation, Service $graphQlService, $args, $context, ResolveInfo $resolveInfo) + public static function processRelation(ElementInterface | DefaultMockup $relation, Service $graphQlService, $args, $context, ResolveInfo $resolveInfo) { $data = new ElementDescriptor($relation); $graphQlService->extractData($data, $relation, $args, $context, $resolveInfo); diff --git a/src/GraphQL/Service.php b/src/GraphQL/Service.php index ef2ca7935..ef2fee80e 100644 --- a/src/GraphQL/Service.php +++ b/src/GraphQL/Service.php @@ -1221,6 +1221,7 @@ public function querySchemaEnabled(string $type) * @param string $method * * @return bool + * * @throws \ReflectionException */ public static function checkContainerMethodExists(object $container, string $method): bool @@ -1259,6 +1260,7 @@ public static function checkContainerMethodExists(object $container, string $met ) { return true; } + return false; } @@ -1269,6 +1271,7 @@ public static function checkContainerMethodExists(object $container, string $met * @param object $container * * @return ClassDefinition|null + * * @throws \Exception */ public static function getContainerClassDefinition(object $container): ?ClassDefinition @@ -1291,6 +1294,7 @@ public static function getContainerClassDefinition(object $container): ?ClassDef } break; } + return null; } } From 180c4592db5276dd52827ba83647e1c80649b043 Mon Sep 17 00:00:00 2001 From: Peter Philipp Date: Fri, 16 Aug 2024 11:10:31 +0200 Subject: [PATCH 10/23] chore: Completely overhauled Mockup handling - introduced custom ElementMockupInterface which allows to handle everything and not just objects / products like \Pimcore\Bundle\EcommerceFrameworkBundle\Model\DefaultMockup. --- src/Controller/WebserviceController.php | 4 + .../Helper/Href.php | 7 +- .../Helper/Image.php | 3 +- .../Helper/ImageGallery.php | 3 +- .../Helper/Multihref.php | 2 - .../Helper/MultihrefMetadata.php | 4 +- .../Helper/Objects.php | 7 +- .../Helper/ObjectsMetadata.php | 4 +- src/GraphQL/ElementDescriptor.php | 11 ++- .../FieldHelper/AbstractFieldHelper.php | 4 +- src/GraphQL/FieldHelper/AssetFieldHelper.php | 2 +- src/GraphQL/RelationHelper.php | 6 +- src/GraphQL/Resolver/AssetType.php | 17 +++++ src/GraphQL/Service.php | 73 ++++++++++--------- src/GraphQL/Traits/ElementLoaderTrait.php | 4 +- src/Model/ElementMockupInterface.php | 60 +++++++++++++++ src/WorkspaceHelper.php | 13 +++- 17 files changed, 155 insertions(+), 69 deletions(-) create mode 100644 src/Model/ElementMockupInterface.php diff --git a/src/Controller/WebserviceController.php b/src/Controller/WebserviceController.php index fd4432051..0dea72783 100644 --- a/src/Controller/WebserviceController.php +++ b/src/Controller/WebserviceController.php @@ -110,6 +110,10 @@ protected function getCachingContextConfiguration(Request $request): array $config['resolveObjectGetter'] = !$request->query->has('datahub-cache-disable-resolveObjectGetter'); } + $config['operations'] = false; + $config['resolveEdge'] = false; + $config['resolveObjectGetter'] = false; + return $config; } diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Href.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Href.php index a74d3153d..a04472d26 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Href.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Href.php @@ -20,8 +20,8 @@ use Pimcore\Bundle\DataHubBundle\GraphQL\ElementDescriptor; use Pimcore\Bundle\DataHubBundle\GraphQL\Service; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ServiceTrait; +use Pimcore\Bundle\DataHubBundle\Model\ElementMockupInterface; use Pimcore\Bundle\DataHubBundle\WorkspaceHelper; -use Pimcore\Bundle\EcommerceFrameworkBundle\Model\DefaultMockup; use Pimcore\Model\DataObject\ClassDefinition; use Pimcore\Model\DataObject\ClassDefinition\Data; use Pimcore\Model\Element\ElementInterface; @@ -75,9 +75,8 @@ public function resolve($value = null, $args = [], $context = [], ResolveInfo $r if ($value instanceof BaseDescriptor) { $relation = \Pimcore\Bundle\DataHubBundle\GraphQL\Service::resolveValue($value, $this->fieldDefinition, $this->attribute, $args); - if ($relation instanceof ElementInterface || $relation instanceof DefaultMockup) { - $type = ($relation instanceof DefaultMockup) ? 'object' : ''; - if (!WorkspaceHelper::checkPermission($relation, 'read', $type)) { + if ($relation instanceof ElementInterface || $relation instanceof ElementMockupInterface) { + if (!WorkspaceHelper::checkPermission($relation, 'read')) { return null; } diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Image.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Image.php index 2a2887f4b..54234aa78 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Image.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Image.php @@ -20,6 +20,7 @@ use Pimcore\Bundle\DataHubBundle\GraphQL\ElementDescriptor; use Pimcore\Bundle\DataHubBundle\GraphQL\Service; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ServiceTrait; +use Pimcore\Bundle\DataHubBundle\Model\ElementMockupInterface; use Pimcore\Bundle\DataHubBundle\WorkspaceHelper; use Pimcore\Model\Asset; use Pimcore\Model\DataObject\ClassDefinition; @@ -74,7 +75,7 @@ public function resolve($value = null, $args = [], $context = [], ResolveInfo $r if ($value instanceof BaseDescriptor) { $relation = Service::resolveValue($value, $this->fieldDefinition, $this->attribute, $args); - if ($relation instanceof Asset) { + if ($relation instanceof Asset || $relation instanceof ElementMockupInterface) { if (!WorkspaceHelper::checkPermission($relation, 'read')) { return null; } diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/ImageGallery.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/ImageGallery.php index faf63dec0..49c6b3a2b 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/ImageGallery.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/ImageGallery.php @@ -21,6 +21,7 @@ use Pimcore\Bundle\DataHubBundle\GraphQL\ElementDescriptor; use Pimcore\Bundle\DataHubBundle\GraphQL\Service as GraphQlService; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ServiceTrait; +use Pimcore\Bundle\DataHubBundle\Model\ElementMockupInterface; use Pimcore\Bundle\DataHubBundle\WorkspaceHelper; use Pimcore\Model\Asset; use Pimcore\Model\DataObject\ClassDefinition; @@ -92,7 +93,7 @@ public function resolve($value = null, $args = [], $context = [], ResolveInfo $r continue; } - if ($image instanceof Asset) { + if ($image instanceof Asset || $relation instanceof ElementMockupInterface) { if (!WorkspaceHelper::checkPermission($image, 'read')) { continue; } diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Multihref.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Multihref.php index 9abb9064b..f46624705 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Multihref.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Multihref.php @@ -21,7 +21,6 @@ use Pimcore\Bundle\DataHubBundle\GraphQL\Service; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ServiceTrait; use Pimcore\Bundle\DataHubBundle\WorkspaceHelper; -use Pimcore\Bundle\EcommerceFrameworkBundle\Model\DefaultMockup; use Pimcore\Model\DataObject\ClassDefinition; use Pimcore\Model\Element\AbstractElement; @@ -75,7 +74,6 @@ public function resolve($value = null, $args = [], $context = [], ResolveInfo $r if ($relations) { /** @var AbstractElement $relation */ foreach ($relations as $relation) { - $type = ($relation instanceof DefaultMockup) ? 'object' : ''; if (!WorkspaceHelper::checkPermission($relation, 'read', $type)) { continue; } diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/MultihrefMetadata.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/MultihrefMetadata.php index b74d1aed8..590a0a54e 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/MultihrefMetadata.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/MultihrefMetadata.php @@ -21,7 +21,6 @@ use Pimcore\Bundle\DataHubBundle\GraphQL\Service; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ServiceTrait; use Pimcore\Bundle\DataHubBundle\WorkspaceHelper; -use Pimcore\Bundle\EcommerceFrameworkBundle\Model\DefaultMockup; use Pimcore\Model\DataObject\ClassDefinition; use Pimcore\Model\DataObject\ClassDefinition\Data; use Pimcore\Model\DataObject\Data\ElementMetadata; @@ -77,8 +76,7 @@ public function resolve($value = null, $args = [], $context = [], ResolveInfo $r /** @var ElementMetadata $relation */ foreach ($relations as $relation) { $element = $relation->getElement(); - $type = ($element instanceof DefaultMockup) ? 'object' : ''; - if (!WorkspaceHelper::checkPermission($element, 'read', $type)) { + if (!WorkspaceHelper::checkPermission($element, 'read')) { continue; } diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Objects.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Objects.php index be4ec3d7a..0b6735ea1 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Objects.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Objects.php @@ -23,7 +23,6 @@ use Pimcore\Bundle\DataHubBundle\GraphQL\Service; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ServiceTrait; use Pimcore\Bundle\DataHubBundle\WorkspaceHelper; -use Pimcore\Bundle\EcommerceFrameworkBundle\Model\DefaultMockup; use Pimcore\Model\DataObject\ClassDefinition; use Pimcore\Model\DataObject\ClassDefinition\Data; use Pimcore\Model\Element\AbstractElement; @@ -79,11 +78,7 @@ public function resolve($value = null, $args = [], $context = [], ResolveInfo $r $result = []; foreach ($relations as $relation) { /** @var $relation AbstractElement */ - // Explicitly set the type of the mockup object because - // these don't have the matching class instance for - // auto-detect. - $type = ($relation instanceof DefaultMockup) ? 'object' : ''; - if (!WorkspaceHelper::checkPermission($relation, 'read', $type)) { + if (!WorkspaceHelper::checkPermission($relation, 'read')) { continue; } diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/ObjectsMetadata.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/ObjectsMetadata.php index fd2e5118c..1ac5b36b2 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/ObjectsMetadata.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/ObjectsMetadata.php @@ -21,7 +21,6 @@ use Pimcore\Bundle\DataHubBundle\GraphQL\Service; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ServiceTrait; use Pimcore\Bundle\DataHubBundle\WorkspaceHelper; -use Pimcore\Bundle\EcommerceFrameworkBundle\Model\DefaultMockup; use Pimcore\Model\DataObject\ClassDefinition; use Pimcore\Model\DataObject\Data\ObjectMetadata; @@ -76,8 +75,7 @@ public function resolve($value = null, $args = [], $context = [], ResolveInfo $r /** @var ObjectMetadata $relation */ foreach ($relations as $relation) { $element = $relation->getElement(); - $type = ($element instanceof DefaultMockup) ? 'object' : ''; - if (!WorkspaceHelper::checkPermission($element, 'read', $type)) { + if (!WorkspaceHelper::checkPermission($element, 'read')) { continue; } diff --git a/src/GraphQL/ElementDescriptor.php b/src/GraphQL/ElementDescriptor.php index 08ac4b8ba..06c66a693 100644 --- a/src/GraphQL/ElementDescriptor.php +++ b/src/GraphQL/ElementDescriptor.php @@ -15,7 +15,7 @@ namespace Pimcore\Bundle\DataHubBundle\GraphQL; -use Pimcore\Bundle\EcommerceFrameworkBundle\Model\DefaultMockup; +use Pimcore\Bundle\DataHubBundle\Model\ElementMockupInterface; use Pimcore\Model\Asset; use Pimcore\Model\DataObject\Concrete; use Pimcore\Model\Document; @@ -26,7 +26,7 @@ class ElementDescriptor extends BaseDescriptor /** * @param ElementInterface|null $element */ - public function __construct(ElementInterface | DefaultMockup $element = null) + public function __construct(ElementInterface | ElementMockupInterface $element = null) { parent::__construct(); if ($element) { @@ -34,10 +34,9 @@ public function __construct(ElementInterface | DefaultMockup $element = null) $this->offsetSet('__elementType', \Pimcore\Model\Element\Service::getElementType($element)); $this->offsetSet('__elementSubtype', $element instanceof Concrete ? $element->getClass()->getName() : $element->getType()); - if ($element instanceof DefaultMockup) { - $subtype = $element->getClass()->getName(); - $this->offsetSet('__elementType', 'object'); - $this->offsetSet('__elementSubtype', $subtype); + if ($element instanceof ElementMockupInterface) { + $this->offsetSet('__elementType', $element->getElementType()); + $this->offsetSet('__elementSubtype', $element->getElementType() === 'object' ? $element->getClass()->getName() : $element->getType()); } elseif ($element instanceof Concrete) { $subtype = $element->getClass()->getName(); diff --git a/src/GraphQL/FieldHelper/AbstractFieldHelper.php b/src/GraphQL/FieldHelper/AbstractFieldHelper.php index 33812bf32..fa313beb3 100644 --- a/src/GraphQL/FieldHelper/AbstractFieldHelper.php +++ b/src/GraphQL/FieldHelper/AbstractFieldHelper.php @@ -24,7 +24,7 @@ use GraphQL\Type\Definition\ResolveInfo; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ElementLoaderTrait; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ServiceTrait; -use Pimcore\Bundle\EcommerceFrameworkBundle\Model\DefaultMockup; +use Pimcore\Bundle\DataHubBundle\Model\ElementMockupInterface; use Pimcore\Model\Element\ElementInterface; abstract class AbstractFieldHelper @@ -120,7 +120,7 @@ public function getArguments(FieldNode $ast) */ public function extractData(&$data, $container, $args, $context = [], ResolveInfo $resolveInfo = null) { - if ($container instanceof ElementInterface || $container instanceof DefaultMockup) { + if ($container instanceof ElementInterface || $container instanceof ElementMockupInterface) { // we have to at least add the ID and pass it around even if not requested because we need it internally // to resolve fields of linked elements (such as asset image and so on) $data = $this->setDataElement($data, $container); diff --git a/src/GraphQL/FieldHelper/AssetFieldHelper.php b/src/GraphQL/FieldHelper/AssetFieldHelper.php index bbabd2e0c..0479b2347 100644 --- a/src/GraphQL/FieldHelper/AssetFieldHelper.php +++ b/src/GraphQL/FieldHelper/AssetFieldHelper.php @@ -128,7 +128,7 @@ public function doExtractData(FieldNode $ast, &$data, $container, $args, $contex } } } else { - if (method_exists($container, $getter)) { + if ($this->getGraphQlService()::checkContainerMethodExists($container, $getter)) { if ($languageArgument) { if ($ast->alias) { // defer it diff --git a/src/GraphQL/RelationHelper.php b/src/GraphQL/RelationHelper.php index 76d384b9e..68c89cc46 100644 --- a/src/GraphQL/RelationHelper.php +++ b/src/GraphQL/RelationHelper.php @@ -16,13 +16,13 @@ namespace Pimcore\Bundle\DataHubBundle\GraphQL; use GraphQL\Type\Definition\ResolveInfo; -use Pimcore\Bundle\EcommerceFrameworkBundle\Model\DefaultMockup; +use Pimcore\Bundle\DataHubBundle\Model\ElementMockupInterface; use Pimcore\Model\Element\ElementInterface; class RelationHelper { /** - * @param ElementInterface|DefaultMockup $relation + * @param ElementInterface|ElementMockupInterface $relation * @param Service $graphQlService * @param array $args * @param array $context @@ -30,7 +30,7 @@ class RelationHelper * * @return ElementDescriptor */ - public static function processRelation(ElementInterface | DefaultMockup $relation, Service $graphQlService, $args, $context, ResolveInfo $resolveInfo) + public static function processRelation(ElementInterface | ElementMockupInterface $relation, Service $graphQlService, $args, $context, ResolveInfo $resolveInfo) { $data = new ElementDescriptor($relation); $graphQlService->extractData($data, $relation, $args, $context, $resolveInfo); diff --git a/src/GraphQL/Resolver/AssetType.php b/src/GraphQL/Resolver/AssetType.php index e49880d8e..7d9d9c20b 100644 --- a/src/GraphQL/Resolver/AssetType.php +++ b/src/GraphQL/Resolver/AssetType.php @@ -23,6 +23,7 @@ use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ElementLoaderTrait; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ElementTagTrait; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ServiceTrait; +use Pimcore\Bundle\DataHubBundle\Model\ElementMockupInterface; use Pimcore\Bundle\DataHubBundle\WorkspaceHelper; use Pimcore\Model\Asset; @@ -141,6 +142,10 @@ public function resolvePath($value = null, $args = [], $context = [], ResolveInf { if ($value instanceof BaseDescriptor) { $asset = $this->getAssetFromValue($value, $context); + // Check if the value was already resolved in a mockup object. + if ($asset instanceof ElementMockupInterface && isset($value[$resolveInfo->fieldName])) { + return $value[$resolveInfo->fieldName]; + } $thumbNailConfig = $args['thumbnail'] ?? null; $thumbNailFormat = $args['format'] ?? null; $assetFieldHelper = $this->getGraphQLService()->getAssetFieldHelper(); @@ -257,6 +262,10 @@ public function resolveResolutions($value = null, $args = [], $context = [], Res if (!$resolveInfo || $resolveInfo->fieldName !== 'data') { $deferredThumbnail = true; } + // Check if the value was already resolved in a mockup object. + if ($thumbnail instanceof ElementMockupInterface && isset($value[$resolveInfo->fieldName])) { + return $value[$resolveInfo->fieldName]; + } if ($thumbnail instanceof Asset\Image\Thumbnail) { $resolutions = []; @@ -292,6 +301,10 @@ public function resolveResolutions($value = null, $args = [], $context = [], Res if (!$asset) { return []; } + // Check if the value was already resolved in a mockup object. + if ($asset instanceof ElementMockupInterface && isset($value[$resolveInfo->fieldName])) { + return $value[$resolveInfo->fieldName]; + } $thumbnail = $assetFieldHelper->getAssetThumbnail($asset, $thumbnailName, $thumbnailFormat, $deferredThumbnail); if (isset($thumbnail)) { $thumbnailConfig = $thumbnail->getConfig(); @@ -331,6 +344,10 @@ public function resolveDimensions($value = null, $args = [], $context = [], Reso if ($value instanceof ElementDescriptor) { $thumbnailName = $args['thumbnail'] ?? null; $asset = $this->getAssetFromValue($value, $context); + // Check if the value was already resolved in a mockup object. + if ($asset instanceof ElementMockupInterface && isset($value[$resolveInfo->fieldName])) { + return $value[$resolveInfo->fieldName]; + } if ($asset instanceof Asset\Video) { $width = $asset->getCustomSetting('videoWidth'); diff --git a/src/GraphQL/Service.php b/src/GraphQL/Service.php index ef2fee80e..c536e6de7 100644 --- a/src/GraphQL/Service.php +++ b/src/GraphQL/Service.php @@ -39,19 +39,16 @@ use Pimcore\Bundle\DataHubBundle\GraphQL\Query\Operator\Factory\OperatorFactoryInterface; use Pimcore\Bundle\DataHubBundle\GraphQL\Query\Value\DefaultValue; use Pimcore\Bundle\DataHubBundle\GraphQL\Traits\ElementLoaderTrait; +use Pimcore\Bundle\DataHubBundle\Model\ElementMockupInterface; use Pimcore\Bundle\DataHubBundle\PimcoreDataHubBundle; -use Pimcore\Bundle\EcommerceFrameworkBundle\Model\DefaultMockup; use Pimcore\Cache\RuntimeCache; use Pimcore\DataObject\GridColumnConfig\ConfigElementInterface; use Pimcore\Localization\LocaleServiceInterface; -use Pimcore\Model\Asset; -use Pimcore\Model\DataObject\AbstractObject; use Pimcore\Model\DataObject\ClassDefinition; use Pimcore\Model\DataObject\ClassDefinition\Data; use Pimcore\Model\DataObject\Concrete; use Pimcore\Model\DataObject\Objectbrick\Data\AbstractData; use Pimcore\Model\DataObject\Objectbrick\Definition; -use Pimcore\Model\Document; use Pimcore\Model\Element\ElementInterface; use Pimcore\Model\Factory; use Pimcore\Translation\Translator; @@ -833,7 +830,7 @@ public static function getValueForObject($object, $key, $brickType = null, $bric if ($fieldDefinition->isEmpty($value)) { $parent = \Pimcore\Model\DataObject\Service::hasInheritableParentObject($object); if (!empty($parent)) { - if (!($parent instanceof Concrete)) { + if (!($parent instanceof Concrete) && !($parent instanceof ElementMockupInterface)) { $parent = Concrete::getById($parent->getId()); } @@ -1173,16 +1170,17 @@ public function getDataObjectDataTypes(): array */ public function extractData($data, $target, $args = [], $context = [], ResolveInfo $resolveInfo = null) { - $fieldHelper = null; - if ($target instanceof Document) { - $fieldHelper = $this->getDocumentFieldHelper(); - } elseif ($target instanceof Asset) { - $fieldHelper = $this->getAssetFieldHelper(); - } elseif ($target instanceof AbstractObject) { - $fieldHelper = $this->getObjectFieldHelper(); - } elseif ($target instanceof DefaultMockup) { - $fieldHelper = $this->getObjectFieldHelper(); + $type = null; + if ($target instanceof ElementInterface) { + $type = \Pimcore\Model\Element\Service::getElementType($target); + } elseif ($target instanceof ElementMockupInterface) { + $type = $target->getElementType(); } + $fieldHelper = match ($type) { + 'document' => $this->getDocumentFieldHelper(), + 'asset' => $this->getAssetFieldHelper(), + 'object' => $this->getObjectFieldHelper(), + }; if ($fieldHelper) { $fieldHelper->extractData($data, $target, $args, $context, $resolveInfo); @@ -1212,8 +1210,8 @@ public function querySchemaEnabled(string $type) /** * Checks if a container has a given method. * - * This works with DefaultMockup objects too that's why it's so overly - * complex. DefaultMockup objects will use the class definition of the + * This works with ElementMockupInterface objects too that's why it's so overly + * complex. ElementMockupInterface objects will use the class definition of the * mocked object to determine which model class has to be checked for the * method. * @@ -1234,13 +1232,30 @@ public static function checkContainerMethodExists(object $container, string $met $skipMethodCallCheck = false; // Adjust meta data for data handling on type of the data container. - $containerDefinition = static::getContainerClassDefinition($container); - if ($container instanceof DefaultMockup) { - if ($containerDefinition) { - // Unfortunately there's no API for this so we re-implement - // what \Pimcore\Model\DataObject\AbstractObject::getById() - // does. - $baseClassName = 'Pimcore\\Model\\DataObject\\' . ucfirst($containerDefinition->getName()); + if ($container instanceof ElementMockupInterface) { + // If the mockup implements it use it straight away. + if (method_exists($container, $method)) { + return true; + } + // Otherwise check if the mocked class implements the method. + switch ($container->getElementType()) { + case 'object': + $containerDefinition = static::getContainerClassDefinition($container); + if ($containerDefinition) { + // Unfortunately there's no API for this so we re-implement + // what \Pimcore\Model\DataObject\AbstractObject::getById() + // does. + $baseClassName = 'Pimcore\\Model\\DataObject\\' . ucfirst($containerDefinition->getName()); + } + break; + case 'document': + $baseClassName = 'Pimcore\\Model\\Document\\' . ucfirst($container->getType()); + break; + case 'asset': + $baseClassName = 'Pimcore\\Model\\Asset\\' . ucfirst($container->getType()); + break; + } + if (isset($baseClassName)) { // @TODO figure out a nicer way to handle this. Really naughty // to call kernel directly - but static doesn't have DI. /** @var self $service */ @@ -1265,7 +1280,7 @@ public static function checkContainerMethodExists(object $container, string $met } /** - * Returns the ClassDefinition of a container - works with DefaultMockup + * Returns the ClassDefinition of a container - works with ElementMockupInterface * objects too. * * @param object $container @@ -1279,20 +1294,12 @@ public static function getContainerClassDefinition(object $container): ?ClassDef // Adjust meta data for data handling on type of the data container. switch (true) { case $container instanceof Concrete: + case $container instanceof ElementMockupInterface: return $container->getClass(); case $container instanceof \Pimcore\Model\DataObject\Fieldcollection\Data\AbstractData: case $container instanceof \Pimcore\Model\DataObject\Objectbrick\Data\AbstractData: return $container->getDefinition(); - - // All default indexers implement o_classId - access it directly to - // load class definition and with it use the model loader to fetch - // the actual implementing class for further reflection. - case $container instanceof DefaultMockup: - if (($mockClassId = $container->getParam('o_classId'))) { - return ClassDefinition::getById($mockClassId); - } - break; } return null; diff --git a/src/GraphQL/Traits/ElementLoaderTrait.php b/src/GraphQL/Traits/ElementLoaderTrait.php index 4516dec67..1cfa00101 100644 --- a/src/GraphQL/Traits/ElementLoaderTrait.php +++ b/src/GraphQL/Traits/ElementLoaderTrait.php @@ -17,7 +17,7 @@ namespace Pimcore\Bundle\DataHubBundle\GraphQL\Traits; -use Pimcore\Bundle\EcommerceFrameworkBundle\Model\DefaultMockup; +use Pimcore\Bundle\DataHubBundle\Model\ElementMockupInterface; use Pimcore\Model\Element\ElementInterface; use Pimcore\Model\Element\Service as ElementService; @@ -33,7 +33,7 @@ trait ElementLoaderTrait * * @return array */ - protected function setDataElement($data, ElementInterface | DefaultMockup $element) + protected function setDataElement($data, ElementInterface | ElementMockupInterface $element) { $data['id'] = $element->getId(); $data[ElementInterface::class . '_type'] = $element->getType(); diff --git a/src/Model/ElementMockupInterface.php b/src/Model/ElementMockupInterface.php new file mode 100644 index 000000000..be61a3e99 --- /dev/null +++ b/src/Model/ElementMockupInterface.php @@ -0,0 +1,60 @@ +getElementType(); + } else { + $elementType = Service::getElementType($element); + } } // Could be dealing with mock objects that can't be loaded due // to stale index so be extra cautions when using. @@ -280,7 +285,11 @@ public static function isAllowed($element, Configuration $configuration, string return false; } if (!$elementType) { - $elementType = Service::getElementType($element); + if ($element instanceof ElementMockupInterface) { + $elementType = $element->getElementType(); + } else { + $elementType = Service::getElementType($element); + } } // collect properties via parent - ids $parentIds = [1]; From 60854eb1116014deacdf5a6b9254c35c0e5ade91 Mon Sep 17 00:00:00 2001 From: das-peter Date: Fri, 16 Aug 2024 09:11:54 +0000 Subject: [PATCH 11/23] Apply php-cs-fixer changes --- src/GraphQL/RelationHelper.php | 2 +- src/Model/ElementMockupInterface.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GraphQL/RelationHelper.php b/src/GraphQL/RelationHelper.php index 68c89cc46..bff1fd633 100644 --- a/src/GraphQL/RelationHelper.php +++ b/src/GraphQL/RelationHelper.php @@ -30,7 +30,7 @@ class RelationHelper * * @return ElementDescriptor */ - public static function processRelation(ElementInterface | ElementMockupInterface $relation, Service $graphQlService, $args, $context, ResolveInfo $resolveInfo) + public static function processRelation(ElementInterface | ElementMockupInterface $relation, Service $graphQlService, $args, $context, ResolveInfo $resolveInfo) { $data = new ElementDescriptor($relation); $graphQlService->extractData($data, $relation, $args, $context, $resolveInfo); diff --git a/src/Model/ElementMockupInterface.php b/src/Model/ElementMockupInterface.php index be61a3e99..db2007b32 100644 --- a/src/Model/ElementMockupInterface.php +++ b/src/Model/ElementMockupInterface.php @@ -56,5 +56,5 @@ public function getId(); /** * @return \Pimcore\Model\Element\ElementInterface|null */ - public function getOriginalObject(): ElementInterface|null; + public function getOriginalObject(): ElementInterface | null; } From 4f99a28ab6ce04ee579adea43602c03b3601a333 Mon Sep 17 00:00:00 2001 From: Peter Philipp Date: Fri, 16 Aug 2024 12:37:36 +0200 Subject: [PATCH 12/23] chore: Fix type of getContainerClassDefinition() AbstractModel works for everything. --- src/GraphQL/Service.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/GraphQL/Service.php b/src/GraphQL/Service.php index c536e6de7..166437aff 100644 --- a/src/GraphQL/Service.php +++ b/src/GraphQL/Service.php @@ -44,6 +44,7 @@ use Pimcore\Cache\RuntimeCache; use Pimcore\DataObject\GridColumnConfig\ConfigElementInterface; use Pimcore\Localization\LocaleServiceInterface; +use Pimcore\Model\AbstractModel; use Pimcore\Model\DataObject\ClassDefinition; use Pimcore\Model\DataObject\ClassDefinition\Data; use Pimcore\Model\DataObject\Concrete; @@ -1285,11 +1286,11 @@ public static function checkContainerMethodExists(object $container, string $met * * @param object $container * - * @return ClassDefinition|null + * @return \Pimcore\Model\AbstractModel|null * * @throws \Exception */ - public static function getContainerClassDefinition(object $container): ?ClassDefinition + public static function getContainerClassDefinition(object $container): ?AbstractModel { // Adjust meta data for data handling on type of the data container. switch (true) { From dc102f41f84e418f6a076b4cd47b4b9f19597706 Mon Sep 17 00:00:00 2001 From: Peter Philipp Date: Fri, 16 Aug 2024 16:38:02 +0200 Subject: [PATCH 13/23] chore: Added context data handling for ElementMockupInterface. This will allow to select appropriate pre-processed data depending on execution context. --- src/GraphQL/FieldHelper/AssetFieldHelper.php | 12 ++--- .../FieldHelper/DataObjectFieldHelper.php | 12 ++--- src/GraphQL/Service.php | 44 ++++++++++++++++--- src/Model/ElementMockupInterface.php | 20 +++++++++ 4 files changed, 71 insertions(+), 17 deletions(-) diff --git a/src/GraphQL/FieldHelper/AssetFieldHelper.php b/src/GraphQL/FieldHelper/AssetFieldHelper.php index 0479b2347..4b1e0fa83 100644 --- a/src/GraphQL/FieldHelper/AssetFieldHelper.php +++ b/src/GraphQL/FieldHelper/AssetFieldHelper.php @@ -17,6 +17,7 @@ use GraphQL\Language\AST\FieldNode; use GraphQL\Type\Definition\ResolveInfo; +use Pimcore\Bundle\DataHubBundle\GraphQL\Service; use Pimcore\Model\Asset; use Pimcore\Model\Asset\Image; use Pimcore\Model\Asset\Video; @@ -128,21 +129,22 @@ public function doExtractData(FieldNode $ast, &$data, $container, $args, $contex } } } else { - if ($this->getGraphQlService()::checkContainerMethodExists($container, $getter)) { + if (Service::checkContainerMethodExists($container, $getter)) { if ($languageArgument) { if ($ast->alias) { // defer it $data[$realName] = function ($source, $args, $context, ResolveInfo $info) use ( $container, - $getter + $getter, + $ast ) { - return $container->$getter($args['language'] ?? null); + return Service::callContainerGetterMethod($container, $getter, ['language' => $args['language'] ?? null], $info, $ast); }; } else { - $data[$realName] = $container->$getter($languageArgument); + $data[$realName] = Service::callContainerGetterMethod($container, $getter, ['language' => $languageArgument], $resolveInfo, $ast); } } else { - $data[$realName] = $container->$getter(); + $data[$realName] = Service::callContainerGetterMethod($container, $getter, [], $resolveInfo, $ast); } } } diff --git a/src/GraphQL/FieldHelper/DataObjectFieldHelper.php b/src/GraphQL/FieldHelper/DataObjectFieldHelper.php index 8845d887a..e46dbc5f2 100644 --- a/src/GraphQL/FieldHelper/DataObjectFieldHelper.php +++ b/src/GraphQL/FieldHelper/DataObjectFieldHelper.php @@ -19,6 +19,7 @@ use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\Type; use Pimcore\Bundle\DataHubBundle\GraphQL\Exception\ClientSafeException; +use Pimcore\Bundle\DataHubBundle\GraphQL\Service; use Pimcore\File; use Pimcore\Logger; use Pimcore\Model\DataObject\ClassDefinition; @@ -372,18 +373,19 @@ public function doExtractData(FieldNode $ast, &$data, $container, $args, $contex // throw new MySafeException("fieldhelper", "TBD customized error message"); $getter = 'get' . ucfirst($astName); - if ($this->getGraphQlService()::checkContainerMethodExists($container, $getter)) { - $isLocalizedField = $this->getGraphQlService()::isLocalizedField($container, $astName); + if (Service::checkContainerMethodExists($container, $getter)) { + $isLocalizedField = Service::isLocalizedField($container, $astName); if ($isLocalizedField) { // defer it $data[$astName] = function ($source, $args, $context, ResolveInfo $info) use ( $container, - $getter + $getter, + $ast ) { - return $container->$getter($args['language'] ?? null); + return Service::callContainerGetterMethod($container, $getter, ['language' => $args['language'] ?? null], $info, $ast); }; } else { - $data[$astName] = $container->$getter(); + $data[$astName] = Service::callContainerGetterMethod($container, $getter, [], $resolveInfo, $ast); } } } diff --git a/src/GraphQL/Service.php b/src/GraphQL/Service.php index 166437aff..67b5fb42f 100644 --- a/src/GraphQL/Service.php +++ b/src/GraphQL/Service.php @@ -44,12 +44,11 @@ use Pimcore\Cache\RuntimeCache; use Pimcore\DataObject\GridColumnConfig\ConfigElementInterface; use Pimcore\Localization\LocaleServiceInterface; -use Pimcore\Model\AbstractModel; use Pimcore\Model\DataObject\ClassDefinition; use Pimcore\Model\DataObject\ClassDefinition\Data; use Pimcore\Model\DataObject\Concrete; +use Pimcore\Model\DataObject\Fieldcollection\Definition; use Pimcore\Model\DataObject\Objectbrick\Data\AbstractData; -use Pimcore\Model\DataObject\Objectbrick\Definition; use Pimcore\Model\Element\ElementInterface; use Pimcore\Model\Factory; use Pimcore\Translation\Translator; @@ -1078,9 +1077,9 @@ public static function resolveValue(BaseDescriptor $descriptor, Data $fieldDefin } elseif (static::checkContainerMethodExists($container, $getter)) { $isLocalizedField = self::isLocalizedField($container, $fieldDefinition->getName()); if ($isLocalizedField) { - $result = $container->$getter($args['language'] ?? null); + $result = Service::callContainerGetterMethod($container, $getter, ['language' => $args['language'] ?? null]); } else { - $result = $container->$getter(); + $result = Service::callContainerGetterMethod($container, $getter); } } @@ -1286,11 +1285,10 @@ public static function checkContainerMethodExists(object $container, string $met * * @param object $container * - * @return \Pimcore\Model\AbstractModel|null + * @return \Pimcore\Model\DataObject\ClassDefinition|\Pimcore\Model\DataObject\Fieldcollection\Definition|null * - * @throws \Exception */ - public static function getContainerClassDefinition(object $container): ?AbstractModel + public static function getContainerClassDefinition(object $container): ClassDefinition | Definition | null { // Adjust meta data for data handling on type of the data container. switch (true) { @@ -1305,4 +1303,36 @@ public static function getContainerClassDefinition(object $container): ?Abstract return null; } + + /** + * Call the getter function on a container. + * + * Passes on execution context to containers with the ElementMockupInterface. + * + * @param object $container + * @param string $getter + * @param array $getterArgs + * @param \GraphQL\Type\Definition\ResolveInfo|null $resolveInfo + * + * @return mixed + */ + public static function callContainerGetterMethod( + object $container, + string $getter, + array $getterArgs = [], + ?ResolveInfo $resolveInfo = null, + ?FieldNode $ast = null + ): mixed { + if ($container instanceof ElementMockupInterface) { + $container->setGraphQLContext($getter, $getterArgs, $resolveInfo, $ast); + } + try { + $return = call_user_func_array([$container, $getter], $getterArgs); + } finally { + if ($container instanceof ElementMockupInterface) { + $container->setGraphQLContext(null); + } + } + return $return; + } } diff --git a/src/Model/ElementMockupInterface.php b/src/Model/ElementMockupInterface.php index db2007b32..19d76c55d 100644 --- a/src/Model/ElementMockupInterface.php +++ b/src/Model/ElementMockupInterface.php @@ -15,6 +15,8 @@ namespace Pimcore\Bundle\DataHubBundle\Model; +use GraphQL\Language\AST\FieldNode; +use GraphQL\Type\Definition\ResolveInfo; use Pimcore\Model\DataObject; use Pimcore\Model\DataObject\ClassDefinition; use Pimcore\Model\Element\ElementInterface; @@ -57,4 +59,22 @@ public function getId(); * @return \Pimcore\Model\Element\ElementInterface|null */ public function getOriginalObject(): ElementInterface | null; + + /** + * Set the GraphQL context. + * + * This can e.g. allow to react on operation arguments and select the + * appropriate pre-processed data from a mock object. + * + * @param string|null $getter + * @param array|null $getterArgs + * @param \GraphQL\Type\Definition\ResolveInfo|null $resolveInfo + * @param FieldNode $ast + */ + public function setGraphQLContext( + ?string $getter, + ?array $getterArgs = null, + ?ResolveInfo $resolveInfo = null, + ?FieldNode $ast = null + ): void; } From f5fd99bc0ad409335cf59bcb8778c0293a85d965 Mon Sep 17 00:00:00 2001 From: das-peter Date: Fri, 16 Aug 2024 14:38:29 +0000 Subject: [PATCH 14/23] Apply php-cs-fixer changes --- src/GraphQL/Service.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/GraphQL/Service.php b/src/GraphQL/Service.php index 67b5fb42f..6f09af67c 100644 --- a/src/GraphQL/Service.php +++ b/src/GraphQL/Service.php @@ -1077,9 +1077,9 @@ public static function resolveValue(BaseDescriptor $descriptor, Data $fieldDefin } elseif (static::checkContainerMethodExists($container, $getter)) { $isLocalizedField = self::isLocalizedField($container, $fieldDefinition->getName()); if ($isLocalizedField) { - $result = Service::callContainerGetterMethod($container, $getter, ['language' => $args['language'] ?? null]); + $result = self::callContainerGetterMethod($container, $getter, ['language' => $args['language'] ?? null]); } else { - $result = Service::callContainerGetterMethod($container, $getter); + $result = self::callContainerGetterMethod($container, $getter); } } @@ -1333,6 +1333,7 @@ public static function callContainerGetterMethod( $container->setGraphQLContext(null); } } + return $return; } } From 364a2477b4cbdfe04394820cf326233f67d1ade8 Mon Sep 17 00:00:00 2001 From: Peter Philipp Date: Fri, 16 Aug 2024 17:36:12 +0200 Subject: [PATCH 15/23] chore: More work on ElementMockup handling. Alias has to be used to resolve pre-processed data otherwise we might miss multiple call signatures of the same operation. --- src/GraphQL/FieldHelper/AssetFieldHelper.php | 19 +---------- .../FieldHelper/DataObjectFieldHelper.php | 17 ++-------- src/GraphQL/Resolver/AssetType.php | 33 ++++++++++++++----- src/GraphQL/Service.php | 25 ++++++++++++++ 4 files changed, 53 insertions(+), 41 deletions(-) diff --git a/src/GraphQL/FieldHelper/AssetFieldHelper.php b/src/GraphQL/FieldHelper/AssetFieldHelper.php index 4b1e0fa83..d8ed92593 100644 --- a/src/GraphQL/FieldHelper/AssetFieldHelper.php +++ b/src/GraphQL/FieldHelper/AssetFieldHelper.php @@ -129,24 +129,7 @@ public function doExtractData(FieldNode $ast, &$data, $container, $args, $contex } } } else { - if (Service::checkContainerMethodExists($container, $getter)) { - if ($languageArgument) { - if ($ast->alias) { - // defer it - $data[$realName] = function ($source, $args, $context, ResolveInfo $info) use ( - $container, - $getter, - $ast - ) { - return Service::callContainerGetterMethod($container, $getter, ['language' => $args['language'] ?? null], $info, $ast); - }; - } else { - $data[$realName] = Service::callContainerGetterMethod($container, $getter, ['language' => $languageArgument], $resolveInfo, $ast); - } - } else { - $data[$realName] = Service::callContainerGetterMethod($container, $getter, [], $resolveInfo, $ast); - } - } + Service::resolveContainerGetterData($container, $data, $getter, $resolveInfo, $ast, $languageArgument); } } } diff --git a/src/GraphQL/FieldHelper/DataObjectFieldHelper.php b/src/GraphQL/FieldHelper/DataObjectFieldHelper.php index e46dbc5f2..07ad69531 100644 --- a/src/GraphQL/FieldHelper/DataObjectFieldHelper.php +++ b/src/GraphQL/FieldHelper/DataObjectFieldHelper.php @@ -373,21 +373,8 @@ public function doExtractData(FieldNode $ast, &$data, $container, $args, $contex // throw new MySafeException("fieldhelper", "TBD customized error message"); $getter = 'get' . ucfirst($astName); - if (Service::checkContainerMethodExists($container, $getter)) { - $isLocalizedField = Service::isLocalizedField($container, $astName); - if ($isLocalizedField) { - // defer it - $data[$astName] = function ($source, $args, $context, ResolveInfo $info) use ( - $container, - $getter, - $ast - ) { - return Service::callContainerGetterMethod($container, $getter, ['language' => $args['language'] ?? null], $info, $ast); - }; - } else { - $data[$astName] = Service::callContainerGetterMethod($container, $getter, [], $resolveInfo, $ast); - } - } + $isLocalizedField = Service::isLocalizedField($container, $astName); + Service::resolveContainerGetterData($container, $data, $getter, $resolveInfo, $ast, null, $isLocalizedField); } /** diff --git a/src/GraphQL/Resolver/AssetType.php b/src/GraphQL/Resolver/AssetType.php index 7d9d9c20b..bc3613847 100644 --- a/src/GraphQL/Resolver/AssetType.php +++ b/src/GraphQL/Resolver/AssetType.php @@ -143,9 +143,14 @@ public function resolvePath($value = null, $args = [], $context = [], ResolveInf if ($value instanceof BaseDescriptor) { $asset = $this->getAssetFromValue($value, $context); // Check if the value was already resolved in a mockup object. - if ($asset instanceof ElementMockupInterface && isset($value[$resolveInfo->fieldName])) { - return $value[$resolveInfo->fieldName]; + $returnName = $resolveInfo->fieldNodes[0]?->alias?->value ?? $resolveInfo->fieldName; + if ($asset instanceof ElementMockupInterface) { + if (isset($value[$returnName])) { + return $value[$returnName]; + } + $asset = $asset->getOriginalObject(); } + $thumbNailConfig = $args['thumbnail'] ?? null; $thumbNailFormat = $args['format'] ?? null; $assetFieldHelper = $this->getGraphQLService()->getAssetFieldHelper(); @@ -263,8 +268,12 @@ public function resolveResolutions($value = null, $args = [], $context = [], Res $deferredThumbnail = true; } // Check if the value was already resolved in a mockup object. - if ($thumbnail instanceof ElementMockupInterface && isset($value[$resolveInfo->fieldName])) { - return $value[$resolveInfo->fieldName]; + $returnName = $resolveInfo->fieldNodes[0]?->alias?->value ?? $resolveInfo->fieldName; + if ($thumbnail instanceof ElementMockupInterface) { + if (isset($value[$returnName])) { + return $value[$returnName]; + } + $thumbnail = $thumbnail->getOriginalObject(); } if ($thumbnail instanceof Asset\Image\Thumbnail) { @@ -302,8 +311,12 @@ public function resolveResolutions($value = null, $args = [], $context = [], Res return []; } // Check if the value was already resolved in a mockup object. - if ($asset instanceof ElementMockupInterface && isset($value[$resolveInfo->fieldName])) { - return $value[$resolveInfo->fieldName]; + $returnName = $resolveInfo->fieldNodes[0]?->alias?->value ?? $resolveInfo->fieldName; + if ($asset instanceof ElementMockupInterface) { + if (isset($value[$returnName])) { + return $value[$returnName]; + } + $asset = $asset->getOriginalObject(); } $thumbnail = $assetFieldHelper->getAssetThumbnail($asset, $thumbnailName, $thumbnailFormat, $deferredThumbnail); if (isset($thumbnail)) { @@ -345,8 +358,12 @@ public function resolveDimensions($value = null, $args = [], $context = [], Reso $thumbnailName = $args['thumbnail'] ?? null; $asset = $this->getAssetFromValue($value, $context); // Check if the value was already resolved in a mockup object. - if ($asset instanceof ElementMockupInterface && isset($value[$resolveInfo->fieldName])) { - return $value[$resolveInfo->fieldName]; + $returnName = $resolveInfo->fieldNodes[0]?->alias?->value ?? $resolveInfo->fieldName; + if ($asset instanceof ElementMockupInterface) { + if (isset($value[$returnName])) { + return $value[$returnName]; + } + $asset = $asset->getOriginalObject(); } if ($asset instanceof Asset\Video) { diff --git a/src/GraphQL/Service.php b/src/GraphQL/Service.php index 6f09af67c..608eda51d 100644 --- a/src/GraphQL/Service.php +++ b/src/GraphQL/Service.php @@ -1313,6 +1313,7 @@ public static function getContainerClassDefinition(object $container): ClassDefi * @param string $getter * @param array $getterArgs * @param \GraphQL\Type\Definition\ResolveInfo|null $resolveInfo + * @param \GraphQL\Language\AST\FieldNode|null $ast * * @return mixed */ @@ -1336,4 +1337,28 @@ public static function callContainerGetterMethod( return $return; } + + public static function resolveContainerGetterData($container, &$data, $getter, ResolveInfo $resolveInfo, FieldNode $ast, $languageArgument = null , $defer = null) + { + if (static::checkContainerMethodExists($container, $getter)) { + $realName = $ast->name->value; + $outputName = $ast->alias?->value ?? $realName; + if ($languageArgument) { + if ($ast->alias || $defer) { + // defer it + $data[$realName] = function ($source, $args, $context, ResolveInfo $info) use ( + $container, + $getter, + $ast + ) { + return Service::callContainerGetterMethod($container, $getter, [$args['language'] ?? null], $info, $ast); + }; + } else { + $data[$outputName] = $data[$realName] = Service::callContainerGetterMethod($container, $getter, [$languageArgument], $resolveInfo, $ast); + } + } else { + $data[$outputName] = $data[$realName] = Service::callContainerGetterMethod($container, $getter, [], $resolveInfo, $ast); + } + } + } } From b81b48676724070fb5ba3af54915d18c2ae897a1 Mon Sep 17 00:00:00 2001 From: das-peter Date: Fri, 16 Aug 2024 15:36:59 +0000 Subject: [PATCH 16/23] Apply php-cs-fixer changes --- src/GraphQL/Service.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/GraphQL/Service.php b/src/GraphQL/Service.php index 608eda51d..53889fdd9 100644 --- a/src/GraphQL/Service.php +++ b/src/GraphQL/Service.php @@ -1338,7 +1338,7 @@ public static function callContainerGetterMethod( return $return; } - public static function resolveContainerGetterData($container, &$data, $getter, ResolveInfo $resolveInfo, FieldNode $ast, $languageArgument = null , $defer = null) + public static function resolveContainerGetterData($container, &$data, $getter, ResolveInfo $resolveInfo, FieldNode $ast, $languageArgument = null, $defer = null) { if (static::checkContainerMethodExists($container, $getter)) { $realName = $ast->name->value; @@ -1351,13 +1351,13 @@ public static function resolveContainerGetterData($container, &$data, $getter, R $getter, $ast ) { - return Service::callContainerGetterMethod($container, $getter, [$args['language'] ?? null], $info, $ast); + return self::callContainerGetterMethod($container, $getter, [$args['language'] ?? null], $info, $ast); }; } else { - $data[$outputName] = $data[$realName] = Service::callContainerGetterMethod($container, $getter, [$languageArgument], $resolveInfo, $ast); + $data[$outputName] = $data[$realName] = self::callContainerGetterMethod($container, $getter, [$languageArgument], $resolveInfo, $ast); } } else { - $data[$outputName] = $data[$realName] = Service::callContainerGetterMethod($container, $getter, [], $resolveInfo, $ast); + $data[$outputName] = $data[$realName] = self::callContainerGetterMethod($container, $getter, [], $resolveInfo, $ast); } } } From 58cc728dcc7699a7c4863707b4920654b833503c Mon Sep 17 00:00:00 2001 From: Peter Philipp Date: Mon, 19 Aug 2024 08:22:37 +0200 Subject: [PATCH 17/23] chore: Add mockup support for asset srcset --- src/GraphQL/Resolver/AssetType.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/GraphQL/Resolver/AssetType.php b/src/GraphQL/Resolver/AssetType.php index bc3613847..be6d66e95 100644 --- a/src/GraphQL/Resolver/AssetType.php +++ b/src/GraphQL/Resolver/AssetType.php @@ -212,6 +212,14 @@ public function resolveSrcSet($value = null, $args = [], $context = [], ResolveI $thumbNailConfig = $args['thumbnail'] ?? null; $thumbNailFormat = $args['format'] ?? null; $assetFieldHelper = $this->getGraphQLService()->getAssetFieldHelper(); + // Check if the value was already resolved in a mockup object. + $returnName = $resolveInfo->fieldNodes[0]?->alias?->value ?? $resolveInfo->fieldName; + if ($asset instanceof ElementMockupInterface) { + if (isset($value[$returnName])) { + return $value[$returnName]; + } + $asset = $asset->getOriginalObject(); + } if ($asset instanceof Asset\Image) { $mediaQueries = []; From bc82cc50fe6a5305abfed388f5ff202dc8d4d250 Mon Sep 17 00:00:00 2001 From: Peter Philipp Date: Mon, 19 Aug 2024 09:36:42 +0200 Subject: [PATCH 18/23] chore: Make mockup element support configurable. --- doc/10_GraphQL/README.md | 49 ++++++++++++ src/Controller/WebserviceController.php | 2 + src/DependencyInjection/Configuration.php | 1 + .../AbstractTable.php | 2 +- .../Helper/AssetBase.php | 2 +- .../Helper/Base.php | 2 +- .../Helper/Hotspotimage.php | 2 +- .../Helper/Href.php | 2 +- .../Helper/Image.php | 2 +- .../Helper/ImageGallery.php | 2 +- .../Helper/Multihref.php | 2 +- .../Helper/MultihrefMetadata.php | 2 +- .../Helper/Objects.php | 2 +- .../Helper/ObjectsMetadata.php | 2 +- src/GraphQL/DataObjectType/BlockEntryType.php | 2 +- src/GraphQL/Resolver/AssetType.php | 2 +- src/GraphQL/Resolver/Base.php | 2 +- src/GraphQL/Resolver/DataObject.php | 6 +- src/GraphQL/Resolver/Element.php | 10 +-- src/GraphQL/Service.php | 4 +- src/GraphQL/Traits/ElementLoaderTrait.php | 13 +-- src/Model/ElementMockupTrait.php | 80 +++++++++++++++++++ 22 files changed, 164 insertions(+), 29 deletions(-) create mode 100644 src/Model/ElementMockupTrait.php diff --git a/doc/10_GraphQL/README.md b/doc/10_GraphQL/README.md index 28a549b3d..8b77abe37 100644 --- a/doc/10_GraphQL/README.md +++ b/doc/10_GraphQL/README.md @@ -95,3 +95,52 @@ Open the settings and change `request.credentials` to `include`. Otherwise the `XDEBUG_SESSION` cookie header will get removed by default. ![Settings](../img/graphql/debugging.png) + +## Mockup Elements + +**!Beware!** It is a quite complex undertaking to implement this correctly, but worth it if you need a high-performance integration of product indexes. + +DataHub supports the use of Mockup Objects / Elements as used in Pimcores Ecommerce Framework Bundle IndexService. +In order to use a Mockup Class it has to implement DataHubs own interface: \Pimcore\Bundle\DataHubBundle\Model\ElementMockupInterface + +Mockup elements will be resolved differently, operators and or sub-elements are +only resolved if the requested data structure isn't already available in the mockup itself. +As such Mockup Objects have to ensure to return the same output as a non-mocked +query would. + +One particular way to do this is to implement a callback in the mockup and use +the passed in GraphQl Execution context to resolve the data, + +Example of returning Image Thumbnail Path: + +```php +class AssetMockup implements ElementMockupInterface +{ + use ElementMockupTrait; + + /** + * @return array + */ + public function getFullpath(): ?string + { + // Check if this is a graphQL callback. + $suffix = null; + if ($this->isGraphqlCallback(__FUNCTION__)) { + if ($thumbnail = $this->getGraphQLArguments()['thumbnail'] ?? null) { + $suffix = '_' . $thumbnail; + } + } + return $this->getParam('fullpath' . $suffix) ?? $this->getParam('fullpath') ?? null; + } +} +``` + +Will use the returned value, or if null is returned the original Asset is used to resolve the value. + +The mockup support can be enabled and configured with a configuration entry like this in your `config.yml` file: +```yml +#### DATAHUB MOCKUP ELEMENTS +pimcore_data_hub: + graphql: + mockup_element_support_enabled: true +``` \ No newline at end of file diff --git a/src/Controller/WebserviceController.php b/src/Controller/WebserviceController.php index 0dea72783..5b26cddfb 100644 --- a/src/Controller/WebserviceController.php +++ b/src/Controller/WebserviceController.php @@ -180,6 +180,8 @@ public function webonyxAction( if (isset($datahubConfig['graphql']) && isset($datahubConfig['graphql']['not_allowed_policy'])) { PimcoreDataHubBundle::setNotAllowedPolicy($datahubConfig['graphql']['not_allowed_policy']); } + $context['mockup_element_support_enabled'] = !empty($datahubConfig['graphql']['mockup_element_support_enabled']); + $context['mockup_element_support_enabled'] = false; $validators = null; if ($request->get('novalidate')) { diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 51983b7c1..808eeaedb 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -39,6 +39,7 @@ public function getConfigTreeBuilder() ->scalarNode('not_allowed_policy')->info('throw exception = 1, return null = 2')->defaultValue(2)->end() ->booleanNode('output_cache_enabled')->info('enables output cache for graphql responses. It is disabled by default')->defaultValue(false)->end() ->integerNode('output_cache_lifetime')->info('output cache in seconds. Default is 30 seconds')->defaultValue(30)->end() + ->booleanNode('mockup_element_support_enabled')->info('Enable support of mockup elements as used by Ecommerce Framework Bundle IndexService. You have to use your own Mockup class(es) that implement DataHubs ElementMockupInterface')->defaultValue(false)->end() ->booleanNode('allow_introspection')->info('enables introspection for graphql. It is enabled by default')->defaultValue(true)->end() ->booleanNode('run_subrequest_per_query')->info('If enabled a Symfony Sub-Requet is triggered for each query in a multi-query request.')->defaultValue(false)->end() ->end() diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/AbstractTable.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/AbstractTable.php index 597287dcc..b83c6d8f1 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/AbstractTable.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/AbstractTable.php @@ -41,7 +41,7 @@ public function getGraphQlFieldConfig($attribute, Data $fieldDefinition, $class 'name' => $fieldDefinition->getName(), 'type' => $this->getFieldType($fieldDefinition, $class, $container), 'resolve' => function ($value, $args, $context = [], ResolveInfo $resolveInfo = null) use ($fieldDefinition, $attribute) { - $result = Service::resolveValue($value, $fieldDefinition, $attribute, $args); + $result = Service::resolveValue($value, $fieldDefinition, $attribute, $args, !empty($context['mockup_element_support_enabled'])); // The table has no specific definition of columns, so we cannot have a ObjectType in schema for it. // Just return the data JSON encoded diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/AssetBase.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/AssetBase.php index 07f85fa31..781321729 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/AssetBase.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/AssetBase.php @@ -69,7 +69,7 @@ public function __construct(Service $graphQlService, $attribute, $fieldDefinitio */ public function resolve($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { - $asset = Service::resolveValue($value, $this->fieldDefinition, $this->attribute, $args = []); + $asset = Service::resolveValue($value, $this->fieldDefinition, $this->attribute, $args = [], !empty($context['mockup_element_support_enabled'])); if (!$asset) { return null; diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Base.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Base.php index ffaf51dcb..a900bbaa3 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Base.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Base.php @@ -67,7 +67,7 @@ public function __construct(Service $graphQlService, $attribute, $fieldDefinitio */ public function resolve($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { - $result = Service::resolveValue($value, $this->fieldDefinition, $this->attribute, $args); + $result = Service::resolveValue($value, $this->fieldDefinition, $this->attribute, $args, !empty($context['mockup_element_support_enabled'])); return $result; } diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Hotspotimage.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Hotspotimage.php index 76338cdf9..f9fd17396 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Hotspotimage.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Hotspotimage.php @@ -84,7 +84,7 @@ public function resolve($value = null, $args = [], $context = [], ResolveInfo $r return Service::resolveCachedValue($value, $resolveInfo); } /** @var $container Hotspotimage */ - $container = Service::resolveValue($value, $this->fieldDefinition, $this->attribute, $args); + $container = Service::resolveValue($value, $this->fieldDefinition, $this->attribute, $args, !empty($context['mockup_element_support_enabled'])); if ($container instanceof \Pimcore\Model\DataObject\Data\Hotspotimage) { $image = $container->getImage(); if ($image instanceof Asset) { diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Href.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Href.php index a04472d26..2165e7333 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Href.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Href.php @@ -73,7 +73,7 @@ public function __construct(Service $graphQlService, $attribute, $fieldDefinitio public function resolve($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { if ($value instanceof BaseDescriptor) { - $relation = \Pimcore\Bundle\DataHubBundle\GraphQL\Service::resolveValue($value, $this->fieldDefinition, $this->attribute, $args); + $relation = \Pimcore\Bundle\DataHubBundle\GraphQL\Service::resolveValue($value, $this->fieldDefinition, $this->attribute, $args, !empty($context['mockup_element_support_enabled'])); if ($relation instanceof ElementInterface || $relation instanceof ElementMockupInterface) { if (!WorkspaceHelper::checkPermission($relation, 'read')) { diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Image.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Image.php index 54234aa78..a87ed0478 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Image.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Image.php @@ -73,7 +73,7 @@ public function __construct(\Pimcore\Bundle\DataHubBundle\GraphQL\Service $graph public function resolve($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { if ($value instanceof BaseDescriptor) { - $relation = Service::resolveValue($value, $this->fieldDefinition, $this->attribute, $args); + $relation = Service::resolveValue($value, $this->fieldDefinition, $this->attribute, $args, !empty($context['mockup_element_support_enabled'])); if ($relation instanceof Asset || $relation instanceof ElementMockupInterface) { if (!WorkspaceHelper::checkPermission($relation, 'read')) { diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/ImageGallery.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/ImageGallery.php index 49c6b3a2b..29d394d26 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/ImageGallery.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/ImageGallery.php @@ -84,7 +84,7 @@ public function __construct( public function resolve($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { $result = []; - $relations = GraphQlService::resolveValue($value, $this->fieldDefinition, $this->attribute, $args); + $relations = GraphQlService::resolveValue($value, $this->fieldDefinition, $this->attribute, $args, !empty($context['mockup_element_support_enabled'])); if ($relations) { foreach ($relations as $relation) { if ($relation instanceof Hotspotimage) { diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Multihref.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Multihref.php index f46624705..b1849774d 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Multihref.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Multihref.php @@ -70,7 +70,7 @@ public function __construct(Service $graphQlService, $attribute, $fieldDefinitio public function resolve($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { $result = []; - $relations = \Pimcore\Bundle\DataHubBundle\GraphQL\Service::resolveValue($value, $this->fieldDefinition, $this->attribute, $args); + $relations = \Pimcore\Bundle\DataHubBundle\GraphQL\Service::resolveValue($value, $this->fieldDefinition, $this->attribute, $args, !empty($context['mockup_element_support_enabled'])); if ($relations) { /** @var AbstractElement $relation */ foreach ($relations as $relation) { diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/MultihrefMetadata.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/MultihrefMetadata.php index 590a0a54e..6b26fb092 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/MultihrefMetadata.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/MultihrefMetadata.php @@ -70,7 +70,7 @@ public function __construct(Service $graphQlService, $attribute, $fieldDefinitio */ public function resolve($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { - $relations = \Pimcore\Bundle\DataHubBundle\GraphQL\Service::resolveValue($value, $this->fieldDefinition, $this->attribute, $args); + $relations = \Pimcore\Bundle\DataHubBundle\GraphQL\Service::resolveValue($value, $this->fieldDefinition, $this->attribute, $args, !empty($context['mockup_element_support_enabled'])); if ($relations) { $result = []; /** @var ElementMetadata $relation */ diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Objects.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Objects.php index 0b6735ea1..68038e0e8 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Objects.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/Objects.php @@ -73,7 +73,7 @@ public function __construct(Service $graphQlService, $attribute, $fieldDefinitio public function resolve($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { if ($value instanceof BaseDescriptor) { - $relations = Service::resolveValue($value, $this->fieldDefinition, $this->attribute, $args); + $relations = Service::resolveValue($value, $this->fieldDefinition, $this->attribute, $args, !empty($context['mockup_element_support_enabled'])); if ($relations) { $result = []; foreach ($relations as $relation) { diff --git a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/ObjectsMetadata.php b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/ObjectsMetadata.php index 1ac5b36b2..3fd59c2ea 100644 --- a/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/ObjectsMetadata.php +++ b/src/GraphQL/DataObjectQueryFieldConfigGenerator/Helper/ObjectsMetadata.php @@ -70,7 +70,7 @@ public function __construct(Service $graphQlService, $attribute, $fieldDefinitio public function resolve($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { $result = []; - $relations = \Pimcore\Bundle\DataHubBundle\GraphQL\Service::resolveValue($value, $this->fieldDefinition, $this->attribute, $args); + $relations = \Pimcore\Bundle\DataHubBundle\GraphQL\Service::resolveValue($value, $this->fieldDefinition, $this->attribute, $args, !empty($context['mockup_element_support_enabled'])); if ($relations) { /** @var ObjectMetadata $relation */ foreach ($relations as $relation) { diff --git a/src/GraphQL/DataObjectType/BlockEntryType.php b/src/GraphQL/DataObjectType/BlockEntryType.php index 5dd997e51..da92f0bdd 100644 --- a/src/GraphQL/DataObjectType/BlockEntryType.php +++ b/src/GraphQL/DataObjectType/BlockEntryType.php @@ -158,7 +158,7 @@ protected function prepareField(Data $fieldDef, bool $localized = false) return $resolve($value, $args, $context, $resolveInfo); } - return $this->graphQlService::resolveValue($value, $this->fieldDefinition, $this->fieldDefinition->getName(), $args); + return $this->graphQlService::resolveValue($value, $this->fieldDefinition, $this->fieldDefinition->getName(), $args, !empty($context['mockup_element_support_enabled'])); }; return $field; diff --git a/src/GraphQL/Resolver/AssetType.php b/src/GraphQL/Resolver/AssetType.php index be6d66e95..8fdb6b05e 100644 --- a/src/GraphQL/Resolver/AssetType.php +++ b/src/GraphQL/Resolver/AssetType.php @@ -453,7 +453,7 @@ protected function getAssetFromValue($value, $context) if (!$value instanceof ElementDescriptor) { return null; } - $asset = $this->loadDataElement($value, 'asset'); + $asset = $this->loadDataElement($value, 'asset', !empty($context['mockup_element_support_enabled'])); if (!WorkspaceHelper::checkPermission($asset, 'read')) { return null; diff --git a/src/GraphQL/Resolver/Base.php b/src/GraphQL/Resolver/Base.php index af3215d10..ef602dce7 100644 --- a/src/GraphQL/Resolver/Base.php +++ b/src/GraphQL/Resolver/Base.php @@ -87,7 +87,7 @@ public function resolve($value = null, $args = [], $context = [], ResolveInfo $r /** @var \Pimcore\Bundle\DataHubBundle\GraphQL\Query\Operator\AbstractOperator $operatorImpl */ $operatorImpl = $this->getGraphQlService()->buildQueryOperator($this->typeName, $this->attributes); - $element = $this->loadDataElement($value, 'object'); + $element = $this->loadDataElement($value, 'object', !empty($context['mockup_element_support_enabled'])); $valueFromOperator = $operatorImpl->getLabeledValue($element, $resolveInfo); $value = $valueFromOperator->value ?? null; diff --git a/src/GraphQL/Resolver/DataObject.php b/src/GraphQL/Resolver/DataObject.php index e3126c82d..65074ce00 100644 --- a/src/GraphQL/Resolver/DataObject.php +++ b/src/GraphQL/Resolver/DataObject.php @@ -42,7 +42,7 @@ public function __construct(Service $graphQlService) */ public function resolveTag($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { - $object = $this->loadDataElement($value, 'object'); + $object = $this->loadDataElement($value, 'object', !empty($context['mockup_element_support_enabled'])); if ($object) { $result = $this->getTags('object', $object->getId()); @@ -68,7 +68,7 @@ public function resolveIndex($value = null, $args = [], $context = [], ResolveIn return null; } - $object = $this->loadDataElement($value, 'object'); + $object = $this->loadDataElement($value, 'object', !empty($context['mockup_element_support_enabled'])); if (!$object instanceof AbstractObject) { return null; @@ -91,7 +91,7 @@ public function resolveChildrenSortBy($value = null, $args = [], $context = [], return null; } - $object = $this->loadDataElement($value, 'object'); + $object = $this->loadDataElement($value, 'object', !empty($context['mockup_element_support_enabled'])); if (!$object instanceof \Pimcore\Model\DataObject) { return null; diff --git a/src/GraphQL/Resolver/Element.php b/src/GraphQL/Resolver/Element.php index a843cb7b3..50fbc2624 100644 --- a/src/GraphQL/Resolver/Element.php +++ b/src/GraphQL/Resolver/Element.php @@ -57,7 +57,7 @@ public function __construct(string $elementType, Service $graphQlService) */ public function resolveTag($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { - $element = $this->loadDataElement($value, $this->elementType); + $element = $this->loadDataElement($value, $this->elementType, !empty($context['mockup_element_support_enabled'])); if ($element) { $result = $this->getTags('document', $element->getId()); @@ -82,7 +82,7 @@ public function resolveTag($value = null, $args = [], $context = [], ResolveInfo public function resolveProperties($value = null, array $args = [], array $context = [], ResolveInfo $resolveInfo = null) { $elementId = $value['id']; - $element = $this->loadDataElement($value, $this->elementType); + $element = $this->loadDataElement($value, $this->elementType, !empty($context['mockup_element_support_enabled'])); if (!$element) { throw new ClientSafeException('element ' . $this->elementType . ' ' . $elementId . ' not found'); @@ -116,7 +116,7 @@ public function resolveProperties($value = null, array $args = [], array $contex */ public function resolveParent($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { - $element = $this->loadDataElement($value, $this->elementType); + $element = $this->loadDataElement($value, $this->elementType, !empty($context['mockup_element_support_enabled'])); if ($element) { $parent = $element->getParent(); if ($parent) { @@ -139,7 +139,7 @@ public function resolveParent($value = null, $args = [], $context = [], ResolveI */ public function resolveChildren($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { - $element = $this->loadDataElement($value, $this->elementType); + $element = $this->loadDataElement($value, $this->elementType, !empty($context['mockup_element_support_enabled'])); if ($element) { $arguments = $this->composeArguments($args); @@ -162,7 +162,7 @@ public function resolveChildren($value = null, $args = [], $context = [], Resolv */ public function resolveSiblings($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { - $element = $this->loadDataElement($value, $this->elementType); + $element = $this->loadDataElement($value, $this->elementType, !empty($context['mockup_element_support_enabled'])); if ($element) { $arguments = $this->composeArguments($args); diff --git a/src/GraphQL/Service.php b/src/GraphQL/Service.php index 53889fdd9..7507248a1 100644 --- a/src/GraphQL/Service.php +++ b/src/GraphQL/Service.php @@ -942,10 +942,10 @@ public static function setValue($object, $attribute, $callback) * * @return mixed */ - public static function resolveValue(BaseDescriptor $descriptor, Data $fieldDefinition, $attribute, $args = []) + public static function resolveValue(BaseDescriptor $descriptor, Data $fieldDefinition, $attribute, $args = [], bool $mockupElementSupport = false) { $getter = 'get' . ucfirst($fieldDefinition->getName()); - $object = self::staticLoadDataElement($descriptor, 'object'); + $object = self::staticLoadDataElement($descriptor, 'object', $mockupElementSupport); if (!$object) { return null; } diff --git a/src/GraphQL/Traits/ElementLoaderTrait.php b/src/GraphQL/Traits/ElementLoaderTrait.php index 1cfa00101..9710cfb77 100644 --- a/src/GraphQL/Traits/ElementLoaderTrait.php +++ b/src/GraphQL/Traits/ElementLoaderTrait.php @@ -46,18 +46,21 @@ protected function setDataElement($data, ElementInterface | ElementMockupInterfa * * @return ElementInterface */ - protected function loadDataElement(&$data, $type) + protected function loadDataElement(&$data, $type, bool $mockupElementSupport = false) { - return self::staticLoadDataElement($data, $type); + return self::staticLoadDataElement($data, $type, $mockupElementSupport); } /** - * * @return ElementInterface */ - protected static function staticLoadDataElement(&$data, $type) + protected static function staticLoadDataElement(&$data, $type, bool $mockupElementSupport = false) { - if (!isset($data[ElementInterface::class . '_instance'])) { + // If mockup element support is disabled always load the object initially. + if ( + !isset($data[ElementInterface::class . '_instance']) + || (!$mockupElementSupport && $data[ElementInterface::class . '_instance'] instanceof ElementMockupInterface) + ) { $data[ElementInterface::class . '_type'] = $type; $data[ElementInterface::class . '_instance'] = ElementService::getElementById($type, $data['id']); } diff --git a/src/Model/ElementMockupTrait.php b/src/Model/ElementMockupTrait.php new file mode 100644 index 000000000..a1ed9c410 --- /dev/null +++ b/src/Model/ElementMockupTrait.php @@ -0,0 +1,80 @@ +getParam('elementType') ?? 'object'; + } + + /** + * @var array + */ + protected array $graphQLContext = []; + + public function setGraphQLContext( + ?string $getter, + ?array $getterArgs = null, + ?ResolveInfo $resolveInfo = null, + ?FieldNode $ast = null + ): void { + $this->graphQLContext = [ + 'getter' => $getter, + 'getterArgs' => $getterArgs, + 'resolveInfo' => $resolveInfo, + 'ast' => $ast, + ]; + } + + protected function isGraphQLCallback($function): bool + { + return ($this->graphQLContext['getter'] ?? null) === $function; + } + + /** + * @return array + */ + protected function getGraphQLArguments(): array + { + $result = []; + if ($ast = $this->graphQLContext['ast'] ?? null) { + if ($nodeList = $ast?->arguments) { + $count = $nodeList->count(); + for ($i = 0; $i < $count; $i++) { + /** @var ArgumentNode $argumentNode */ + $argumentNode = $nodeList[$i]; + $value = $argumentNode->value->kind === 'ListValue' ? $argumentNode->value->values : $argumentNode->value->value; + $result[$argumentNode->name->value] = $value; + } + } + } + + return $result; + } +} From d15216feb6f51520e2005ecee1df86f92357354b Mon Sep 17 00:00:00 2001 From: Peter Philipp Date: Mon, 19 Aug 2024 09:38:00 +0200 Subject: [PATCH 19/23] fix: Remove development artifact. --- src/Controller/WebserviceController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Controller/WebserviceController.php b/src/Controller/WebserviceController.php index 5b26cddfb..ef5096127 100644 --- a/src/Controller/WebserviceController.php +++ b/src/Controller/WebserviceController.php @@ -181,7 +181,6 @@ public function webonyxAction( PimcoreDataHubBundle::setNotAllowedPolicy($datahubConfig['graphql']['not_allowed_policy']); } $context['mockup_element_support_enabled'] = !empty($datahubConfig['graphql']['mockup_element_support_enabled']); - $context['mockup_element_support_enabled'] = false; $validators = null; if ($request->get('novalidate')) { From fba37371108f5d645f84b6c0ca41d984cd9c90a8 Mon Sep 17 00:00:00 2001 From: Peter Philipp Date: Mon, 19 Aug 2024 14:05:22 +0200 Subject: [PATCH 20/23] fix: Remove development code. --- src/Controller/WebserviceController.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Controller/WebserviceController.php b/src/Controller/WebserviceController.php index ef5096127..e78dede06 100644 --- a/src/Controller/WebserviceController.php +++ b/src/Controller/WebserviceController.php @@ -110,10 +110,6 @@ protected function getCachingContextConfiguration(Request $request): array $config['resolveObjectGetter'] = !$request->query->has('datahub-cache-disable-resolveObjectGetter'); } - $config['operations'] = false; - $config['resolveEdge'] = false; - $config['resolveObjectGetter'] = false; - return $config; } From d5787b3e3258426d8c07c25adc20239e1016087c Mon Sep 17 00:00:00 2001 From: Peter Philipp Date: Tue, 20 Aug 2024 08:53:01 +0200 Subject: [PATCH 21/23] chore: Add static cache to WorkspaceHelper::checkPermission. There doesn't seem a real need to check the same element over and over again in the same request. --- doc/10_GraphQL/README.md | 13 ++++++++ src/Controller/WebserviceController.php | 1 + src/DependencyInjection/Configuration.php | 1 + src/WorkspaceHelper.php | 36 ++++++++++++++++------- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/doc/10_GraphQL/README.md b/doc/10_GraphQL/README.md index 8b77abe37..9edfa4258 100644 --- a/doc/10_GraphQL/README.md +++ b/doc/10_GraphQL/README.md @@ -143,4 +143,17 @@ The mockup support can be enabled and configured with a configuration entry like pimcore_data_hub: graphql: mockup_element_support_enabled: true +``` + +## Permission Check Cache + +By default only a single permission check per request is executed. +This is different from previous versions in which the event +`pimcore.datahub.graphql.permission.preCheck` was fired multiple times during the execution. + +The new approach limits the performance impact of the permission handling - but can be disabled using following setting: +```yml +pimcore_data_hub: + graphql: + disable_permission_check_cache: true ``` \ No newline at end of file diff --git a/src/Controller/WebserviceController.php b/src/Controller/WebserviceController.php index e78dede06..5c49633ac 100644 --- a/src/Controller/WebserviceController.php +++ b/src/Controller/WebserviceController.php @@ -176,6 +176,7 @@ public function webonyxAction( if (isset($datahubConfig['graphql']) && isset($datahubConfig['graphql']['not_allowed_policy'])) { PimcoreDataHubBundle::setNotAllowedPolicy($datahubConfig['graphql']['not_allowed_policy']); } + $context['disable_permission_check_cache'] = !empty($datahubConfig['graphql']['disable_permission_check_cache']); $context['mockup_element_support_enabled'] = !empty($datahubConfig['graphql']['mockup_element_support_enabled']); $validators = null; diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 808eeaedb..0636d38d5 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -40,6 +40,7 @@ public function getConfigTreeBuilder() ->booleanNode('output_cache_enabled')->info('enables output cache for graphql responses. It is disabled by default')->defaultValue(false)->end() ->integerNode('output_cache_lifetime')->info('output cache in seconds. Default is 30 seconds')->defaultValue(30)->end() ->booleanNode('mockup_element_support_enabled')->info('Enable support of mockup elements as used by Ecommerce Framework Bundle IndexService. You have to use your own Mockup class(es) that implement DataHubs ElementMockupInterface')->defaultValue(false)->end() + ->booleanNode('disable_permission_check_cache')->info('Disables the permission check static cache. Use only if you have very specific cases in which the permission can change _within_ the same request!')->defaultValue(false)->end() ->booleanNode('allow_introspection')->info('enables introspection for graphql. It is enabled by default')->defaultValue(true)->end() ->booleanNode('run_subrequest_per_query')->info('If enabled a Symfony Sub-Requet is triggered for each query in a multi-query request.')->defaultValue(false)->end() ->end() diff --git a/src/WorkspaceHelper.php b/src/WorkspaceHelper.php index 46a343a73..a1ccd36fe 100644 --- a/src/WorkspaceHelper.php +++ b/src/WorkspaceHelper.php @@ -226,7 +226,7 @@ public static function deleteConfiguration(Configuration $config) * * @throws NotAllowedException */ - public static function checkPermission($element, $type, string $elementType = '') + public static function checkPermission($element, $type, string $elementType = '', bool $skipCache = false) { $context = RuntimeCache::get('datahub_context'); /** @var Configuration $configuration */ @@ -235,7 +235,26 @@ public static function checkPermission($element, $type, string $elementType = '' if ($configuration->skipPermisssionCheck()) { return true; } - + // This can be called multiple times with the same element - use static + // cache to avoid multiple execution in favor of performance. + // @TODO Is there really a reason for the configuration? + if (!$elementType) { + if ($element instanceof ElementMockupInterface) { + $elementType = $element->getElementType(); + } else { + $elementType = Service::getElementType($element); + } + } + $cid = $element->getId() . ':' . $type . ':' . $elementType ; + if (!$skipCache && empty($context['disable_permission_check_cache'])) { + if (!RuntimeCache::isRegistered(__METHOD__)) { + RuntimeCache::set(__METHOD__, []); + } + $permissionCache = RuntimeCache::get(__METHOD__); + if (isset($permissionCache[$cid])) { + return $permissionCache[$cid]; + } + } $event = new PermissionEvent($element, $type); /** @var EventDispatcher $eventDispatcher */ $eventDispatcher = \Pimcore::getContainer()->get('event_dispatcher'); @@ -245,18 +264,15 @@ public static function checkPermission($element, $type, string $elementType = '' } // we can allow nullable elements e.g. linked Assets where the asset itself was removed if (!$element) { - return true; + $permissionCache[$cid] = true; + RuntimeCache::set(__METHOD__, $permissionCache); + return $permissionCache[$cid]; } $isAllowed = self::isAllowed($element, $configuration, $type, $elementType); + $permissionCache[$cid] = $isAllowed; + RuntimeCache::set(__METHOD__, $permissionCache); if (!$isAllowed && PimcoreDataHubBundle::getNotAllowedPolicy() === PimcoreDataHubBundle::NOT_ALLOWED_POLICY_EXCEPTION) { - if (!$elementType) { - if ($element instanceof ElementMockupInterface) { - $elementType = $element->getElementType(); - } else { - $elementType = Service::getElementType($element); - } - } // Could be dealing with mock objects that can't be loaded due // to stale index so be extra cautions when using. try { From 1737edabd25b8dcd0a5aebe2b6e754f5143f575b Mon Sep 17 00:00:00 2001 From: das-peter Date: Tue, 20 Aug 2024 06:53:29 +0000 Subject: [PATCH 22/23] Apply php-cs-fixer changes --- src/WorkspaceHelper.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/WorkspaceHelper.php b/src/WorkspaceHelper.php index a1ccd36fe..82b6f7681 100644 --- a/src/WorkspaceHelper.php +++ b/src/WorkspaceHelper.php @@ -266,6 +266,7 @@ public static function checkPermission($element, $type, string $elementType = '' if (!$element) { $permissionCache[$cid] = true; RuntimeCache::set(__METHOD__, $permissionCache); + return $permissionCache[$cid]; } From d0dcde1b8157f9ff005f932ce7bdc2998004c5b3 Mon Sep 17 00:00:00 2001 From: Peter Philipp Date: Fri, 23 Aug 2024 15:34:29 +0200 Subject: [PATCH 23/23] chore: FilterTotalCount should be resolver is now deferred to optimize call flow - in the best case this saves an entire call to the search index. --- src/GraphQL/Resolver/QueryType.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/GraphQL/Resolver/QueryType.php b/src/GraphQL/Resolver/QueryType.php index ef9ca6e37..ef559c6b4 100644 --- a/src/GraphQL/Resolver/QueryType.php +++ b/src/GraphQL/Resolver/QueryType.php @@ -1047,7 +1047,13 @@ public function resolveFilter($value = null, $args = [], $context = [], ResolveI */ public function resolveFilterTotalCount($value = null, $args = [], $context = [], ResolveInfo $resolveInfo = null) { - return $value['totalCount'](); + // Run this deferred because it is very likely that the edges are + // resolved "later" which means at this point the totalCount resolved + // will be able to re-use the results loaded by them. This can save a + // complete call to the search index. + return new Deferred(function () use ($value) { + return $value['totalCount'](); + }); } /**