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

api-platform / core / 10014117656

19 Jul 2024 08:44PM UTC coverage: 7.856% (-56.3%) from 64.185%
10014117656

push

github

soyuka
Merge branch 'sf/remove-flag'

0 of 527 new or added lines in 83 files covered. (0.0%)

10505 existing lines in 362 files now uncovered.

12705 of 161727 relevant lines covered (7.86%)

26.85 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;
25
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
26

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

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

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

50
            if (!$type || !$values = (array) $values) {
×
51
                continue;
×
52
            }
53

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

58
            if (!$this->hasValidValues($values, $type)) {
×
59
                continue;
×
60
            }
61

62
            $property = null === $this->nameConverter ? $property : $this->nameConverter->normalize($property, $resourceClass, null, $context);
×
63
            $nestedPath = $this->getNestedFieldPath($resourceClass, $property);
×
64
            $nestedPath = null === $nestedPath || null === $this->nameConverter ? $nestedPath : $this->nameConverter->normalize($nestedPath, $resourceClass, null, $context);
×
65

66
            $searches[] = $this->getQuery($property, $values, $nestedPath);
×
67
        }
68

69
        if (!$searches) {
×
70
            return $clauseBody;
×
71
        }
72

73
        return array_merge_recursive($clauseBody, [
×
74
            'bool' => [
×
75
                'must' => $searches,
×
76
            ],
×
77
        ]);
×
78
    }
79

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

87
        foreach ($this->getProperties($resourceClass) as $property) {
×
88
            [$type, $hasAssociation] = $this->getMetadata($resourceClass, $property);
×
89

90
            if (!$type) {
×
91
                continue;
×
92
            }
93

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

104
        return $description;
×
105
    }
106

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

112
    /**
113
     * Converts the given {@see Type} in PHP type.
114
     */
115
    protected function getPhpType(Type $type): string
116
    {
117
        switch ($builtinType = $type->getBuiltinType()) {
×
UNCOV
118
            case Type::BUILTIN_TYPE_ARRAY:
×
UNCOV
119
            case Type::BUILTIN_TYPE_INT:
×
UNCOV
120
            case Type::BUILTIN_TYPE_FLOAT:
×
UNCOV
121
            case Type::BUILTIN_TYPE_BOOL:
×
UNCOV
122
            case Type::BUILTIN_TYPE_STRING:
×
123
                return $builtinType;
×
UNCOV
124
            case Type::BUILTIN_TYPE_OBJECT:
×
125
                if (null !== ($className = $type->getClassName()) && is_a($className, \DateTimeInterface::class, true)) {
×
126
                    return \DateTimeInterface::class;
×
127
                }
128

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

135
    /**
136
     * Is the given property of the given resource class an identifier?
137
     */
138
    protected function isIdentifier(string $resourceClass, string $property, ?Operation $operation = null): bool
139
    {
140
        $identifier = 'id';
×
141
        if ($operation instanceof HttpOperation) {
×
142
            $uriVariable = $operation->getUriVariables()[0] ?? null;
×
143

144
            if ($uriVariable) {
×
145
                $identifier = $uriVariable->getIdentifiers()[0] ?? 'id';
×
146
            }
147
        }
148

149
        return $property === $identifier;
×
150
    }
151

152
    /**
153
     * Gets the ID from an IRI or a raw ID.
154
     */
155
    protected function getIdentifierValue(string $iri, string $property): mixed
156
    {
157
        try {
158
            $item = $this->iriConverter->getResourceFromIri($iri, ['fetch_data' => false]);
×
159

160
            return $this->propertyAccessor->getValue($item, $property);
×
161
        } catch (InvalidArgumentException) {
×
162
        }
163

164
        return $iri;
×
165
    }
166

167
    /**
168
     * Are the given values valid according to the given {@see Type}?
169
     */
170
    protected function hasValidValues(array $values, Type $type): bool
171
    {
172
        foreach ($values as $value) {
×
173
            if (
174
                null !== $value
×
175
                && Type::BUILTIN_TYPE_INT === $type->getBuiltinType()
×
176
                && false === filter_var($value, \FILTER_VALIDATE_INT)
×
177
            ) {
178
                return false;
×
179
            }
180
        }
181

182
        return true;
×
183
    }
184
}
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