Skip to content

Commit f22fa73

Browse files
fix(elasticsearch): elasticsearch BC
1 parent d3a584c commit f22fa73

File tree

12 files changed

+306
-59
lines changed

12 files changed

+306
-59
lines changed

src/Core/Bridge/Elasticsearch/DataProvider/Extension/AbstractFilterExtension.php

+55-3
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,62 @@
1313

1414
namespace ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Extension;
1515

16-
class_exists(\ApiPlatform\Elasticsearch\Extension\AbstractFilterExtension::class);
16+
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
17+
use Psr\Container\ContainerInterface;
1718

18-
if (false) {
19-
class AbstractFilterExtension extends \ApiPlatform\Elasticsearch\Extension\AbstractFilterExtension
19+
/**
20+
* Abstract class for easing the implementation of a filter extension.
21+
*
22+
* @experimental
23+
*
24+
* @author Baptiste Meyer <[email protected]>
25+
*/
26+
abstract class AbstractFilterExtension implements RequestBodySearchCollectionExtensionInterface
27+
{
28+
private $resourceMetadataFactory;
29+
private $filterLocator;
30+
31+
public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, ContainerInterface $filterLocator)
32+
{
33+
$this->resourceMetadataFactory = $resourceMetadataFactory;
34+
$this->filterLocator = $filterLocator;
35+
}
36+
37+
/**
38+
* {@inheritdoc}
39+
*/
40+
public function applyToCollection(array $requestBody, string $resourceClass, ?string $operationName = null, array $context = []): array
2041
{
42+
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
43+
$resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true);
44+
45+
if (!$resourceFilters) {
46+
return $requestBody;
47+
}
48+
49+
$context['filters'] = $context['filters'] ?? [];
50+
$clauseBody = [];
51+
52+
foreach ($resourceFilters as $filterId) {
53+
if ($this->filterLocator->has($filterId) && is_a($filter = $this->filterLocator->get($filterId), $this->getFilterInterface())) {
54+
$clauseBody = $filter->apply($clauseBody, $resourceClass, $operationName, $context);
55+
}
56+
}
57+
58+
if (!$clauseBody) {
59+
return $requestBody;
60+
}
61+
62+
return $this->alterRequestBody($requestBody, $clauseBody);
2163
}
64+
65+
/**
66+
* Gets the related filter interface.
67+
*/
68+
abstract protected function getFilterInterface(): string;
69+
70+
/**
71+
* Alters the request body.
72+
*/
73+
abstract protected function alterRequestBody(array $requestBody, array $clauseBody): array;
2274
}

src/Core/Bridge/Elasticsearch/DataProvider/Extension/ConstantScoreFilterExtension.php

+32-3
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,39 @@
1313

1414
namespace ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Extension;
1515

16-
class_exists(\ApiPlatform\Elasticsearch\Extension\ConstantScoreFilterExtension::class);
16+
use ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Filter\ConstantScoreFilterInterface;
1717

