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

api-platform / core / 15133993414

20 May 2025 09:30AM UTC coverage: 26.313% (-1.2%) from 27.493%
15133993414

Pull #7161

github

web-flow
Merge e2c03d45f into 5459ba375
Pull Request #7161: fix(metadata): infer parameter string type from schema

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

11019 existing lines in 363 files now uncovered.

12898 of 49018 relevant lines covered (26.31%)

34.33 hits per line

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

96.83
/src/Doctrine/Common/Filter/SearchFilterTrait.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\Doctrine\Common\Filter;
15

16
use ApiPlatform\Doctrine\Common\PropertyHelperTrait;
17
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
18
use ApiPlatform\Metadata\IdentifiersExtractorInterface;
19
use ApiPlatform\Metadata\IriConverterInterface;
20
use Psr\Log\LoggerInterface;
21
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
22

23
/**
24
 * Trait for filtering the collection by given properties.
25
 *
26
 * @author Kévin Dunglas <dunglas@gmail.com>
27
 * @author Alan Poulain <contact@alanpoulain.eu>
28
 */
29
trait SearchFilterTrait
30
{
31
    use PropertyHelperTrait;
32

33
    protected IriConverterInterface $iriConverter;
34
    protected PropertyAccessorInterface $propertyAccessor;
35
    protected ?IdentifiersExtractorInterface $identifiersExtractor = null;
36

37
    /**
38
     * {@inheritdoc}
39
     */
40
    public function getDescription(string $resourceClass): array
41
    {
UNCOV
42
        $description = [];
241✔
43

UNCOV
44
        $properties = $this->getProperties();
241✔
UNCOV
45
        if (null === $properties) {
241✔
46
            $properties = array_fill_keys($this->getClassMetadata($resourceClass)->getFieldNames(), null);
×
47
        }
48

UNCOV
49
        foreach ($properties as $property => $strategy) {
241✔
UNCOV
50
            if (!$this->isPropertyMapped($property, $resourceClass, true)) {
241✔
UNCOV
51
                continue;
225✔
52
            }
53

UNCOV
54
            if ($this->isPropertyNested($property, $resourceClass)) {
241✔
UNCOV
55
                $propertyParts = $this->splitPropertyParts($property, $resourceClass);
231✔
UNCOV
56
                $field = $propertyParts['field'];
231✔
UNCOV
57
                $metadata = $this->getNestedMetadata($resourceClass, $propertyParts['associations']);
231✔
58
            } else {
UNCOV
59
                $field = $property;
240✔
UNCOV
60
                $metadata = $this->getClassMetadata($resourceClass);
240✔
61
            }
62

UNCOV
63
            $propertyName = $this->normalizePropertyName($property);
241✔
UNCOV
64
            if ($metadata->hasField($field)) {
241✔
UNCOV
65
                $typeOfField = $this->getType($metadata->getTypeOfField($field));
241✔
UNCOV
66
                $strategy = $this->getProperties()[$property] ?? self::STRATEGY_EXACT;
241✔
UNCOV
67
                $filterParameterNames = [$propertyName];
241✔
68

UNCOV
69
                if (\in_array($strategy, [self::STRATEGY_EXACT, self::STRATEGY_IEXACT], true)) {
241✔
UNCOV
70
                    $filterParameterNames[] = $propertyName.'[]';
239✔
71
                }
72

UNCOV
73
                foreach ($filterParameterNames as $filterParameterName) {
241✔
UNCOV
74
                    $description[$filterParameterName] = [
241✔
UNCOV
75
                        'property' => $propertyName,
241✔
UNCOV
76
                        'type' => $typeOfField,
241✔
UNCOV
77
                        'required' => false,
241✔
UNCOV
78
                        'strategy' => $strategy,
241✔
UNCOV
79
                        'is_collection' => str_ends_with((string) $filterParameterName, '[]'),
241✔
UNCOV
80
                    ];
241✔
81
                }
UNCOV
82
            } elseif ($metadata->hasAssociation($field)) {
18✔
UNCOV
83
                $filterParameterNames = [
18✔
UNCOV
84
                    $propertyName,
18✔
UNCOV
85
                    $propertyName.'[]',
18✔
UNCOV
86
                ];
18✔
87

UNCOV
88
                foreach ($filterParameterNames as $filterParameterName) {
18✔
UNCOV
89
                    $description[$filterParameterName] = [
18✔
UNCOV
90
                        'property' => $propertyName,
18✔
UNCOV
91
                        'type' => 'string',
18✔
UNCOV
92
                        'required' => false,
18✔
UNCOV
93
                        'strategy' => self::STRATEGY_EXACT,
18✔
UNCOV
94
                        'is_collection' => str_ends_with((string) $filterParameterName, '[]'),
18✔
UNCOV
95
                    ];
18✔
96
                }
97
            }
98
        }
99

UNCOV
100
        return $description;
241✔
101
    }
102

103
    /**
104
     * Converts a Doctrine type in PHP type.
105
     */
106
    abstract protected function getType(string $doctrineType): string;
107

108
    abstract protected function getProperties(): ?array;
109

110
    abstract protected function getLogger(): LoggerInterface;
111

112
    abstract protected function getIriConverter(): IriConverterInterface;
113

114
    abstract protected function getPropertyAccessor(): PropertyAccessorInterface;
115

116
    abstract protected function normalizePropertyName(string $property): string;
117

118
    /**
119
     * Gets the ID from an IRI or a raw ID.
120
     */
121
    protected function getIdFromValue(string $value): mixed
122
    {
123
        try {
124
            $iriConverter = $this->getIriConverter();
8✔
125
            $item = $iriConverter->getResourceFromIri($value, ['fetch_data' => false]);
8✔
126

127
            if (null === $this->identifiersExtractor) {
4✔
128
                return $this->getPropertyAccessor()->getValue($item, 'id');
×
129
            }
130

131
            $identifiers = $this->identifiersExtractor->getIdentifiersFromItem($item);
4✔
132

133
            return 1 === \count($identifiers) ? array_pop($identifiers) : $identifiers;
4✔
134
        } catch (InvalidArgumentException) {
4✔
135
            // Do nothing, return the raw value
136
        }
137

138
        return $value;
4✔
139
    }
140

141
    /**
142
     * Normalize the values array.
143
     */
144
    protected function normalizeValues(array $values, string $property): ?array
145
    {
UNCOV
146
        foreach ($values as $key => $value) {
42✔
UNCOV
147
            if (!\is_int($key) || !(\is_string($value) || \is_int($value))) {
42✔
148
                unset($values[$key]);
1✔
149
            }
150
        }
151

UNCOV
152
        if (empty($values)) {
42✔
153
            $this->getLogger()->notice('Invalid filter ignored', [
1✔
154
                'exception' => new InvalidArgumentException(\sprintf('At least one value is required, multiple values should be in "%1$s[]=firstvalue&%1$s[]=secondvalue" format', $property)),
1✔
155
            ]);
1✔
156

157
            return null;
1✔
158
        }
159

UNCOV
160
        return array_values($values);
41✔
161
    }
162

163
    /**
164
     * When the field should be an integer, check that the given value is a valid one.
165
     */
166
    protected function hasValidValues(array $values, ?string $type = null): bool
167
    {
UNCOV
168
        foreach ($values as $value) {
39✔
UNCOV
169
            if (null !== $value && \in_array($type, (array) self::DOCTRINE_INTEGER_TYPE, true) && false === filter_var($value, \FILTER_VALIDATE_INT)) {
39✔
170
                return false;
1✔
171
            }
172
        }
173

UNCOV
174
        return true;
38✔
175
    }
176
}
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

© 2025 Coveralls, Inc