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

api-platform / core / 14635100171

24 Apr 2025 06:39AM UTC coverage: 8.271% (+0.02%) from 8.252%
14635100171

Pull #6904

github

web-flow
Merge c9cefd82e into a3e5e53ea
Pull Request #6904: feat(graphql): added support for graphql subscriptions to work for actions

0 of 73 new or added lines in 3 files covered. (0.0%)

1999 existing lines in 144 files now uncovered.

13129 of 158728 relevant lines covered (8.27%)

13.6 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
    {
42
        $description = [];
250✔
43

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

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

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

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

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

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

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

100
        return $description;
250✔
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 {
UNCOV
124
            $iriConverter = $this->getIriConverter();
8✔
UNCOV
125
            $item = $iriConverter->getResourceFromIri($value, ['fetch_data' => false]);
8✔
126

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

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

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

UNCOV
138
        return $value;
4✔
139
    }
140

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

152
        if (empty($values)) {
44✔
UNCOV
153
            $this->getLogger()->notice('Invalid filter ignored', [
1✔
UNCOV
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✔
UNCOV
155
            ]);
1✔
156

UNCOV
157
            return null;
1✔
158
        }
159

160
        return array_values($values);
43✔
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
    {
168
        foreach ($values as $value) {
41✔
169
            if (null !== $value && \in_array($type, (array) self::DOCTRINE_INTEGER_TYPE, true) && false === filter_var($value, \FILTER_VALIDATE_INT)) {
41✔
UNCOV
170
                return false;
1✔
171
            }
172
        }
173

174
        return true;
40✔
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