Completed
Push — master ( 29b346...1faa7f )
by Amrouche
19s
created

src/Bridge/Doctrine/Orm/Filter/DateFilter.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 Doctrine\ORM\QueryBuilder;
18
19
/**
20
 * Filters the collection by date intervals.
21
 *
22
 * @author Kévin Dunglas <[email protected]>
23
 * @author Théo FIDRY <[email protected]>
24
 */
25
class DateFilter extends AbstractFilter
26
{
27
    const PARAMETER_BEFORE = 'before';
28
    const PARAMETER_STRICTLY_BEFORE = 'strictly_before';
29
    const PARAMETER_AFTER = 'after';
30
    const PARAMETER_STRICTLY_AFTER = 'strictly_after';
31
    const EXCLUDE_NULL = 'exclude_null';
32
    const INCLUDE_NULL_BEFORE = 'include_null_before';
33
    const INCLUDE_NULL_AFTER = 'include_null_after';
34
    const DOCTRINE_DATE_TYPES = [
35
        'date' => true,
36
        'datetime' => true,
37
        'datetimetz' => true,
38
        'time' => true,
39
    ];
40
41
    /**
42
     * {@inheritdoc}
43
     */
44 View Code Duplication
    public function getDescription(string $resourceClass): array
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
45
    {
46
        $description = [];
47
48
        $properties = $this->properties;
49
        if (null === $properties) {
50
            $properties = array_fill_keys($this->getClassMetadata($resourceClass)->getFieldNames(), null);
51
        }
52
53
        foreach ($properties as $property => $nullManagement) {
54
            if (!$this->isPropertyMapped($property, $resourceClass) || !$this->isDateField($property, $resourceClass)) {
55
                continue;
56
            }
57
58
            $description += $this->getFilterDescription($property, self::PARAMETER_BEFORE);
59
            $description += $this->getFilterDescription($property, self::PARAMETER_STRICTLY_BEFORE);
60
            $description += $this->getFilterDescription($property, self::PARAMETER_AFTER);
61
            $description += $this->getFilterDescription($property, self::PARAMETER_STRICTLY_AFTER);
62
        }
63
64
        return $description;
65
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70
    protected function filterProperty(string $property, $values, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
71
    {
72
        // Expect $values to be an array having the period as keys and the date value as values
73 View Code Duplication
        if (
74
            !is_array($values) ||
75
            !$this->isPropertyEnabled($property, $resourceClass) ||
76
            !$this->isPropertyMapped($property, $resourceClass) ||
77
            !$this->isDateField($property, $resourceClass)
78
        ) {
79
            return;
80
        }
81
82
        $alias = 'o';
83
        $field = $property;
84
85 View Code Duplication
        if ($this->isPropertyNested($property, $resourceClass)) {
86
            list($alias, $field) = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass);
87
        }
88
89
        $nullManagement = $this->properties[$property] ?? null;
90
91
        if (self::EXCLUDE_NULL === $nullManagement) {
92
            $queryBuilder->andWhere($queryBuilder->expr()->isNotNull(sprintf('%s.%s', $alias, $field)));
93
        }
94
95 View Code Duplication
        if (isset($values[self::PARAMETER_BEFORE])) {
96
            $this->addWhere(
97
                $queryBuilder,
98
                $queryNameGenerator,
99
                $alias,
100
                $field,
101
                self::PARAMETER_BEFORE,
102
                $values[self::PARAMETER_BEFORE],
103
                $nullManagement
104
            );
105
        }
106
107 View Code Duplication
        if (isset($values[self::PARAMETER_STRICTLY_BEFORE])) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
108
            $this->addWhere(
109
                $queryBuilder,
110
                $queryNameGenerator,
111
                $alias,
112
                $field,
113
                self::PARAMETER_STRICTLY_BEFORE,
114
                $values[self::PARAMETER_STRICTLY_BEFORE],
115
                $nullManagement
116
            );
117
        }
118
119 View Code Duplication
        if (isset($values[self::PARAMETER_AFTER])) {
120
            $this->addWhere(
121
                $queryBuilder,
122
                $queryNameGenerator,
123
                $alias,
124
                $field,
125
                self::PARAMETER_AFTER,
126
                $values[self::PARAMETER_AFTER],
127
                $nullManagement
128
            );
129
        }
130
131 View Code Duplication
        if (isset($values[self::PARAMETER_STRICTLY_AFTER])) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
132
            $this->addWhere(
133
                $queryBuilder,
134
                $queryNameGenerator,
135
                $alias,
136
                $field,
137
                self::PARAMETER_STRICTLY_AFTER,
138
                $values[self::PARAMETER_STRICTLY_AFTER],
139
                $nullManagement
140
            );
141
        }
142
    }
