mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-14 09:02:12 +00:00
minor #16697 [API] Fix translation.name filters (NoResponseMate)
This PR was merged into the 1.13 branch. Discussion ---------- | Q | A |-----------------|----- | Branch? | 1.13 | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Related tickets | - | License | MIT Commits ------- [API] Fix translation.name filters
This commit is contained in:
commit
78c0e1baa5
4 changed files with 326 additions and 26 deletions
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* 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\Doctrine\QueryCollectionExtension;
|
||||
|
||||
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\ContextAwareQueryCollectionExtensionInterface;
|
||||
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface as LegacyQueryNameGeneratorInterface;
|
||||
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
|
||||
use Doctrine\ORM\Query\Expr\Join;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Sylius\Component\Resource\Model\TranslatableInterface;
|
||||
|
||||
final class TranslationOrderLocaleExtension implements ContextAwareQueryCollectionExtensionInterface
|
||||
{
|
||||
/** @param array<string, mixed> $context */
|
||||
public function applyToCollection(
|
||||
QueryBuilder $queryBuilder,
|
||||
LegacyQueryNameGeneratorInterface|QueryNameGeneratorInterface $queryNameGenerator,
|
||||
string $resourceClass,
|
||||
string $operationName = null,
|
||||
array $context = [],
|
||||
): void {
|
||||
if (!is_a($resourceClass, TranslatableInterface::class, true)) {
|
||||
return;
|
||||
}
|
||||
/* @see \Sylius\Bundle\ApiBundle\Filter\Doctrine\TranslationOrderNameAndLocaleFilter */
|
||||
if (!isset($context['filters']['order']['translation.name'])) {
|
||||
return;
|
||||
}
|
||||
if (!$queryBuilder->getEntityManager()->getClassMetadata($resourceClass)->hasAssociation('translations')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$localeCode = $this->resolveContextLocaleCode($context);
|
||||
if (empty($localeCode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rootAlias = $queryBuilder->getRootAliases()[0];
|
||||
$localeCodeParameterName = $queryNameGenerator->generateParameterName('localeCode');
|
||||
|
||||
$queryBuilder
|
||||
->addSelect('translation')
|
||||
->leftJoin(
|
||||
sprintf('%s.translations', $rootAlias),
|
||||
'translation',
|
||||
Join::WITH,
|
||||
sprintf('translation.locale = :%s', $localeCodeParameterName),
|
||||
)
|
||||
->setParameter($localeCodeParameterName, $localeCode)
|
||||
;
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $context */
|
||||
private function resolveContextLocaleCode(array $context): ?string
|
||||
{
|
||||
return
|
||||
$context['filters']['localeCode for order']['translation.name'] ??
|
||||
$context['filters']['order']['localeCode']['translation.name'] ??
|
||||
$context['filters']['order']['localeCode'] ??
|
||||
null
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,8 @@ namespace Sylius\Bundle\ApiBundle\Filter\Doctrine;
|
|||
|
||||
use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\OrderFilterInterface;
|
||||
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractContextAwareFilter;
|
||||
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
|
||||
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface as LegacyQueryNameGeneratorInterface;
|
||||
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
final class TranslationOrderNameAndLocaleFilter extends AbstractContextAwareFilter
|
||||
|
|
@ -24,38 +25,18 @@ final class TranslationOrderNameAndLocaleFilter extends AbstractContextAwareFilt
|
|||
string $property,
|
||||
$value,
|
||||
QueryBuilder $queryBuilder,
|
||||
QueryNameGeneratorInterface $queryNameGenerator,
|
||||
LegacyQueryNameGeneratorInterface|QueryNameGeneratorInterface $queryNameGenerator,
|
||||
string $resourceClass,
|
||||
?string $operationName = null,
|
||||
): void {
|
||||
if ('order' === $property) {
|
||||
if (!isset($value['translation.name'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$direction = $value['translation.name'];
|
||||
$rootAlias = $queryBuilder->getRootAliases()[0];
|
||||
|
||||
if (isset($value['localeCode'])) {
|
||||
$localeParameterName = $queryNameGenerator->generateParameterName('locale');
|
||||
|
||||
$queryBuilder
|
||||
->addSelect('translation')
|
||||
->leftJoin(
|
||||
sprintf('%s.translations', $rootAlias),
|
||||
'translation',
|
||||
'WITH',
|
||||
sprintf('translation.locale = :%s', $localeParameterName),
|
||||
)
|
||||
->orderBy('translation.name', $direction)
|
||||
->setParameter($localeParameterName, $value['localeCode'])
|
||||
;
|
||||
|
||||
if ('order' === $property && isset($value['translation.name'])) {
|
||||
/** @phpstan-ignore-next-line */
|
||||
if (!$queryBuilder->getEntityManager()->getClassMetadata($resourceClass)->hasAssociation('translations')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$queryBuilder
|
||||
->orderBy('translation.name', $direction)
|
||||
->orderBy('translation.name', $value['translation.name'])
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
@ -75,6 +56,7 @@ final class TranslationOrderNameAndLocaleFilter extends AbstractContextAwareFilt
|
|||
],
|
||||
],
|
||||
],
|
||||
/* @see \Sylius\Bundle\ApiBundle\Doctrine\QueryCollectionExtension\TranslationOrderLocaleExtension */
|
||||
'localeCode for order[translation.name]' => [
|
||||
'type' => 'string',
|
||||
'required' => false,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@
|
|||
<services>
|
||||
<defaults public="true" />
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\Doctrine\QueryCollectionExtension\TranslationOrderLocaleExtension">
|
||||
<tag name="api_platform.doctrine.orm.query_extension.collection" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\Doctrine\QueryCollectionExtension\HideArchivedShippingMethodExtension">
|
||||
<argument>%sylius.model.shipping_method.class%</argument>
|
||||
<tag name="api_platform.doctrine.orm.query_extension.collection" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,240 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* 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\Tests\Doctrine\QueryCollectionExtension;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Query\Expr\Join;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Sylius\Bundle\ApiBundle\Doctrine\QueryCollectionExtension\TranslationOrderLocaleExtension;
|
||||
use Sylius\Component\Core\Model\ProductInterface;
|
||||
|
||||
final class TranslationOrderLocaleExtensionTest extends TestCase
|
||||
{
|
||||
private QueryNameGeneratorInterface&MockObject $queryNameGenerator;
|
||||
|
||||
private QueryBuilder&MockObject $queryBuilder;
|
||||
|
||||
private EntityManagerInterface&MockObject $entityManager;
|
||||
|
||||
private ClassMetadata&MockObject $classMetadata;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->queryNameGenerator = $this->createMock(QueryNameGeneratorInterface::class);
|
||||
$this->queryBuilder = $this->createMock(QueryBuilder::class);
|
||||
$this->entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$this->classMetadata = $this->createMock(ClassMetadata::class);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_does_nothing_when_resource_class_is_not_translatable(): void
|
||||
{
|
||||
$this->queryBuilder
|
||||
->expects($this->never())
|
||||
->method('leftJoin')
|
||||
;
|
||||
|
||||
$this->doApplyToCollection(\stdClass::class);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_does_nothing_when_the_resource_is_not_sorted_by_translation_name(): void
|
||||
{
|
||||
$this->queryBuilder
|
||||
->expects($this->never())
|
||||
->method('leftJoin')
|
||||
;
|
||||
|
||||
$this->doApplyToCollection(ProductInterface::class);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_does_nothing_when_the_resource_does_not_have_a_translations_association(): void
|
||||
{
|
||||
$this->queryBuilder
|
||||
->expects($this->once())
|
||||
->method('getEntityManager')
|
||||
->willReturn($this->entityManager)
|
||||
;
|
||||
|
||||
$this->entityManager
|
||||
->expects($this->once())
|
||||
->method('getClassMetadata')
|
||||
->with(ProductInterface::class)
|
||||
->willReturn($this->classMetadata)
|
||||
;
|
||||
|
||||
$this->classMetadata
|
||||
->expects($this->once())
|
||||
->method('hasAssociation')
|
||||
->with('translations')
|
||||
->willReturn(false)
|
||||
;
|
||||
|
||||
$this->queryBuilder
|
||||
->expects($this->never())
|
||||
->method('leftJoin')
|
||||
;
|
||||
|
||||
$this->doApplyToCollection(ProductInterface::class, [
|
||||
'filters' => [
|
||||
'order' => [
|
||||
'translation.name' => 'test',
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_does_nothing_when_no_locale_code_has_been_resolved_from_filters(): void
|
||||
{
|
||||
$this->queryBuilder
|
||||
->expects($this->once())
|
||||
->method('getEntityManager')
|
||||
->willReturn($this->entityManager)
|
||||
;
|
||||
|
||||
$this->entityManager
|
||||
->expects($this->once())
|
||||
->method('getClassMetadata')
|
||||
->with(ProductInterface::class)
|
||||
->willReturn($this->classMetadata)
|
||||
;
|
||||
|
||||
$this->classMetadata
|
||||
->expects($this->once())
|
||||
->method('hasAssociation')
|
||||
->with('translations')
|
||||
->willReturn(true)
|
||||
;
|
||||
|
||||
$this->queryBuilder
|
||||
->expects($this->never())
|
||||
->method('leftJoin')
|
||||
->withAnyParameters()
|
||||
;
|
||||
|
||||
$this->doApplyToCollection(ProductInterface::class, [
|
||||
'filters' => [
|
||||
'order' => [
|
||||
'translation.name' => 'test',
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider getLocaleCodeContexts
|
||||
*/
|
||||
public function it_joins_on_a_specific_translation_when_locale_code_has_been_resolved_from_filters(
|
||||
array $contextWithLocaleCode,
|
||||
): void {
|
||||
$this->queryBuilder
|
||||
->expects($this->once())
|
||||
->method('getEntityManager')
|
||||
->willReturn($this->entityManager)
|
||||
;
|
||||
|
||||
$this->entityManager
|
||||
->expects($this->once())
|
||||
->method('getClassMetadata')
|
||||
->with(ProductInterface::class)
|
||||
->willReturn($this->classMetadata)
|
||||
;
|
||||
|
||||
$this->classMetadata
|
||||
->expects($this->once())
|
||||
->method('hasAssociation')
|
||||
->with('translations')
|
||||
->willReturn(true)
|
||||
;
|
||||
|
||||
$this->queryBuilder
|
||||
->expects($this->once())
|
||||
->method('getRootAliases')
|
||||
->willReturn(['alias'])
|
||||
;
|
||||
|
||||
$this->queryNameGenerator
|
||||
->method('generateParameterName')
|
||||
->with('localeCode')
|
||||
->willReturn('param')
|
||||
;
|
||||
|
||||
$this->queryBuilder
|
||||
->expects($this->once())
|
||||
->method('addSelect')
|
||||
->with('translation')
|
||||
->willReturnSelf()
|
||||
;
|
||||
|
||||
$this->queryBuilder
|
||||
->expects($this->once())
|
||||
->method('leftJoin')
|
||||
->with('alias.translations', 'translation', Join::WITH, 'translation.locale = :param')
|
||||
->willReturnSelf()
|
||||
;
|
||||
|
||||
$this->queryBuilder
|
||||
->expects($this->once())
|
||||
->method('setParameter')
|
||||
->with('param', 'en_US')
|
||||
->willReturnSelf()
|
||||
;
|
||||
|
||||
$this->doApplyToCollection(ProductInterface::class, array_merge_recursive([
|
||||
'filters' => [
|
||||
'order' => [
|
||||
'translation.name' => 'test',
|
||||
],
|
||||
],
|
||||
], $contextWithLocaleCode));
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $context */
|
||||
private function doApplyToCollection(string $resourceClass, array $context = []): void
|
||||
{
|
||||
(new TranslationOrderLocaleExtension())->applyToCollection(
|
||||
queryBuilder: $this->queryBuilder,
|
||||
queryNameGenerator: $this->queryNameGenerator,
|
||||
resourceClass: $resourceClass,
|
||||
context: $context,
|
||||
);
|
||||
}
|
||||
|
||||
/** @return iterable<array<string, mixed>> */
|
||||
private function getLocaleCodeContexts(): iterable
|
||||
{
|
||||
yield 'locale code in documentation filter' => [[
|
||||
'filters' => [
|
||||
'localeCode for order' => ['translation.name' => 'en_US'],
|
||||
],
|
||||
]];
|
||||
yield 'locale code in localeCode translation.name filter' => [[
|
||||
'filters' => [
|
||||
'order' => ['localeCode' => ['translation.name' => 'en_US']],
|
||||
],
|
||||
]];
|
||||
yield 'locale code in localeCode filter' => [[
|
||||
'filters' => [
|
||||
'order' => ['localeCode' => 'en_US'],
|
||||
],
|
||||
]];
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue