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

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