• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 14980015570

12 May 2025 06:41PM UTC coverage: 26.309% (+2.6%) from 23.685%
14980015570

Pull #7140

github

web-flow
Merge 1e6b14143 into 202c60fcb
Pull Request #7140: Fix: PHPize HTTP cache headers

0 of 1 new or added line in 1 file covered. (0.0%)

4614 existing lines in 185 files now uncovered.

13550 of 51504 relevant lines covered (26.31%)

71.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/Elasticsearch/Filter/AbstractSearchFilter.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
declare(strict_types=1);
13

14
namespace ApiPlatform\Elasticsearch\Filter;
15

16
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
17
use ApiPlatform\Metadata\HttpOperation;
18
use ApiPlatform\Metadata\IriConverterInterface;
19
use ApiPlatform\Metadata\Operation;
20
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
21
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
22
use ApiPlatform\Metadata\ResourceClassResolverInterface;
23
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
24
use Symfony\Component\PropertyInfo\Type as LegacyType;
25
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
26
use Symfony\Component\TypeInfo\Type;
27
use Symfony\Component\TypeInfo\Type\WrappingTypeInterface;
28
use Symfony\Component\TypeInfo\TypeIdentifier;
29

30
/**
31
 * Abstract class with helpers for easing the implementation of a search filter like a term filter or a match filter.
32
 *
33
 * @experimental
34
 *
35
 * @internal
36
 *
37
 * @author Baptiste Meyer <baptiste.meyer@gmail.com>
38
 */
39
abstract class AbstractSearchFilter extends AbstractFilter implements ConstantScoreFilterInterface
40
{
41
    public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, protected IriConverterInterface $iriConverter, protected PropertyAccessorInterface $propertyAccessor, ?NameConverterInterface $nameConverter = null, ?array $properties = null)
42
    {
UNCOV
43
        parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $resourceClassResolver, $nameConverter, $properties);
×
44
    }
45

46
    public function apply(array $clauseBody, string $resourceClass, ?Operation $operation = null, array $context = []): array
47
    {
48
        $searches = [];
×
49

50
        foreach ($context['filters'] ?? [] as $property => $values) {
×
51
            [$type, $hasAssociation, $nestedResourceClass, $nestedProperty] = $this->getMetadata($resourceClass, $property);
×
52

UNCOV
53
            if (!$type || !$values = (array) $values) {
×
54
                continue;
×
55
            }
56

UNCOV
57
            if ($hasAssociation || $this->isIdentifier($nestedResourceClass, $nestedProperty, $operation)) {
×
58
                $values = array_map($this->getIdentifierValue(...), $values, array_fill(0, \count($values), $nestedProperty));
×
59
            }
60

UNCOV
61
            if (!$this->hasValidValues($values, $type)) {
×
62
                continue;
×
63
            }
64

UNCOV
65
            $property = null === $this->nameConverter ? $property : $this->nameConverter->normalize($property, $resourceClass, null, $context);
×
66
            $nestedPath = $this->getNestedFieldPath($resourceClass, $property);
×
UNCOV
67
            $nestedPath = null === $nestedPath || null === $this->nameConverter ? $nestedPath : $this->nameConverter->normalize($nestedPath, $resourceClass, null, $context);
×
68

69
            $searches[] = $this->getQuery($property, $values, $nestedPath);
×
70
        }
71

UNCOV
72
        if (!$searches) {
×
73
            return $clauseBody;
×
74
        }
75

76
        return array_merge_recursive($clauseBody, [
×
77
            'bool' => [
×
UNCOV
78
                'must' => $searches,
×
UNCOV
79
            ],
×
UNCOV
80
        ]);
×
81
    }
82

83
    /**
84
     * {@inheritdoc}
85
     */
86
    public function getDescription(string $resourceClass): array
87
    {
88
        $description = [];
×
89

90
        foreach ($this->getProperties($resourceClass) as $property) {
×
91
            [$type, $hasAssociation] = $this->getMetadata($resourceClass, $property);
×
92

UNCOV
93
            if (!$type) {
×
94
                continue;
×
95
            }
96

97
            foreach ([$property, "{$property}[]"] as $filterParameterName) {
×
98
                $description[$filterParameterName] = [
×
99
                    'property' => $property,
×
100
                    'type' => $hasAssociation ? 'string' : $this->getPhpType($type),
×
UNCOV
101
                    'required' => false,
×
UNCOV
102
                    'is_collection' => str_ends_with((string) $filterParameterName, '[]'),
×
UNCOV
103
                ];
×
104
            }
105
        }
106

UNCOV
107
        return $description;
×
108
    }
