Completed
Push — master ( e3d50f...dd2d49 )
by Antoine
20s queued 10s
created

DateFilter::addWhere()   C

Complexity

Conditions 11
Paths 8

Size

Total Lines 41
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 41
rs 5.2653
c 0
b 0
f 0
cc 11
eloc 27
nc 8
nop 8

How to fix   Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <[email protected]>
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\Core\Bridge\Doctrine\Orm\Filter;
15
16
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
17
use ApiPlatform\Core\Exception\InvalidArgumentException;
18
use Doctrine\DBAL\Types\Type;
19
use Doctrine\ORM\QueryBuilder;
20
21
/**
22
 * Filters the collection by date intervals.
23
 *
24
 * @author Kévin Dunglas <[email protected]>
25
 * @author Théo FIDRY <[email protected]>
26
 */
27
class DateFilter extends AbstractContextAwareFilter
28
{
29
    const PARAMETER_BEFORE = 'before';
30
    const PARAMETER_STRICTLY_BEFORE = 'strictly_before';
31
    const PARAMETER_AFTER = 'after';
32
    const PARAMETER_STRICTLY_AFTER = 'strictly_after';
33
    const EXCLUDE_NULL = 'exclude_null';
34
    const INCLUDE_NULL_BEFORE = 'include_null_before';
35
    const INCLUDE_NULL_AFTER = 'include_null_after';
36
    const INCLUDE_NULL_BEFORE_AND_AFTER = 'include_null_before_and_after';
37
    const DOCTRINE_DATE_TYPES = [
38
        'date' => true,
39
        'datetime' => true,
40
        'datetimetz' => true,
41
        'time' => true,
42
        'date_immutable' => true,
43
        'datetime_immutable' => true,
44
        'datetimetz_immutable' => true,
45
        'time_immutable' => true,
46
    ];
47
48
    /**
49
     * {@inheritdoc}
50
     */
51
    public function getDescription(string $resourceClass): array
52
    {
53
        $description = [];
54
55
        $properties = $this->properties;
56
        if (null === $properties) {
57
            $properties = array_fill_keys($this->getClassMetadata($resourceClass)->getFieldNames(), null);
58
        }
59
60
        foreach ($properties as $property => $nullManagement) {
61
            if (!$this->isPropertyMapped($property, $resourceClass) || !$this->isDateField($property, $resourceClass)) {
62
                continue;
63
            }
64
65
            $description += $this->getFilterDescription($property, self::PARAMETER_BEFORE);
66
            $description += $this->getFilterDescription($property, self::PARAMETER_STRICTLY_BEFORE);
67
            $description += $this->getFilterDescription($property, self::PARAMETER_AFTER);
68
            $description += $this->getFilterDescription($property, self::PARAMETER_STRICTLY_AFTER);
69
        }
70
71
        return $description;
72
    }
73
74
    /**
75
     * {@inheritdoc}
76
     */
77
    protected function filterProperty(string $property, $values, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
78
    {
79
        // Expect $values to be an array having the period as keys and the date value as values
80
        if (
81
            !\is_array($values) ||
82
            !$this->isPropertyEnabled($property, $resourceClass) ||
83
            !$this->isPropertyMapped($property, $resourceClass) ||
84
            !$this->isDateField($property, $resourceClass)
85
        ) {
86
            return;
87
        }
88
89
        $alias = $queryBuilder->getRootAliases()[0];
90
        $field = $property;
91
92
        if ($this->isPropertyNested($property, $resourceClass)) {
93
            list($alias, $field) = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass);
94
        }
95
96
        $nullManagement = $this->properties[$property] ?? null;
97
        $type = $this->getDoctrineFieldType($property, $resourceClass);
98
99
        if (self::EXCLUDE_NULL === $nullManagement) {
100
            $queryBuilder->andWhere($queryBuilder->expr()->isNotNull(sprintf('%s.%s', $alias, $field)));
101
        }
102
103
        if (isset($values[self::PARAMETER_BEFORE])) {
104
            $this->addWhere(
105
                $queryBuilder,
106
                $queryNameGenerator,
107
                $alias,
108
                $field,
109
                self::PARAMETER_BEFORE,
110
                $values[self::PARAMETER_BEFORE],
111
                $nullManagement,
112
                $type
113
            );
114
        }
115
116
        if (isset($values[self::PARAMETER_STRICTLY_BEFORE])) {
117
            $this->addWhere(
118
                $queryBuilder,
119
                $queryNameGenerator,
120
                $alias,
121
                $field,
122
                self::PARAMETER_STRICTLY_BEFORE,
123
                $values[self::PARAMETER_STRICTLY_BEFORE],
124
                $nullManagement,
125
                $type
126
            );
127
        }
128
129
        if (isset($values[self::PARAMETER_AFTER])) {
130
            $this->addWhere(
131
                $queryBuilder,
132
                $queryNameGenerator,
133
                $alias,
134
                $field,
135
                self::PARAMETER_AFTER,
136
                $values[self::PARAMETER_AFTER],
137
                $nullManagement,
138
                $type
139
            );
140
        }
141
142
        if (isset($values[self::PARAMETER_STRICTLY_AFTER])) {
143
            $this->addWhere(
144
                $queryBuilder,
145
                $queryNameGenerator,
146
                $alias,
147
                $field,
148
                self::PARAMETER_STRICTLY_AFTER,
149
                $values[self::PARAMETER_STRICTLY_AFTER],
150
                $nullManagement,
151
                $type
152
            );
153
        }
154
    }
155
156
    /**
157
     * Adds the where clause according to the chosen null management.
158
     *
159
     * @param QueryBuilder                $queryBuilder
160
     * @param QueryNameGeneratorInterface $queryNameGenerator
161
     * @param string                      $alias
162
     * @param string                      $field
163
     * @param string                      $operator
164
     * @param string                      $value
165
     * @param string|null                 $nullManagement
166
     * @param string|Type                 $type
167
     */
168
    protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, string $operator, string $value, string $nullManagement = null, $type = null)
