bug #11615 [Composer] Remove conflict to api-platform/core (GSadee)

This PR was merged into the 1.8-dev branch.

Discussion
----------

| Q               | A
| --------------- | -----
| Branch?         | master
| Bug fix?        | yes
| New feature?    | no
| BC breaks?      | no
| Deprecations?   | no
| Related tickets | associated with https://github.com/api-platform/core/issues/3585
| License         | MIT


Commits
-------

85362684c3 [Composer] Remove conflict to api-platform/core
1e4a687a86 [API] Temporarily overwrite doctrine orm subresource data provider
This commit is contained in:
Łukasz Chruściel 2020-06-30 10:16:40 +02:00 committed by GitHub
commit 3fca3292f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 238 additions and 1 deletions

View file

@ -185,7 +185,6 @@
"sylius/user-bundle": "self.version"
},
"conflict": {
"api-platform/core": "^2.5.5",
"doctrine/inflector": "^1.4",
"sylius/grid-bundle": "1.7.4",
"symfony/form": "4.4.9|4.4.10",

View file

@ -9,6 +9,7 @@
<directory name="src" />
<ignoreFiles>
<file name="src/Sylius/Bundle/CoreBundle/Application/Kernel.php" />
<file name="src/Sylius/Bundle/ApiBundle/ApiPlatform/Bridge/Doctrine/ORM/SubresourceDataProvider.php" />
<directory name="src/Sylius/Behat" />

View file

@ -0,0 +1,233 @@
<?php
/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Bundle\ApiBundle\ApiPlatform\Bridge\Doctrine\ORM;
use ApiPlatform\Core\Bridge\Doctrine\Common\Util\IdentifierManagerTrait;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\FilterEagerLoadingExtension;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryItemExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultItemExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGenerator;
use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface;
use ApiPlatform\Core\Exception\ResourceClassNotSupportedException;
use ApiPlatform\Core\Exception\RuntimeException;
use ApiPlatform\Core\Identifier\IdentifierConverterInterface;
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\QueryBuilder;
/**
* Subresource data provider for the Doctrine ORM.
*
* @author Antoine Bluchet <soyuka@gmail.com>
* @internal
*/
final class SubresourceDataProvider implements SubresourceDataProviderInterface
{
use IdentifierManagerTrait;
private $managerRegistry;
private $collectionExtensions;
private $itemExtensions;
/**
* @param QueryCollectionExtensionInterface[] $collectionExtensions
* @param QueryItemExtensionInterface[] $itemExtensions
*/
public function __construct(ManagerRegistry $managerRegistry, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, iterable $collectionExtensions = [], iterable $itemExtensions = [])
{
$this->managerRegistry = $managerRegistry;
$this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
$this->propertyMetadataFactory = $propertyMetadataFactory;
$this->collectionExtensions = $collectionExtensions;
$this->itemExtensions = $itemExtensions;
}
/**
* {@inheritdoc}
*
* @throws RuntimeException
*/
public function getSubresource(string $resourceClass, array $identifiers, array $context, string $operationName = null)
{
$manager = $this->managerRegistry->getManagerForClass($resourceClass);
if (null === $manager) {
throw new ResourceClassNotSupportedException(sprintf('The object manager associated with the "%s" resource class cannot be retrieved.', $resourceClass));
}
$repository = $manager->getRepository($resourceClass);
if (!method_exists($repository, 'createQueryBuilder')) {
throw new RuntimeException('The repository class must have a "createQueryBuilder" method.');
}
if (!isset($context['identifiers'], $context['property'])) {
throw new ResourceClassNotSupportedException('The given resource class is not a subresource.');
}
$queryNameGenerator = new QueryNameGenerator();
/*
* The following recursively translates to this pseudo-dql:
*
* SELECT thirdLevel WHERE thirdLevel IN (
* SELECT thirdLevel FROM relatedDummies WHERE relatedDummies = ? AND relatedDummies IN (
* SELECT relatedDummies FROM Dummy WHERE Dummy = ?
* )
* )
*
* By using subqueries, we're forcing the SQL execution plan to go through indexes on doctrine identifiers.
*/
$queryBuilder = $this->buildQuery($identifiers, $context, $queryNameGenerator, $repository->createQueryBuilder($alias = 'o'), $alias, \count($context['identifiers']));
if (true === $context['collection']) {
foreach ($this->collectionExtensions as $extension) {
// We don't need this anymore because we already made sub queries to ensure correct results
if ($extension instanceof FilterEagerLoadingExtension) {
continue;
}
$extension->applyToCollection($queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context);
if ($extension instanceof QueryResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) {
return $extension->getResult($queryBuilder, $resourceClass, $operationName, $context);
}
}
} else {
foreach ($this->itemExtensions as $extension) {
$extension->applyToItem($queryBuilder, $queryNameGenerator, $resourceClass, $identifiers, $operationName, $context);
if ($extension instanceof QueryResultItemExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) {
return $extension->getResult($queryBuilder, $resourceClass, $operationName, $context);
}
}
}
$query = $queryBuilder->getQuery();
return $context['collection'] ? $query->getResult() : $query->getOneOrNullResult();
}
/**
* @throws RuntimeException
*/
private function buildQuery(array $identifiers, array $context, QueryNameGenerator $queryNameGenerator, QueryBuilder $previousQueryBuilder, string $previousAlias, int $remainingIdentifiers, QueryBuilder $topQueryBuilder = null): QueryBuilder
{
if ($remainingIdentifiers <= 0) {
return $previousQueryBuilder;
}
$topQueryBuilder = $topQueryBuilder ?? $previousQueryBuilder;
[$identifier, $identifierResourceClass] = $context['identifiers'][$remainingIdentifiers - 1];
$previousAssociationProperty = $context['identifiers'][$remainingIdentifiers][0] ?? $context['property'];
$manager = $this->managerRegistry->getManagerForClass($identifierResourceClass);
if (!$manager instanceof EntityManagerInterface) {
throw new RuntimeException("The manager for $identifierResourceClass must be an EntityManager.");
}
$classMetadata = $manager->getClassMetadata($identifierResourceClass);
if (!$classMetadata instanceof ClassMetadataInfo) {
throw new RuntimeException("The class metadata for $identifierResourceClass must be an instance of ClassMetadataInfo.");
}
$qb = $manager->createQueryBuilder();
$alias = $queryNameGenerator->generateJoinAlias($identifier);
$normalizedIdentifiers = [];
if (isset($identifiers[$identifier])) {
// if it's an array it's already normalized, the IdentifierManagerTrait is deprecated
if ($context[IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER] ?? false) {
$normalizedIdentifiers = $identifiers[$identifier];
} else {
$normalizedIdentifiers = $this->normalizeIdentifiers($identifiers[$identifier], $manager, $identifierResourceClass);
}
}
if ($classMetadata->hasAssociation($previousAssociationProperty)) {
$relationType = $classMetadata->getAssociationMapping($previousAssociationProperty)['type'];
switch ($relationType) {
// MANY_TO_MANY relations need an explicit join so that the identifier part can be retrieved
case ClassMetadataInfo::MANY_TO_MANY:
$joinAlias = $queryNameGenerator->generateJoinAlias($previousAssociationProperty);
$qb->select($joinAlias)
->from($identifierResourceClass, $alias)
->innerJoin("$alias.$previousAssociationProperty", $joinAlias);
break;
case ClassMetadataInfo::ONE_TO_MANY:
$mappedBy = $classMetadata->getAssociationMapping($previousAssociationProperty)['mappedBy'];
$previousAlias = "$previousAlias.$mappedBy";
$qb->select($alias)
->from($identifierResourceClass, $alias);
break;
case ClassMetadataInfo::ONE_TO_ONE:
$association = $classMetadata->getAssociationMapping($previousAssociationProperty);
if (!isset($association['mappedBy'])) {
$qb->select("IDENTITY($alias.$previousAssociationProperty)")
->from($identifierResourceClass, $alias);
break;
}
$mappedBy = $association['mappedBy'];
$previousAlias = "$previousAlias.$mappedBy";
$qb->select($alias)
->from($identifierResourceClass, $alias);
break;
default:
$qb->select("IDENTITY($alias.$previousAssociationProperty)")
->from($identifierResourceClass, $alias);
}
} elseif ($classMetadata->isIdentifier($previousAssociationProperty)) {
$qb->select($alias)
->from($identifierResourceClass, $alias);
}
$isLeaf = 1 === $remainingIdentifiers;
// Add where clause for identifiers
foreach ($normalizedIdentifiers as $key => $value) {
$placeholder = $queryNameGenerator->generateParameterName($key);
$topQueryBuilder->setParameter($placeholder, $value, (string) $classMetadata->getTypeOfField($key));
// Optimization: add where clause for identifiers, but not via a WHERE ... IN ( ...subquery... ).
// Instead we use a direct identifier equality clause, to speed things up when dealing with large tables.
// We may do so if there is no more recursion levels from here, and if relation allows it.
$association = $classMetadata->hasAssociation($previousAssociationProperty) ? $classMetadata->getAssociationMapping($previousAssociationProperty) : [];
$oneToOneBidirectional = isset($association['inversedBy']) && ClassMetadataInfo::ONE_TO_ONE === $association['type'];
$oneToManyBidirectional = isset($association['mappedBy']) && ClassMetadataInfo::ONE_TO_MANY === $association['type'];
if ($isLeaf && $oneToOneBidirectional) {
$joinAlias = $queryNameGenerator->generateJoinAlias($association['inversedBy']);
return $previousQueryBuilder->innerJoin("$previousAlias.{$association['inversedBy']}", $joinAlias)
->andWhere("$joinAlias.$key = :$placeholder");
}
if ($isLeaf && $oneToManyBidirectional && \in_array($key, $classMetadata->getIdentifier(), true)) {
return $previousQueryBuilder->andWhere("IDENTITY($previousAlias) = :$placeholder");
}
$qb->andWhere("$alias.$key = :$placeholder");
}
// Recurse queries
$qb = $this->buildQuery($identifiers, $context, $queryNameGenerator, $qb, $alias, --$remainingIdentifiers, $topQueryBuilder);
return $previousQueryBuilder->andWhere($qb->expr()->in($previousAlias, $qb->getDQL()));
}
}

View file

@ -63,6 +63,10 @@
<argument type="service" id="service_container" />
</service>
<service id="api_platform.doctrine.orm.default.subresource_data_provider" parent="api_platform.doctrine.orm.subresource_data_provider" class="Sylius\Bundle\ApiBundle\ApiPlatform\Bridge\Doctrine\ORM\SubresourceDataProvider">
<tag name="api_platform.subresource_data_provider" />
</service>
<service id="sylius.api.property_info.extractor.empty_list_extractor" class="Sylius\Bundle\ApiBundle\PropertyInfo\Extractor\EmptyPropertyListExtractor">
<tag name="property_info.list_extractor" priority="-2000" />
</service>