18-
if (false) {
19-
final class ConstantScoreFilterExtension extends \ApiPlatform\Elasticsearch\Extension\ConstantScoreFilterExtension
18+
/**
19+
* Applies filter clauses while executing a constant score query.
20+
*
21+
* @see https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-constant-score-query.html
22+
*
23+
* @experimental
24+
*
25+
* @author Baptiste Meyer <[email protected]>
26+
*/
27+
final class ConstantScoreFilterExtension extends AbstractFilterExtension
28+
{
29+
/**
30+
* {@inheritdoc}
31+
*/
32+
protected function getFilterInterface(): string
2033
{
34+
return ConstantScoreFilterInterface::class;
35+
}
36+
37+
/**
38+
* {@inheritdoc}
39+
*/
40+
protected function alterRequestBody(array $requestBody, array $clauseBody): array
41+
{
42+
$requestBody['query'] = $requestBody['query'] ?? [];
43+
$requestBody['query'] += [
44+
'constant_score' => [
45+
'filter' => $clauseBody,
46+
],
47+
];
48+
49+
return $requestBody;
2150
}
2251
}

src/Core/Bridge/Elasticsearch/DataProvider/Extension/SortExtension.php

+81-3
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,88 @@
1313

1414
namespace ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Extension;
1515

16-
class_exists(\ApiPlatform\Elasticsearch\Extension\SortExtension::class);
16+
use ApiPlatform\Core\Api\ResourceClassResolverInterface;
17+
use ApiPlatform\Core\Bridge\Elasticsearch\Api\IdentifierExtractorInterface;
18+
use ApiPlatform\Core\Bridge\Elasticsearch\Util\FieldDatatypeTrait;
19+
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
20+
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
21+
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
1722

18-
if (false) {
19-
final class SortExtension extends \ApiPlatform\Elasticsearch\Extension\SortExtension
23+
/**
24+
* Applies selected sorting while querying resource collection.
25+
*
26+
* @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-sort.html
27+
*
28+
* @experimental
29+
*
30+
* @author Baptiste Meyer <[email protected]>
31+
*/
32+
final class SortExtension implements RequestBodySearchCollectionExtensionInterface
33+
{
34+
use FieldDatatypeTrait;
35+
36+
private $defaultDirection;
37+
private $identifierExtractor;
38+
private $resourceMetadataFactory;
39+
private $nameConverter;
40+
41+
public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, IdentifierExtractorInterface $identifierExtractor, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, ?NameConverterInterface $nameConverter = null, ?string $defaultDirection = null)
42+
{
43+
$this->resourceMetadataFactory = $resourceMetadataFactory;
44+
$this->identifierExtractor = $identifierExtractor;
45+
$this->propertyMetadataFactory = $propertyMetadataFactory;
46+
$this->resourceClassResolver = $resourceClassResolver;
47+
$this->nameConverter = $nameConverter;
48+
$this->defaultDirection = $defaultDirection;
49+
}
50+
51+
/**
52+
* {@inheritdoc}
53+
*/
54+
public function applyToCollection(array $requestBody, string $resourceClass, ?string $operationName = null, array $context = []): array
55+
{
56+
$orders = [];
57+
58+
if (
59+
null !== ($defaultOrder = $this->resourceMetadataFactory->create($resourceClass)->getAttribute('order'))
60+
&& \is_array($defaultOrder)
61+
) {
62+
foreach ($defaultOrder as $property => $direction) {
63+
if (\is_int($property)) {
64+
$property = $direction;
65+
$direction = 'asc';
66+
}
67+
68+
$orders[] = $this->getOrder($resourceClass, $property, $direction);
69+
}
70+
} elseif (null !== $this->defaultDirection) {
71+
$orders[] = $this->getOrder(
72+
$resourceClass,
73+
$this->identifierExtractor->getIdentifierFromResourceClass($resourceClass),
74+
$this->defaultDirection
75+
);
76+
}
77+
78+
if (!$orders) {
79+
return $requestBody;
80+
}
81+
82+
$requestBody['sort'] = array_merge_recursive($requestBody['sort'] ?? [], $orders);
83+
84+
return $requestBody;
85+
}
86+
87+
private function getOrder(string $resourceClass, string $property, string $direction): array
2088
{
89+
$order = ['order' => strtolower($direction)];
90+
91+
if (null !== $nestedPath = $this->getNestedFieldPath($resourceClass, $property)) {
92+
$nestedPath = null === $this->nameConverter ? $nestedPath : $this->nameConverter->normalize($nestedPath, $resourceClass);
93+
$order['nested'] = ['path' => $nestedPath];
94+
}
95+
96+
$property = null === $this->nameConverter ? $property : $this->nameConverter->normalize($property, $resourceClass);
97+
98+
return [$property => $order];
2199
}
22100
}

src/Core/Bridge/Elasticsearch/DataProvider/Extension/SortFilterExtension.php

+27-3
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,34 @@
1313

1414
namespace ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Extension;
1515

16-
class_exists(\ApiPlatform\Elasticsearch\Extension\SortFilterExtension::class);
16+
use ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Filter\SortFilterInterface;
1717

18-
if (false) {
19-
final class SortFilterExtension extends \ApiPlatform\Elasticsearch\Extension\SortFilterExtension
18+
/**
19+
* Applies filters on the sort parameter while querying resource collection.
20+
*
21+
* @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-sort.html
22+
*
23+
* @experimental
24+
*
25+
* @author Baptiste Meyer <[email protected]>
26+
*/
27+
final class SortFilterExtension extends AbstractFilterExtension
28+
{
29+
/**
30+
* {@inheritdoc}
31+
*/
32+
protected function getFilterInterface(): string
2033
{
34+
return SortFilterInterface::class;
35+
}
36+
37+
/**
38+
* {@inheritdoc}
39+
*/
40+
protected function alterRequestBody(array $requestBody, array $clauseBody): array
41+
{
42+
$requestBody['sort'] = array_merge_recursive($requestBody['sort'] ?? [], $clauseBody);
43+
44+
return $requestBody;
2145
}
2246
}

src/Core/Bridge/Elasticsearch/Serializer/DocumentNormalizer.php

-22
This file was deleted.

src/Core/Bridge/Elasticsearch/Serializer/ItemNormalizer.php

+83-3
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,90 @@
1313

1414
namespace ApiPlatform\Core\Bridge\Elasticsearch\Serializer;
1515

16-
class_exists(\ApiPlatform\Elasticsearch\Serializer\ItemNormalizer::class);
16+
use ApiPlatform\Core\Bridge\Elasticsearch\Api\IdentifierExtractorInterface;
17+
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
18+
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
19+
use Symfony\Component\Serializer\Exception\LogicException;
20+
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface;
21+
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
22+
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
23+
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
1724

18-
if (false) {
19-
final class ItemNormalizer extends \ApiPlatform\Elasticsearch\Serializer\ItemNormalizer
25+
/**
26+
* Item denormalizer for Elasticsearch.
27+
*
28+
* @experimental
29+
*
30+
* @author Baptiste Meyer <[email protected]>
31+
*/
32+
final class ItemNormalizer extends ObjectNormalizer
33+
{
34+
public const FORMAT = 'elasticsearch';
35+
36+
private $identifierExtractor;
37+
38+
public function __construct(IdentifierExtractorInterface $identifierExtractor, ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null, array $defaultContext = [])
39+
{
40+
parent::__construct($classMetadataFactory, $nameConverter, $propertyAccessor, $propertyTypeExtractor, $classDiscriminatorResolver, $objectClassResolver, $defaultContext);
41+
42+
$this->identifierExtractor = $identifierExtractor;
43+
}
44+
45+
/**
46+
* {@inheritdoc}
47+
*/
48+
public function supportsDenormalization($data, $type, $format = null): bool
49+
{
50+
return self::FORMAT === $format && parent::supportsDenormalization($data, $type, $format);
51+
}
52+
53+
/**
54+
* {@inheritdoc}
55+
*
56+
* @return mixed
57+
*/
58+
public function denormalize($data, $class, $format = null, array $context = [])
2059
{
60+
if (\is_string($data['_id'] ?? null) && \is_array($data['_source'] ?? null)) {
61+
$data = $this->populateIdentifier($data, $class)['_source'];
62+
}
63+
64+
return parent::denormalize($data, $class, $format, $context);
65+
}
66+
67+
/**
68+
* {@inheritdoc}
69+
*/
70+
public function supportsNormalization($data, $format = null): bool
71+
{
72+
// prevent the use of lower priority normalizers (e.g. serializer.normalizer.object) for this format
73+
return self::FORMAT === $format;
74+
}
75+
76+
/**
77+
* {@inheritdoc}
78+
*
79+
* @throws LogicException
80+
*
81+
* @return mixed
82+
*/
83+
public function normalize($object, $format = null, array $context = [])
84+
{
85+
throw new LogicException(sprintf('%s is a write-only format.', self::FORMAT));
86+
}
87+
88+
/**
89+
* Populates the resource identifier with the document identifier if not present in the original JSON document.
90+
*/
91+
private function populateIdentifier(array $data, string $class): array
92+
{
93+
$identifier = $this->identifierExtractor->getIdentifierFromResourceClass($class);
94+
$identifier = null === $this->nameConverter ? $identifier : $this->nameConverter->normalize($identifier, $class, self::FORMAT);
95+
96+
if (!isset($data['_source'][$identifier])) {
97+
$data['_source'][$identifier] = $data['_id'];
98+
}
99+
100+
return $data;
21101
}
22102
}

src/Elasticsearch/Serializer/DocumentNormalizer.php

-2
Original file line numberDiff line numberDiff line change
@@ -119,5 +119,3 @@ private function populateIdentifier(array $data, string $class): array
119119
return $data;
120120
}
121121
}
122-
123-
class_alias(DocumentNormalizer::class, \ApiPlatform\Core\Bridge\Elasticsearch\Serializer\DocumentNormalizer::class);

src/Elasticsearch/Serializer/ItemNormalizer.php

-2
Original file line numberDiff line numberDiff line change
@@ -111,5 +111,3 @@ public function setSerializer(SerializerInterface $serializer)
111111
$this->decorated->setSerializer($serializer);
112112
}
113113
}
114-
115-
class_alias(ItemNormalizer::class, \ApiPlatform\Core\Bridge\Elasticsearch\Serializer\ItemNormalizer::class);