169
    {
170
        try {
171
            $value = false === strpos($type, '_immutable') ? new \DateTime($value) : new \DateTimeImmutable($value);
172
        } catch (\Exception $e) {
173
            // Silently ignore this filter if it can not be transformed to a \DateTime
174
            $this->logger->notice('Invalid filter ignored', [
175
                'exception' => new InvalidArgumentException(sprintf('The field "%s" has a wrong date format. Use one accepted by the \DateTime constructor', $field)),
176
            ]);
177
178
            return;
179
        }
180
181
        $valueParameter = $queryNameGenerator->generateParameterName($field);
182
        $operatorValue = [
183
            self::PARAMETER_BEFORE => '<=',
184
            self::PARAMETER_STRICTLY_BEFORE => '<',
185
            self::PARAMETER_AFTER => '>=',
186
            self::PARAMETER_STRICTLY_AFTER => '>',
187
        ];
188
        $baseWhere = sprintf('%s.%s %s :%s', $alias, $field, $operatorValue[$operator], $valueParameter);
189
190
        if (null === $nullManagement || self::EXCLUDE_NULL === $nullManagement) {
191
            $queryBuilder->andWhere($baseWhere);
192
        } elseif (
193
            (self::INCLUDE_NULL_BEFORE === $nullManagement && \in_array($operator, [self::PARAMETER_BEFORE, self::PARAMETER_STRICTLY_BEFORE], true)) ||
194
            (self::INCLUDE_NULL_AFTER === $nullManagement && \in_array($operator, [self::PARAMETER_AFTER, self::PARAMETER_STRICTLY_AFTER], true)) ||
195
            (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))
196
        ) {
197
            $queryBuilder->andWhere($queryBuilder->expr()->orX(
198
                $baseWhere,
199
                $queryBuilder->expr()->isNull(sprintf('%s.%s', $alias, $field))
200
            ));
201
        } else {
202
            $queryBuilder->andWhere($queryBuilder->expr()->andX(
203
                $baseWhere,
204
                $queryBuilder->expr()->isNotNull(sprintf('%s.%s', $alias, $field))
205
            ));
206
        }
207
208
        $queryBuilder->setParameter($valueParameter, $value, $type);
209
    }
210
211
    /**
212
     * Determines whether the given property refers to a date field.
213
     *
214
     * @param string $property
215
     * @param string $resourceClass
216
     *
217
     * @return bool
218
     */
219
    protected function isDateField(string $property, string $resourceClass): bool
220
    {
221
        return isset(self::DOCTRINE_DATE_TYPES[$this->getDoctrineFieldType($property, $resourceClass)]);
222
    }
223
224
    /**
225
     * Gets filter description.
226
     *
227
     * @param string $property
228
     * @param string $period
229
     *
230
     * @return array
231
     */
232
    protected function getFilterDescription(string $property, string $period): array
233
    {
234
        return [
235
            sprintf('%s[%s]', $property, $period) => [
236
                'property' => $property,
237
                'type' => \DateTimeInterface::class,
238
                'required' => false,
239
            ],
240
        ];
241
    }
242
}
243