143
144
    /**
145
     * Adds the where clause according to the chosen null management.
146
     *
147
     * @param QueryBuilder                $queryBuilder
148
     * @param QueryNameGeneratorInterface $queryNameGenerator
149
     * @param string                      $alias
150
     * @param string                      $field
151
     * @param string                      $operator
152
     * @param string                      $value
153
     * @param string|null                 $nullManagement
154
     */
155
    protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, string $operator, string $value, string $nullManagement = null)
156
    {
157
        $valueParameter = $queryNameGenerator->generateParameterName($field);
158
159
        $operatorValue = [
160
            self::PARAMETER_BEFORE => '<=',
161
            self::PARAMETER_STRICTLY_BEFORE => '<',
162
            self::PARAMETER_AFTER => '>=',
163
            self::PARAMETER_STRICTLY_AFTER => '>',
164
        ];
165
        $baseWhere = sprintf('%s.%s %s :%s', $alias, $field, $operatorValue[$operator], $valueParameter);
166
167
        if (null === $nullManagement || self::EXCLUDE_NULL === $nullManagement) {
168
            $queryBuilder->andWhere($baseWhere);
169
        } elseif (
170
            (in_array($operator, [self::PARAMETER_BEFORE, self::PARAMETER_STRICTLY_BEFORE], true) && self::INCLUDE_NULL_BEFORE === $nullManagement) ||
171
            (in_array($operator, [self::PARAMETER_AFTER, self::PARAMETER_STRICTLY_AFTER], true) && self::INCLUDE_NULL_AFTER === $nullManagement)
172
        ) {
173
            $queryBuilder->andWhere($queryBuilder->expr()->orX(
174
                $baseWhere,
175
                $queryBuilder->expr()->isNull(sprintf('%s.%s', $alias, $field))
176
            ));
177
        } else {
178
            $queryBuilder->andWhere($queryBuilder->expr()->andX(
179
                $baseWhere,
180
                $queryBuilder->expr()->isNotNull(sprintf('%s.%s', $alias, $field))
181
            ));
182
        }
183
184
        $queryBuilder->setParameter($valueParameter, new \DateTime($value));
185
    }
186
187
    /**
188
     * Determines whether the given property refers to a date field.
189
     *
190
     * @param string $property
191
     * @param string $resourceClass
192
     *
193
     * @return bool
194
     */
195 View Code Duplication
    protected function isDateField(string $property, string $resourceClass): bool
196
    {
197
        $propertyParts = $this->splitPropertyParts($property, $resourceClass);
198
        $metadata = $this->getNestedMetadata($resourceClass, $propertyParts['associations']);
199
200
        return isset(self::DOCTRINE_DATE_TYPES[$metadata->getTypeOfField($propertyParts['field'])]);
201
    }
202
203
    /**
204
     * Gets filter description.
205
     *
206
     * @param string $property
207
     * @param string $period
208
     *
209
     * @return array
210
     */
211
    protected function getFilterDescription(string $property, string $period): array
212
    {
213
        return [
214
            sprintf('%s[%s]', $property, $period) => [
215
                'property' => $property,
216
                'type' => \DateTimeInterface::class,
217
                'required' => false,
218
            ],
219
        ];
220
    }
221
}
222