FilterComponentFactory::isRangeFilter()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace BenTools\OpenCubes\Component\Filter;
4
5
use BenTools\OpenCubes\Component\ComponentFactoryInterface;
6
use BenTools\OpenCubes\Component\ComponentInterface;
7
use BenTools\OpenCubes\Component\Filter\Model\CollectionFilter;
8
use BenTools\OpenCubes\Component\Filter\Model\CompositeFilter;
9
use BenTools\OpenCubes\Component\Filter\Model\Filter;
10
use BenTools\OpenCubes\Component\Filter\Model\FilterValue;
11
use BenTools\OpenCubes\Component\Filter\Model\RangeFilter;
12
use BenTools\OpenCubes\Component\Filter\Model\SimpleFilter;
13
use BenTools\OpenCubes\Component\Filter\Model\StringMatchFilter;
14
use Psr\Http\Message\UriInterface;
15
use function BenTools\OpenCubes\contains_only_scalars;
16
use function BenTools\OpenCubes\is_indexed_array;
17
18
final class FilterComponentFactory implements ComponentFactoryInterface
19
{
20
    private const OPT_COLLECTION_SATISFIED_BY = 'collection_satisfied_by';
21
    private const OPT_COMPOSITE_SATISFIED_BY = 'composite_satisfied_by';
22
23
    /**
24
     * @var FilterUriManagerInterface
25
     */
26
    private $uriManager;
27
28
    /**
29
     * FilterComponentFactory constructor.
30
     * @param array                          $options - This component doesn't have options yet, but let's keep the same signature, just in case
31
     * @param FilterUriManagerInterface|null $uriManager
32
     * @throws \Symfony\Component\OptionsResolver\Exception\AccessException
33
     * @throws \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
34
     * @throws \Symfony\Component\OptionsResolver\Exception\MissingOptionsException
35
     * @throws \Symfony\Component\OptionsResolver\Exception\NoSuchOptionException
36
     * @throws \Symfony\Component\OptionsResolver\Exception\OptionDefinitionException
37
     * @throws \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException
38
     */
39
    public function __construct(array $options = [], FilterUriManagerInterface $uriManager = null)
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
40
    {
41
        $this->uriManager = $uriManager ?? new FilterUriManager();
42
    }
43
44
    /**
45
     * @inheritDoc
46
     */
47
    public function supports(string $name): bool
48
    {
49
        return FilterComponent::getName() === $name;
50
    }
51
52
    /**
53
     * @inheritDoc
54
     * @return FilterComponent
55
     */
56
    public function createComponent(UriInterface $uri, array $options = []): ComponentInterface
57
    {
58
        $rawFilters = $this->uriManager->getAppliedFilters($uri);
59
        $filters = [];
60
61
        foreach ($rawFilters as $field => $value) {
62
            $filters[] = $this->createFilter($field, $value, $uri, true, []);
63
        }
64
65
        return new FilterComponent($filters);
66
    }
67
68
    /**
69
     * @param string       $key
70
     * @param              $value
71
     * @param UriInterface $baseUri
72
     * @param bool         $applied
73
     * @param array        $options
74
     * @return Filter
75
     * @throws \InvalidArgumentException
76
     */
77
    private function createFilter(string $key, $value, UriInterface $baseUri, bool $applied, array $options): Filter
78
    {
79
        $options[self::OPT_COLLECTION_SATISFIED_BY] = $options[self::OPT_COLLECTION_SATISFIED_BY] ?? $this->uriManager->getOption(FilterUriManager::OPT_DEFAULT_COLLECTION_SATISFIED_BY);
80
        $options[self::OPT_COMPOSITE_SATISFIED_BY] = $options[self::OPT_COMPOSITE_SATISFIED_BY] ?? $this->uriManager->getOption(FilterUriManager::OPT_DEFAULT_COMPOSITE_SATISFIED_BY);
81
82
        if (is_array($value)) {
83
            if (is_indexed_array($value) && contains_only_scalars($value)) {
0 ignored issues
show
Documentation introduced by
$value is of type array, but the function expects a object<BenTools\OpenCubes\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
84
                if ($this->containsRangeFilter($value)) {
85
                    return $this->createCompositeFilter($key, array_map(function ($value) use ($key, $baseUri, $applied, $options) {
86
                        return $this->createFilter($key, $value, $baseUri, $applied, $options);
87
                    }, $value), $baseUri, $applied, $options);
88
                }
89
90
                return $this->createCollectionFilter($key, $value, $baseUri, $applied, $options);
91
            }
92
93
            if ($this->hasNegation($value)) {
94
                $negated = $value['NOT'];
95
                unset($value['NOT']);
96
97
                if ($this->hasSatisfiedByClause($negated, $satisfiedBy)) {
98
                    $negated = $negated[$satisfiedBy];
99
                    $options = array_replace($options, [self::OPT_COLLECTION_SATISFIED_BY => $satisfiedBy]);
100
                }
101
102
                $filter = $this->createFilter($key, $negated, $baseUri, $applied, $options)->negate();
103
                if (0 === count($value)) {
104
                    return $filter;
105
                }
106
107
                return $this->createCompositeFilter($key, array_merge([$this->createFilter($key, $value, $baseUri, $applied, $options)], [$filter]), $baseUri, $applied, $options);
108
            }
109
110
            if ($this->hasSatisfiedByClause($value, $satisfiedBy)) {
111
                return $this->createFilter($key, $value[$satisfiedBy], $baseUri, $applied, array_replace($options, [self::OPT_COLLECTION_SATISFIED_BY => $satisfiedBy]));
112
            }
113
114
            if ($this->hasMatchOperator($value, $operator)) {
115
                $value = $value[$operator];
116
                return $this->createStringMatchFilter($key, $value, $operator, $baseUri, $applied, $options);
117
            }
118
119
            throw new \InvalidArgumentException("Unable to parse filters.");
120
        }
121
122
        if ($this->isRangeFilter($value, $matches)) {
123
            return $this->createRangeFilter($key, $matches[2], $matches[3], $baseUri, $applied, $options);
124
        }
125
126
        return $this->createSimpleFilter($key, $value, $baseUri, $applied, $options);
127
    }
128
129
    /**
130
     * @param string       $key
131
     * @param array        $filters
132
     * @param UriInterface $baseUri
133
     * @param bool         $applied
134
     * @param array        $options
135
     * @return Filter
136
     * @throws \InvalidArgumentException
137
     */
138
    private function createCompositeFilter(string $key, array $filters, UriInterface $baseUri, bool $applied, array $options): Filter
139
    {
140
        $filter = new CompositeFilter($key, $filters, $options[self::OPT_COMPOSITE_SATISFIED_BY] ?? CompositeFilter::SATISFIED_BY_ALL);
141
        $filter->setApplied($applied);
142
        $filter->setToggleUri($applied ? $this->uriManager->buildRemoveFilterUrl($baseUri, $filter) : $this->uriManager->buildApplyFilterUrl($baseUri, $filter));
143
        return $filter;
144
    }
145
146
    /**
147
     * @param string       $key
148
     * @param              $left
149
     * @param              $right
150
     * @param UriInterface $baseUri
151
     * @param bool         $applied
152
     * @param array        $options
153
     * @return Filter
154
     */
155
    private function createRangeFilter(string $key, $left, $right, UriInterface $baseUri, bool $applied, array $options): Filter
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
156
    {
157
        $left = '*' === $left ? null : $left;
158
        $right = '*' === $right ? null : $right;
159
        $filter = new RangeFilter($key, $left, $right);
160
        $filter->setApplied($applied);
161
        $filter->setToggleUri($applied ? $this->uriManager->buildRemoveFilterUrl($baseUri, $filter) : $this->uriManager->buildApplyFilterUrl($baseUri, $filter, [$left, $right]));
162
        return $filter;
163
    }
164
165
    /**
166
     * @param string       $key
167
     * @param              $value
168
     * @param UriInterface $baseUri
169
     * @param bool         $applied
170
     * @param array        $options
171
     * @return Filter
172
     */
173
    private function createSimpleFilter(string $key, $value, UriInterface $baseUri, bool $applied, array $options): Filter
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
174
    {
175
        if ('NULL' === $value) {
176
            $value = null;
177
        }
178
        $filterValue = new FilterValue($value, $value, true);
179
        $filter = new SimpleFilter($key, $filterValue);
180
        $filter->setApplied($applied);
181
        $filter->setToggleUri($applied ? $this->uriManager->buildRemoveFilterUrl($baseUri, $filter) : $this->uriManager->buildApplyFilterUrl($baseUri, $filter, $value));
182
        $filterValue->setToggleUri($this->uriManager->buildRemoveFilterUrl($baseUri, $filter, $value));
183
        return $filter;
184
    }
185
186
    /**
187
     * @param              $key
188
     * @param              $value
189
     * @param              $operator
190
     * @param UriInterface $baseUri
191
     * @param bool         $applied
192
     * @param array        $options
193
     * @return Filter
194
     * @throws \InvalidArgumentException
195
     */
196
    private function createStringMatchFilter($key, $value, $operator, UriInterface $baseUri, bool $applied, array $options): Filter
197
    {
198
        if (is_array($value)) {
199
            return $this->createCompositeFilter($key, array_map(function ($value) use ($key, $baseUri, $applied, $options) {
200
                return $this->createFilter($key, $value, $baseUri, $applied, $options);
201
            }, $value), $baseUri, $applied, $options);
202
        }
203
        $filterValue = new FilterValue($value, $value, $applied);
204
        $filter = new StringMatchFilter($key, $filterValue, $operator);
205
        $filter->setApplied($applied);
206
        $filter->setToggleUri($applied ? $this->uriManager->buildRemoveFilterUrl($baseUri, $filter) : $this->uriManager->buildApplyFilterUrl($baseUri, $filter));
207
        $filterValue->setToggleUri($this->uriManager->buildRemoveFilterUrl($baseUri, $filter, $value));
208
        return $filter;
209
    }
210
211
    /**
212
     * @param string       $key
213
     * @param array        $values
214
     * @param UriInterface $baseUri
215
     * @param bool         $applied
216
     * @param array        $options
217
     * @return Filter
218
     * @throws \InvalidArgumentException
219
     */
220
    private function createCollectionFilter(string $key, array $values, UriInterface $baseUri, bool $applied, array $options): Filter
221
    {
222
        $values = array_values($values);
223
        $filterValues = array_map(function ($value) {
224
            return new FilterValue($value, $value, true);
225
        }, $values);
226
        $filter = new CollectionFilter($key, $filterValues, $options[self::OPT_COLLECTION_SATISFIED_BY] ?? CollectionFilter::SATISFIED_BY_ANY);
227
        $filter->setApplied($applied);
228
        $filter->setToggleUri($applied ? $this->uriManager->buildRemoveFilterUrl($baseUri, $filter) : $this->uriManager->buildApplyFilterUrl($baseUri, $filter, $values));
229
        return $filter;
230
    }
231
232
    /**
233
     * @param $value
234
     * @return bool
235
     */
236
    private function hasNegation($value): bool
237
    {
238
        return is_array($value) && array_key_exists('NOT', $value);
239
    }
240
241
    /**
242
     * @param $value
243
     * @return bool
244
     */
245
    private function hasSatisfiedByClause($value, &$satisfiedClause = null): bool
246
    {
247
        if (is_array($value) && (array_key_exists(CollectionFilter::SATISFIED_BY_ALL, $value) || array_key_exists(CollectionFilter::SATISFIED_BY_ANY, $value))) {
248
            $satisfiedClause = array_key_exists(CollectionFilter::SATISFIED_BY_ALL, $value) ? CollectionFilter::SATISFIED_BY_ALL : CollectionFilter::SATISFIED_BY_ANY;
249
250
            return true;
251
        }
252
        return false;
253
    }
254
255
    /**
256
     * @param      $value
257
     * @param null $operator
258
     * @return bool
259
     */
260
    private function hasMatchOperator($value, &$operator = null): bool
261
    {
262
        if (is_array($value)) {
263
            foreach (array_keys($value) as $key) {
264
                if (in_array($key, StringMatchFilter::OPERATORS)) {
265
                    $operator = $key;
266
                    return true;
267
                }
268
            }
269
        }
270
271
        return false;
272
    }
273
274
    /**
275
     * @param      $value
276
     * @param null $matches
277
     * @return bool
278
     */
279
    private function isRangeFilter($value, &$matches = null): bool
280
    {
281
        return preg_match('/(^\[(.*) TO (.*)\]$)/', $value, $matches);
282
    }
283
284
    /**
285
     * @param array $values
286
     * @return bool
287
     */
288
    private function containsRangeFilter(array $values): bool
289
    {
290
        foreach ($values as $value) {
291
            if ($this->isRangeFilter($value)) {
292
                return true;
293
            }
294
        }
295
296
        return false;
297
    }
298
}
299