src/Symfony/Bundle/Resources/config/elasticsearch.xml

-16
Original file line numberDiff line numberDiff line change
@@ -45,22 +45,6 @@
4545
<argument type="service" id="api_platform.name_converter" on-invalid="ignore" />
4646
</service>
4747

48-
<service id="api_platform.elasticsearch.normalizer.item" class="ApiPlatform\Elasticsearch\Serializer\ItemNormalizer" decorates="api_platform.serializer.normalizer.item" public="false">
49-
<argument type="service" id="api_platform.elasticsearch.normalizer.item.inner" />
50-
</service>
51-
52-
<service id="api_platform.elasticsearch.normalizer.document" class="ApiPlatform\Elasticsearch\Serializer\DocumentNormalizer" public="false">
53-
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory.retro_compatible" />
54-
<argument type="service" id="serializer.mapping.class_metadata_factory" />
55-
<argument type="service" id="api_platform.elasticsearch.name_converter.inner_fields" />
56-
<argument type="service" id="serializer.property_accessor" />
57-
<argument type="service" id="property_info" on-invalid="ignore" />
58-
<argument type="service" id="serializer.mapping.class_discriminator_resolver" on-invalid="ignore" />
59-
60-
<!-- Run after serializer.normalizer.data_uri but before serializer.normalizer.object -->
61-
<tag name="serializer.normalizer" priority="-922" />
62-
</service>
63-
6448
<service id="api_platform.elasticsearch.item_data_provider" class="ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\ItemDataProvider" public="false">
6549
<argument type="service" id="api_platform.elasticsearch.client" />
6650
<argument type="service" id="api_platform.elasticsearch.metadata.document.metadata_factory" />

0 commit comments

Comments
 (0)