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

api-platform / core / 13814792797

12 Mar 2025 03:09PM UTC coverage: 5.889% (-1.4%) from 7.289%
13814792797

Pull #7012

github

web-flow
Merge 199d44919 into 284937039
Pull Request #7012: doc: comment typo in ApiResource.php

10048 of 170615 relevant lines covered (5.89%)

5.17 hits per line

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

0.0
/src/Doctrine/Odm/Filter/DateFilter.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\Odm\Filter;
15

16
use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface;
17
use ApiPlatform\Doctrine\Common\Filter\DateFilterTrait;
18
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
19
use ApiPlatform\Metadata\Operation;
20
use Doctrine\ODM\MongoDB\Aggregation\Builder;
21
use Doctrine\ODM\MongoDB\Types\Type as MongoDbType;
22

23
/**
24
 * The date filter allows to filter a collection by date intervals.
25
 *
26
 * Syntax: `?property[<after|before|strictly_after|strictly_before>]=value`.
27
 *
28
 * The value can take any date format supported by the [`\DateTime` constructor](https://www.php.net/manual/en/datetime.construct.php).
29
 *
30
 * The `after` and `before` filters will filter including the value whereas `strictly_after` and `strictly_before` will filter excluding the value.
31
 *
32
 * The date filter is able to deal with date properties having `null` values. Four behaviors are available at the property level of the filter:
33
 * - Use the default behavior of the DBMS: use `null` strategy
34
 * - Exclude items: use `ApiPlatform\Doctrine\Odm\Filter\DateFilter::EXCLUDE_NULL` (`exclude_null`) strategy
35
 * - Consider items as oldest: use `ApiPlatform\Doctrine\Odm\Filter\DateFilter::INCLUDE_NULL_BEFORE` (`include_null_before`) strategy
36
 * - Consider items as youngest: use `ApiPlatform\Doctrine\Odm\Filter\DateFilter::INCLUDE_NULL_AFTER` (`include_null_after`) strategy
37
 * - Always include items: use `ApiPlatform\Doctrine\Odm\Filter\DateFilter::INCLUDE_NULL_BEFORE_AND_AFTER` (`include_null_before_and_after`) strategy
38
 *
39
 * <div data-code-selector>
40
 *
41
 * ```php
42
 * <?php
43
 * // api/src/Entity/Book.php
44
 * use ApiPlatform\Metadata\ApiFilter;
45
 * use ApiPlatform\Metadata\ApiResource;
46
 * use ApiPlatform\Doctrine\Odm\Filter\DateFilter;
47
 *
48
 * #[ApiResource]
49
 * #[ApiFilter(DateFilter::class, properties: ['createdAt'])]
50
 * class Book
51
 * {
52
 *     // ...
53
 * }
54
 * ```
55
 *
56
 * ```yaml
57
 * # config/services.yaml
58
 * services:
59
 *     book.date_filter:
60
 *         parent: 'api_platform.doctrine.odm.date_filter'
61
 *         arguments: [ { createdAt: ~ } ]
62
 *         tags:  [ 'api_platform.filter' ]
63
 *         # The following are mandatory only if a _defaults section is defined with inverted values.
64
 *         # You may want to isolate filters in a dedicated file to avoid adding the following lines (by adding them in the defaults section)
65
 *         autowire: false
66
 *         autoconfigure: false
67
 *         public: false
68
 *
69
 * # api/config/api_platform/resources.yaml
70
 * resources:
71
 *     App\Entity\Book:
72
 *         - operations:
73
 *               ApiPlatform\Metadata\GetCollection:
74
 *                   filters: ['book.date_filter']
75
 * ```
76
 *
77
 * ```xml
78
 * <!-- api/config/services.xml -->
79
 * <?xml version="1.0" encoding="UTF-8" ?>
80
 * <container
81
 *         xmlns="http://symfony.com/schema/dic/services"
82
 *         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
83
 *         xsi:schemaLocation="http://symfony.com/schema/dic/services
84
 *         https://symfony.com/schema/dic/services/services-1.0.xsd">
85
 *     <services>
86
 *         <service id="book.date_filter" parent="api_platform.doctrine.odm.date_filter">
87
 *             <argument type="collection">
88
 *                 <argument key="createdAt"></argument>
89
 *             </argument>
90
 *             <tag name="api_platform.filter"/>
91
 *         </service>
92
 *     </services>
93
 * </container>
94
 * <!-- api/config/api_platform/resources.xml -->
95
 * <resources
96
 *         xmlns="https://api-platform.com/schema/metadata/resources-3.0"
97
 *         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
98
 *         xsi:schemaLocation="https://api-platform.com/schema/metadata/resources-3.0
99
 *         https://api-platform.com/schema/metadata/resources-3.0.xsd">
100
 *     <resource class="App\Entity\Book">
101
 *         <operations>
102
 *             <operation class="ApiPlatform\Metadata\GetCollection">
103
 *                 <filters>
104
 *                     <filter>book.date_filter</filter>
105
 *                 </filters>
106
 *             </operation>
107
 *         </operations>
108
 *     </resource>
109
 * </resources>
110
 * ```
111
 *
112
 * </div>
113
 *
114
 * Given that the collection endpoint is `/books`, you can filter books by date with the following query: `/books?createdAt[after]=2018-03-19`.
115
 *
116
 * @author Kévin Dunglas <dunglas@gmail.com>
117
 * @author Théo FIDRY <theo.fidry@gmail.com>
118
 * @author Alan Poulain <contact@alanpoulain.eu>
119
 */