109

110
    /**
111
     * Gets the Elasticsearch query corresponding to the current search filter.
112
     */
113
    abstract protected function getQuery(string $property, array $values, ?string $nestedPath): array;
114

115
    protected function getPhpType(LegacyType|Type $type): string
116
    {
117
        if ($type instanceof LegacyType) {
×
118
            switch ($builtinType = $type->getBuiltinType()) {
×
119
                case LegacyType::BUILTIN_TYPE_ARRAY:
×
120
                case LegacyType::BUILTIN_TYPE_INT:
×
121
                case LegacyType::BUILTIN_TYPE_FLOAT:
×
122
                case LegacyType::BUILTIN_TYPE_BOOL:
×
123
                case LegacyType::BUILTIN_TYPE_STRING:
×
124
                    return $builtinType;
×
125
                case LegacyType::BUILTIN_TYPE_OBJECT:
×
126
                    if (null !== ($className = $type->getClassName()) && is_a($className, \DateTimeInterface::class, true)) {
×
UNCOV
127
                        return \DateTimeInterface::class;
×
128
                    }
129

130
                    // no break
131
                default:
UNCOV
132
                    return 'string';
×
133
            }
134
        }
135

UNCOV
136
        if ($type->isIdentifiedBy(TypeIdentifier::ARRAY, TypeIdentifier::INT, TypeIdentifier::FLOAT, TypeIdentifier::BOOL, TypeIdentifier::STRING)) {
×
UNCOV
137
            while ($type instanceof WrappingTypeInterface) {
×
UNCOV
138
                $type = $type->getWrappedType();
×
139
            }
140

141
            return (string) $type;
×
142
        }
143

144
        if ($type->isIdentifiedBy(\DateTimeInterface::class)) {
×
145
            return \DateTimeInterface::class;
×
146
        }
147

UNCOV
148
        return 'string';
×
149
    }
150

151
    /**
152
     * Is the given property of the given resource class an identifier?
153
     */
154
    protected function isIdentifier(string $resourceClass, string $property, ?Operation $operation = null): bool
155
    {
UNCOV
156
        $identifier = 'id';
×
UNCOV
157
        if ($operation instanceof HttpOperation) {
×
158
            $uriVariable = $operation->getUriVariables()[0] ?? null;
×
159

160
            if ($uriVariable) {
×
161
                $identifier = $uriVariable->getIdentifiers()[0] ?? 'id';
×
162
            }
163
        }
164

UNCOV
165
        return $property === $identifier;
×
166
    }
167

168
    /**
169
     * Gets the ID from an IRI or a raw ID.
170
     */
171
    protected function getIdentifierValue(string $iri, string $property): mixed
172
    {
173
        try {
174
            $item = $this->iriConverter->getResourceFromIri($iri, ['fetch_data' => false]);
×
175

176
            return $this->propertyAccessor->getValue($item, $property);
×
UNCOV
177
        } catch (InvalidArgumentException) {
×
178
        }
179

UNCOV
180
        return $iri;
×
181
    }
182

183
    protected function hasValidValues(array $values, LegacyType|Type $type): bool
184
    {
UNCOV
185
        if ($type instanceof LegacyType) {
×
UNCOV
186
            foreach ($values as $value) {
×
187
                if (
UNCOV
188
                    null !== $value
×
UNCOV
189
                    && LegacyType::BUILTIN_TYPE_INT === $type->getBuiltinType()
×
UNCOV
190
                    && false === filter_var($value, \FILTER_VALIDATE_INT)
×
191
                ) {
UNCOV
192
                    return false;
×
193
                }
194
            }
195

UNCOV
196
            return true;
×
197
        }
198

UNCOV
199
        foreach ($values as $value) {
×
200
            if (
UNCOV
201
                null !== $value
×
UNCOV
202
                && $type->isIdentifiedBy(TypeIdentifier::INT)
×
UNCOV
203
                && false === filter_var($value, \FILTER_VALIDATE_INT)
×
204
            ) {
UNCOV
205
                return false;
×
206
            }
207
        }
208

UNCOV
209
        return true;
×
210
    }
211
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc