vendor/shopware/core/Framework/DataAbstractionLayer/Dbal/EntityDefinitionQueryHelper.php line 528

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Framework\DataAbstractionLayer\Dbal;
  3. use Doctrine\DBAL\Connection;
  4. use Shopware\Core\Defaults;
  5. use Shopware\Core\Framework\Context;
  6. use Shopware\Core\Framework\DataAbstractionLayer\Dbal\Exception\FieldAccessorBuilderNotFoundException;
  7. use Shopware\Core\Framework\DataAbstractionLayer\Dbal\Exception\UnmappedFieldException;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Dbal\FieldResolver\FieldResolverContext;
  9. use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Field\AssociationField;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Field\Field;
  12. use Shopware\Core\Framework\DataAbstractionLayer\Field\FkField;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Inherited;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\PrimaryKey;
  15. use Shopware\Core\Framework\DataAbstractionLayer\Field\IdField;
  16. use Shopware\Core\Framework\DataAbstractionLayer\Field\ManyToManyAssociationField;
  17. use Shopware\Core\Framework\DataAbstractionLayer\Field\ReferenceVersionField;
  18. use Shopware\Core\Framework\DataAbstractionLayer\Field\StorageAware;
  19. use Shopware\Core\Framework\DataAbstractionLayer\Field\TranslatedField;
  20. use Shopware\Core\Framework\DataAbstractionLayer\Field\VersionField;
  21. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  22. use Shopware\Core\Framework\DataAbstractionLayer\Search\CriteriaPartInterface;
  23. use Shopware\Core\Framework\Uuid\Uuid;
  24. /**
  25.  * This class acts only as helper/common class for all dbal operations for entity definitions.
  26.  * It knows how an association should be joined, how a parent-child inheritance should act, how translation chains work, ...
  27.  *
  28.  * @deprecated tag:v6.5.0 - reason:becomes-internal - Will be internal
  29.  */
  30. class EntityDefinitionQueryHelper
  31. {
  32.     public const HAS_TO_MANY_JOIN 'has_to_many_join';
  33.     public static function escape(string $string): string
  34.     {
  35.         if (mb_strpos($string'`') !== false) {
  36.             throw new \InvalidArgumentException('Backtick not allowed in identifier');
  37.         }
  38.         return '`' $string '`';
  39.     }
  40.     public static function columnExists(Connection $connectionstring $tablestring $column): bool
  41.     {
  42.         $exists $connection->fetchOne(
  43.             'SHOW COLUMNS FROM ' self::escape($table) . ' WHERE `Field` LIKE :column',
  44.             ['column' => $column]
  45.         );
  46.         return !empty($exists);
  47.     }
  48.     /**
  49.      * @return list<Field>
  50.      */
  51.     public static function getFieldsOfAccessor(EntityDefinition $definitionstring $accessorbool $resolveTranslated true): array
  52.     {
  53.         $parts explode('.'$accessor);
  54.         if ($definition->getEntityName() === $parts[0]) {
  55.             array_shift($parts);
  56.         }
  57.         $accessorFields = [];
  58.         $source $definition;
  59.         foreach ($parts as $part) {
  60.             if ($part === 'extensions') {
  61.                 continue;
  62.             }
  63.             $fields $source->getFields();
  64.             $field $fields->get($part);
  65.             // continue if the current part is not a real field to allow access on collections
  66.             if (!$field) {
  67.                 continue;
  68.             }
  69.             if ($field instanceof TranslatedField && $resolveTranslated) {
  70.                 /** @var EntityDefinition $source */
  71.                 $source $source->getTranslationDefinition();
  72.                 $fields $source->getFields();
  73.                 $accessorFields[] = $fields->get($part);
  74.                 continue;
  75.             }
  76.             if ($field instanceof TranslatedField && !$resolveTranslated) {
  77.                 $accessorFields[] = $field;
  78.                 continue;
  79.             }
  80.             $accessorFields[] = $field;
  81.             if (!$field instanceof AssociationField) {
  82.                 break;
  83.             }
  84.             $source $field->getReferenceDefinition();
  85.             if ($field instanceof ManyToManyAssociationField) {
  86.                 $source $field->getToManyReferenceDefinition();
  87.             }
  88.         }
  89.         return array_filter($accessorFields);
  90.     }
  91.     /**
  92.      * Returns the field instance of the provided fieldName.
  93.      *
  94.      * @example
  95.      *
  96.      * fieldName => 'product.name'
  97.      * Returns the (new TranslatedField('name')) declaration
  98.      *
  99.      * Allows additionally nested referencing
  100.      *
  101.      * fieldName => 'category.products.name'
  102.      * Returns as well the above field definition
  103.      */
  104.     public function getField(string $fieldNameEntityDefinition $definitionstring $rootbool $resolveTranslated true): ?Field
  105.     {
  106.         $original $fieldName;
  107.         $prefix $root '.';
  108.         if (mb_strpos($fieldName$prefix) === 0) {
  109.             $fieldName mb_substr($fieldNamemb_strlen($prefix));
  110.         } else {
  111.             $original $prefix $original;
  112.         }
  113.         $fields $definition->getFields();
  114.         $isAssociation mb_strpos($fieldName'.') !== false;
  115.         if (!$isAssociation && $fields->has($fieldName)) {
  116.             return $fields->get($fieldName);
  117.         }
  118.         $associationKey explode('.'$fieldName);
  119.         $associationKey array_shift($associationKey);
  120.         $field $fields->get($associationKey);
  121.         if ($field instanceof TranslatedField && $resolveTranslated) {
  122.             return self::getTranslatedField($definition$field);
  123.         }
  124.         if ($field instanceof TranslatedField) {
  125.             return $field;
  126.         }
  127.         if (!$field instanceof AssociationField) {
  128.             return $field;
  129.         }
  130.         $referenceDefinition $field->getReferenceDefinition();
  131.         if ($field instanceof ManyToManyAssociationField) {
  132.             $referenceDefinition $field->getToManyReferenceDefinition();
  133.         }
  134.         return $this->getField(
  135.             $original,
  136.             $referenceDefinition,
  137.             $root '.' $field->getPropertyName()
  138.         );
  139.     }
  140.     /**
  141.      * Builds the sql field accessor for the provided field.
  142.      *
  143.      * @example
  144.      *
  145.      * fieldName => product.taxId
  146.      * root      => product
  147.      * returns   => `product`.`tax_id`
  148.      *
  149.      * This function is also used for complex field accessors like JsonArray Field, JsonObject fields.
  150.      * It considers the translation and parent-child inheritance.
  151.      *
  152.      * fieldName => product.name
  153.      * root      => product
  154.      * return    => COALESCE(`product.translation`.`name`,`product.parent.translation`.`name`)
  155.      *
  156.      * @throws UnmappedFieldException
  157.      */
  158.     public function getFieldAccessor(string $fieldNameEntityDefinition $definitionstring $rootContext $context): string
  159.     {
  160.         $fieldName str_replace('extensions.'''$fieldName);
  161.         $original $fieldName;
  162.         $prefix $root '.';
  163.         if (mb_strpos($fieldName$prefix) === 0) {
  164.             $fieldName mb_substr($fieldNamemb_strlen($prefix));
  165.         } else {
  166.             $original $prefix $original;
  167.         }
  168.         $fields $definition->getFields();
  169.         if ($fields->has($fieldName)) {
  170.             $field $fields->get($fieldName);
  171.             return $this->buildInheritedAccessor($field$root$definition$context$fieldName);
  172.         }
  173.         $parts explode('.'$fieldName);
  174.         $associationKey array_shift($parts);
  175.         if ($associationKey === 'extensions') {
  176.             $associationKey array_shift($parts);
  177.         }
  178.         if (!$fields->has($associationKey)) {
  179.             throw new UnmappedFieldException($original$definition);
  180.         }
  181.         $field $fields->get($associationKey);
  182.         //case for json object fields, other fields has now same option to act with more point notations but hasn't to be an association field. E.g. price.gross
  183.         if (!$field instanceof AssociationField && ($field instanceof StorageAware || $field instanceof TranslatedField)) {
  184.             return $this->buildInheritedAccessor($field$root$definition$context$fieldName);
  185.         }
  186.         if (!$field instanceof AssociationField) {
  187.             throw new \RuntimeException(sprintf('Expected field "%s" to be instance of %s'$associationKeyAssociationField::class));
  188.         }
  189.         $referenceDefinition $field->getReferenceDefinition();
  190.         if ($field instanceof ManyToManyAssociationField) {
  191.             $referenceDefinition $field->getToManyReferenceDefinition();
  192.         }
  193.         return $this->getFieldAccessor(
  194.             $original,
  195.             $referenceDefinition,
  196.             $root '.' $field->getPropertyName(),
  197.             $context
  198.         );
  199.     }
  200.     public static function getAssociationPath(string $accessorEntityDefinition $definition): ?string
  201.     {
  202.         $fields self::getFieldsOfAccessor($definition$accessor);
  203.         $path = [];
  204.         foreach ($fields as $field) {
  205.             if (!$field instanceof AssociationField) {
  206.                 break;
  207.             }
  208.             $path[] = $field->getPropertyName();
  209.         }
  210.         if (empty($path)) {
  211.             return null;
  212.         }
  213.         return implode('.'$path);
  214.     }
  215.     /**
  216.      * Creates the basic root query for the provided entity definition and application context.
  217.      * It considers the current context version.
  218.      */
  219.     public function getBaseQuery(QueryBuilder $queryEntityDefinition $definitionContext $context): QueryBuilder
  220.     {
  221.         $table $definition->getEntityName();
  222.         $query->from(self::escape($table));
  223.         $useVersionFallback // only applies for versioned entities
  224.             $definition->isVersionAware()
  225.             // only add live fallback if the current version isn't the live version
  226.             && $context->getVersionId() !== Defaults::LIVE_VERSION
  227.             // sub entities have no live fallback
  228.             && $definition->getParentDefinition() === null;
  229.         if ($useVersionFallback) {
  230.             $this->joinVersion($query$definition$definition->getEntityName(), $context);
  231.         } elseif ($definition->isVersionAware()) {
  232.             $versionIdField array_filter(
  233.                 $definition->getPrimaryKeys()->getElements(),
  234.                 function ($f) {
  235.                     return $f instanceof VersionField || $f instanceof ReferenceVersionField;
  236.                 }
  237.             );
  238.             if (!$versionIdField) {
  239.                 throw new \RuntimeException('Missing `VersionField` in `' $definition->getClass() . '`');
  240.             }
  241.             /** @var FkField $versionIdField */
  242.             $versionIdField array_shift($versionIdField);
  243.             $query->andWhere(self::escape($table) . '.' self::escape($versionIdField->getStorageName()) . ' = :version');
  244.             $query->setParameter('version'Uuid::fromHexToBytes($context->getVersionId()));
  245.         }
  246.         return $query;
  247.     }
  248.     /**
  249.      * Used for dynamic sql joins. In case that the given fieldName is unknown or event nested with multiple association
  250.      * roots, the function can resolve each association part of the field name, even if one part of the fieldName contains a translation or event inherited data field.
  251.      */
  252.     public function resolveAccessor(
  253.         string $accessor,
  254.         EntityDefinition $definition,
  255.         string $root,
  256.         QueryBuilder $query,
  257.         Context $context,
  258.         ?CriteriaPartInterface $criteriaPart null
  259.     ): void {
  260.         $accessor str_replace('extensions.'''$accessor);
  261.         $parts explode('.'$accessor);
  262.         if ($parts[0] === $root) {
  263.             unset($parts[0]);
  264.         }
  265.         $alias $root;
  266.         $path = [$root];
  267.         $rootDefinition $definition;
  268.         foreach ($parts as $part) {
  269.             $field $definition->getFields()->get($part);
  270.             if ($field === null) {
  271.                 return;
  272.             }
  273.             $resolver $field->getResolver();
  274.             if ($resolver === null) {
  275.                 continue;
  276.             }
  277.             if ($field instanceof AssociationField) {
  278.                 $path[] = $field->getPropertyName();
  279.             }
  280.             $currentPath implode('.'$path);
  281.             $resolverContext = new FieldResolverContext($currentPath$alias$field$definition$rootDefinition$query$context$criteriaPart);
  282.             $alias $this->callResolver($resolverContext);
  283.             if (!$field instanceof AssociationField) {
  284.                 return;
  285.             }
  286.             $definition $field->getReferenceDefinition();
  287.             if ($field instanceof ManyToManyAssociationField) {
  288.                 $definition $field->getToManyReferenceDefinition();
  289.             }
  290.             if ($definition->isInheritanceAware() && $context->considerInheritance() && $parent $definition->getField('parent')) {
  291.                 $resolverContext = new FieldResolverContext($currentPath$alias$parent$definition$rootDefinition$query$context$criteriaPart);
  292.                 $this->callResolver($resolverContext);
  293.             }
  294.         }
  295.     }
  296.     public function resolveField(Field $fieldEntityDefinition $definitionstring $rootQueryBuilder $queryContext $context): void
  297.     {
  298.         $resolver $field->getResolver();
  299.         if ($resolver === null) {
  300.             return;
  301.         }
  302.         $resolver->join(new FieldResolverContext($root$root$field$definition$definition$query$contextnull));
  303.     }
  304.     /**
  305.      * Adds the full translation select part to the provided sql query.
  306.      * Considers the parent-child inheritance and provided context language inheritance.
  307.      * The raw parameter allows to skip the parent-child inheritance.
  308.      *
  309.      * @param array<string, mixed> $partial
  310.      */
  311.     public function addTranslationSelect(string $rootEntityDefinition $definitionQueryBuilder $queryContext $context, array $partial = []): void
  312.     {
  313.         $translationDefinition $definition->getTranslationDefinition();
  314.         if (!$translationDefinition) {
  315.             return;
  316.         }
  317.         $fields $translationDefinition->getFields();
  318.         if (!empty($partial)) {
  319.             $fields $translationDefinition->getFields()->filter(function (Field $field) use ($partial) {
  320.                 return $field->is(PrimaryKey::class)
  321.                     || isset($partial[$field->getPropertyName()])
  322.                     || $field instanceof FkField;
  323.             });
  324.         }
  325.         $inherited $context->considerInheritance() && $definition->isInheritanceAware();
  326.         $chain EntityDefinitionQueryHelper::buildTranslationChain($root$context$inherited);
  327.         /** @var TranslatedField $field */
  328.         foreach ($fields as $field) {
  329.             if (!$field instanceof StorageAware) {
  330.                 continue;
  331.             }
  332.             $selects = [];
  333.             foreach ($chain as $select) {
  334.                 $vars = [
  335.                     '#root#' => $select,
  336.                     '#field#' => $field->getPropertyName(),
  337.                 ];
  338.                 $query->addSelect(str_replace(
  339.                     array_keys($vars),
  340.                     array_values($vars),
  341.                     EntityDefinitionQueryHelper::escape('#root#.#field#')
  342.                 ));
  343.                 $selects[] = str_replace(
  344.                     array_keys($vars),
  345.                     array_values($vars),
  346.                     self::escape('#root#.#field#')
  347.                 );
  348.             }
  349.             //check if current field is a translated field of the origin definition
  350.             $origin $definition->getFields()->get($field->getPropertyName());
  351.             if (!$origin instanceof TranslatedField) {
  352.                 continue;
  353.             }
  354.             $selects[] = self::escape($root '.translation.' $field->getPropertyName());
  355.             //add selection for resolved parent-child and language inheritance
  356.             $query->addSelect(
  357.                 sprintf('COALESCE(%s)'implode(','$selects)) . ' as '
  358.                 self::escape($root '.' $field->getPropertyName())
  359.             );
  360.         }
  361.     }
  362.     public function joinVersion(QueryBuilder $queryEntityDefinition $definitionstring $rootContext $context): void
  363.     {
  364.         $table $definition->getEntityName();
  365.         $versionRoot $root '_version';
  366.         $query->andWhere(
  367.             str_replace(
  368.                 ['#root#''#table#''#version#'],
  369.                 [self::escape($root), self::escape($table), self::escape($versionRoot)],
  370.                 '#root#.version_id = COALESCE(
  371.                     (SELECT DISTINCT version_id FROM #table# AS #version# WHERE #version#.`id` = #root#.`id` AND `version_id` = :version),
  372.                     :liveVersion
  373.                 )'
  374.             )
  375.         );
  376.         $query->setParameter('liveVersion'Uuid::fromHexToBytes(Defaults::LIVE_VERSION));
  377.         $query->setParameter('version'Uuid::fromHexToBytes($context->getVersionId()));
  378.     }
  379.     public static function getTranslatedField(EntityDefinition $definitionTranslatedField $translatedField): Field
  380.     {
  381.         $translationDefinition $definition->getTranslationDefinition();
  382.         if ($translationDefinition === null) {
  383.             throw new \RuntimeException(sprintf('Entity %s has no translation definition'$definition->getEntityName()));
  384.         }
  385.         $field $translationDefinition->getFields()->get($translatedField->getPropertyName());
  386.         if ($field === null || !$field instanceof StorageAware || !$field instanceof Field) {
  387.             throw new \RuntimeException(
  388.                 sprintf(
  389.                     'Missing translated storage aware property %s in %s',
  390.                     $translatedField->getPropertyName(),
  391.                     $translationDefinition->getEntityName()
  392.                 )
  393.             );
  394.         }
  395.         return $field;
  396.     }
  397.     /**
  398.      * @return list<string>
  399.      */
  400.     public static function buildTranslationChain(string $rootContext $contextbool $includeParent): array
  401.     {
  402.         $count = \count($context->getLanguageIdChain()) - 1;
  403.         for ($i $count$i >= 1; --$i) {
  404.             $chain[] = $root '.translation.fallback_' $i;
  405.             if ($includeParent) {
  406.                 $chain[] = $root '.parent.translation.fallback_' $i;
  407.             }
  408.         }
  409.         $chain[] = $root '.translation';
  410.         if ($includeParent) {
  411.             $chain[] = $root '.parent.translation';
  412.         }
  413.         return $chain;
  414.     }
  415.     public function addIdCondition(Criteria $criteriaEntityDefinition $definitionQueryBuilder $query): void
  416.     {
  417.         $primaryKeys $criteria->getIds();
  418.         $primaryKeys array_values($primaryKeys);
  419.         if (empty($primaryKeys)) {
  420.             return;
  421.         }
  422.         if (!\is_array($primaryKeys[0]) || \count($primaryKeys[0]) === 1) {
  423.             $primaryKeyField $definition->getPrimaryKeys()->first();
  424.             if ($primaryKeyField instanceof IdField || $primaryKeyField instanceof FkField) {
  425.                 $primaryKeys array_map(function ($id) {
  426.                     if (\is_array($id)) {
  427.                         /** @var string $shiftedId */
  428.                         $shiftedId array_shift($id);
  429.                         return Uuid::fromHexToBytes($shiftedId);
  430.                     }
  431.                     return Uuid::fromHexToBytes($id);
  432.                 }, $primaryKeys);
  433.             }
  434.             if (!$primaryKeyField instanceof StorageAware) {
  435.                 throw new \RuntimeException('Primary key fields has to be an instance of StorageAware');
  436.             }
  437.             $query->andWhere(sprintf(
  438.                 '%s.%s IN (:ids)',
  439.                 EntityDefinitionQueryHelper::escape($definition->getEntityName()),
  440.                 EntityDefinitionQueryHelper::escape($primaryKeyField->getStorageName())
  441.             ));
  442.             $query->setParameter('ids'$primaryKeysConnection::PARAM_STR_ARRAY);
  443.             return;
  444.         }
  445.         $this->addIdConditionWithOr($criteria$definition$query);
  446.     }
  447.     private function callResolver(FieldResolverContext $context): string
  448.     {
  449.         $resolver $context->getField()->getResolver();
  450.         if (!$resolver) {
  451.             return $context->getAlias();
  452.         }
  453.         return $resolver->join($context);
  454.     }
  455.     private function addIdConditionWithOr(Criteria $criteriaEntityDefinition $definitionQueryBuilder $query): void
  456.     {
  457.         $wheres = [];
  458.         foreach ($criteria->getIds() as $primaryKey) {
  459.             if (!\is_array($primaryKey)) {
  460.                 $primaryKey = ['id' => $primaryKey];
  461.             }
  462.             $where = [];
  463.             foreach ($primaryKey as $propertyName => $value) {
  464.                 $field $definition->getFields()->get($propertyName);
  465.                 /*
  466.                  * @deprecated tag:v6.5.0 - with 6.5.0 the only passing the propertyName will be supported
  467.                  */
  468.                 if (!$field) {
  469.                     $field $definition->getFields()->getByStorageName($propertyName);
  470.                 }
  471.                 if (!$field) {
  472.                     throw new UnmappedFieldException($propertyName$definition);
  473.                 }
  474.                 if (!$field instanceof StorageAware) {
  475.                     throw new \RuntimeException('Only storage aware fields are supported in read condition');
  476.                 }
  477.                 if ($field instanceof IdField || $field instanceof FkField) {
  478.                     $value Uuid::fromHexToBytes($value);
  479.                 }
  480.                 $key 'pk' Uuid::randomHex();
  481.                 $accessor EntityDefinitionQueryHelper::escape($definition->getEntityName()) . '.' EntityDefinitionQueryHelper::escape($field->getStorageName());
  482.                 /*
  483.                  * @deprecated tag:v6.5.0 - check for duplication in accessors will be removed,
  484.                  * when we only support propertyNames to be used in search and when IdSearchResult only returns the propertyNames
  485.                  */
  486.                 if (!\array_key_exists($accessor$where)) {
  487.                     $where[$accessor] = $accessor ' = :' $key;
  488.                     $query->setParameter($key$value);
  489.                 }
  490.             }
  491.             $wheres[] = '(' implode(' AND '$where) . ')';
  492.         }
  493.         $wheres implode(' OR '$wheres);
  494.         $query->andWhere($wheres);
  495.     }
  496.     /**
  497.      * @param list<string> $chain
  498.      */
  499.     private function getTranslationFieldAccessor(Field $fieldstring $accessor, array $chainContext $context): string
  500.     {
  501.         if (!$field instanceof StorageAware) {
  502.             throw new \RuntimeException('Only storage aware fields are supported as translated field');
  503.         }
  504.         $selects = [];
  505.         foreach ($chain as $part) {
  506.             $select $this->buildFieldSelector($part$field$context$accessor);
  507.             $selects[] = str_replace(
  508.                 '`.' self::escape($field->getStorageName()),
  509.                 '.' $field->getPropertyName() . '`',
  510.                 $select
  511.             );
  512.         }
  513.         /*
  514.          * Simplified Example:
  515.          * COALESCE(
  516.              JSON_UNQUOTE(JSON_EXTRACT(`tbl.translation.fallback_2`.`translated_attributes`, '$.path')) AS datetime(3), # child language
  517.              JSON_UNQUOTE(JSON_EXTRACT(`tbl.translation.fallback_1`.`translated_attributes`, '$.path')) AS datetime(3), # root language
  518.              JSON_UNQUOTE(JSON_EXTRACT(`tbl.translation`.`translated_attributes`, '$.path')) AS datetime(3) # system language
  519.            );
  520.          */
  521.         return sprintf('COALESCE(%s)'implode(','$selects));
  522.     }
  523.     private function buildInheritedAccessor(
  524.         Field $field,
  525.         string $root,
  526.         EntityDefinition $definition,
  527.         Context $context,
  528.         string $original
  529.     ): string {
  530.         if ($field instanceof TranslatedField) {
  531.             $inheritedChain self::buildTranslationChain($root$context$definition->isInheritanceAware() && $context->considerInheritance());
  532.             $translatedField self::getTranslatedField($definition$field);
  533.             return $this->getTranslationFieldAccessor($translatedField$original$inheritedChain$context);
  534.         }
  535.         $select $this->buildFieldSelector($root$field$context$original);
  536.         if (!$field->is(Inherited::class) || !$context->considerInheritance()) {
  537.             return $select;
  538.         }
  539.         $parentSelect $this->buildFieldSelector($root '.parent'$field$context$original);
  540.         return sprintf('IFNULL(%s, %s)'$select$parentSelect);
  541.     }
  542.     private function buildFieldSelector(string $rootField $fieldContext $contextstring $accessor): string
  543.     {
  544.         $accessorBuilder $field->getAccessorBuilder();
  545.         if (!$accessorBuilder) {
  546.             throw new FieldAccessorBuilderNotFoundException($field->getPropertyName());
  547.         }
  548.         $accessor $accessorBuilder->buildAccessor($root$field$context$accessor);
  549.         if (!$accessor) {
  550.             throw new \RuntimeException(sprintf('Can not build accessor for field "%s" on root "%s"'$field->getPropertyName(), $root));
  551.         }
  552.         return $accessor;
  553.     }
  554. }