120
final class DateFilter extends AbstractFilter implements DateFilterInterface
121
{
122
    use DateFilterTrait;
123

124
    public const DOCTRINE_DATE_TYPES = [
125
        MongoDbType::DATE => true,
126
        MongoDbType::DATE_IMMUTABLE => true,
127
    ];
128

129
    /**
130
     * {@inheritdoc}
131
     */
132
    protected function filterProperty(string $property, $values, Builder $aggregationBuilder, string $resourceClass, ?Operation $operation = null, array &$context = []): void
133
    {
134
        // Expect $values to be an array having the period as keys and the date value as values
135
        if (
136
            !\is_array($values)
×
137
            || !$this->isPropertyEnabled($property, $resourceClass)
×
138
            || !$this->isPropertyMapped($property, $resourceClass)
×
139
            || !$this->isDateField($property, $resourceClass)
×
140
        ) {
141
            return;
×
142
        }
143

144
        $matchField = $property;
×
145

146
        if ($this->isPropertyNested($property, $resourceClass)) {
×
147
            [$matchField] = $this->addLookupsForNestedProperty($property, $aggregationBuilder, $resourceClass);
×
148
        }
149

150
        $nullManagement = $this->properties[$property] ?? null;
×
151

152
        if (self::EXCLUDE_NULL === $nullManagement) {
×
153
            $aggregationBuilder->match()->field($matchField)->notEqual(null);
×
154
        }
155

156
        if (isset($values[self::PARAMETER_BEFORE])) {
×
157
            $this->addMatch(
×
158
                $aggregationBuilder,
×
159
                $matchField,
×
160
                self::PARAMETER_BEFORE,
×
161
                $values[self::PARAMETER_BEFORE],
×
162
                $nullManagement
×
163
            );
×
164
        }
165

166
        if (isset($values[self::PARAMETER_STRICTLY_BEFORE])) {
×
167
            $this->addMatch(
×
168
                $aggregationBuilder,
×
169
                $matchField,
×
170
                self::PARAMETER_STRICTLY_BEFORE,
×
171
                $values[self::PARAMETER_STRICTLY_BEFORE],
×
172
                $nullManagement
×
173
            );
×
174
        }
175

176
        if (isset($values[self::PARAMETER_AFTER])) {
×
177
            $this->addMatch(
×
178
                $aggregationBuilder,
×
179
                $matchField,
×
180
                self::PARAMETER_AFTER,
×
181
                $values[self::PARAMETER_AFTER],
×
182
                $nullManagement
×
183
            );
×
184
        }
185

186
        if (isset($values[self::PARAMETER_STRICTLY_AFTER])) {
×
187
            $this->addMatch(
×
188
                $aggregationBuilder,
×
189
                $matchField,
×
190
                self::PARAMETER_STRICTLY_AFTER,
×
191
                $values[self::PARAMETER_STRICTLY_AFTER],
×
192
                $nullManagement
×
193
            );
×
194
        }
195
    }
196

197
    /**
198
     * Adds the match stage according to the chosen null management.
199
     */
200
    private function addMatch(Builder $aggregationBuilder, string $field, string $operator, $value, ?string $nullManagement = null): void
201
    {
202
        $value = $this->normalizeValue($value, $operator);
×
203

204
        if (null === $value) {
×
205
            return;
×
206
        }
207

208
        try {
209
            $value = new \DateTime($value);
×
210
        } catch (\Exception) {
×
211
            // Silently ignore this filter if it can not be transformed to a \DateTime
212
            $this->logger->notice('Invalid filter ignored', [
×
213
                'exception' => new InvalidArgumentException(\sprintf('The field "%s" has a wrong date format. Use one accepted by the \DateTime constructor', $field)),
×
214
            ]);
×
215

216
            return;
×
217
        }
218

219
        $operatorValue = [
×
220
            self::PARAMETER_BEFORE => '$lte',
×
221
            self::PARAMETER_STRICTLY_BEFORE => '$lt',
×
222
            self::PARAMETER_AFTER => '$gte',
×
223
            self::PARAMETER_STRICTLY_AFTER => '$gt',
×
224
        ];
×
225

226
        if ((self::INCLUDE_NULL_BEFORE === $nullManagement && \in_array($operator, [self::PARAMETER_BEFORE, self::PARAMETER_STRICTLY_BEFORE], true))
×
227
            || (self::INCLUDE_NULL_AFTER === $nullManagement && \in_array($operator, [self::PARAMETER_AFTER, self::PARAMETER_STRICTLY_AFTER], true))
×
228
            || (self::INCLUDE_NULL_BEFORE_AND_AFTER === $nullManagement && \in_array($operator, [self::PARAMETER_AFTER, self::PARAMETER_STRICTLY_AFTER, self::PARAMETER_BEFORE, self::PARAMETER_STRICTLY_BEFORE], true))
×
229
        ) {
230
            $aggregationBuilder->match()->addOr(
×
231
                $aggregationBuilder->matchExpr()->field($field)->operator($operatorValue[$operator], $value),
×
232
                $aggregationBuilder->matchExpr()->field($field)->equals(null)
×
233
            );
×
234

235
            return;
×
236
        }
237

238
        $aggregationBuilder->match()->addAnd($aggregationBuilder->matchExpr()->field($field)->operator($operatorValue[$operator], $value));
×
239
    }
240
}
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