Completed
Pull Request — master (#203)
by
unknown
61:13
created

DynamicAggregate::getState()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 2
nop 1
1
<?php
2
3
/*
4
 * This file is part of the ONGR package.
5
 *
6
 * (c) NFQ Technologies UAB <[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
namespace ONGR\FilterManagerBundle\Filter\Widget\Dynamic;
13
14
use ONGR\ElasticsearchBundle\Result\Aggregation\AggregationValue;
15
use ONGR\ElasticsearchDSL\Aggregation\FilterAggregation;
16
use ONGR\ElasticsearchDSL\Aggregation\NestedAggregation;
17
use ONGR\ElasticsearchDSL\Aggregation\TermsAggregation;
18
use ONGR\ElasticsearchDSL\BuilderInterface;
19
use ONGR\ElasticsearchDSL\Query\BoolQuery;
20
use ONGR\ElasticsearchDSL\Query\MatchAllQuery;
21
use ONGR\ElasticsearchDSL\Query\NestedQuery;
22
use ONGR\ElasticsearchDSL\Query\TermQuery;
23
use ONGR\ElasticsearchDSL\Search;
24
use ONGR\ElasticsearchBundle\Result\DocumentIterator;
25
use ONGR\FilterManagerBundle\Filter\FilterState;
26
use ONGR\FilterManagerBundle\Filter\Helper\SizeAwareTrait;
27
use ONGR\FilterManagerBundle\Filter\Helper\ViewDataFactoryInterface;
28
use ONGR\FilterManagerBundle\Filter\ViewData\AggregateViewData;
29
use ONGR\FilterManagerBundle\Filter\ViewData;
30
use ONGR\FilterManagerBundle\Filter\Widget\AbstractSingleRequestValueFilter;
31
use ONGR\FilterManagerBundle\Filter\Helper\FieldAwareInterface;
32
use ONGR\FilterManagerBundle\Filter\Helper\FieldAwareTrait;
33
use ONGR\FilterManagerBundle\Search\SearchRequest;
34
use Symfony\Component\HttpFoundation\Request;
35
36
/**
37
 * This class provides single terms choice.
38
 */
39
class DynamicAggregate extends AbstractSingleRequestValueFilter implements
0 ignored issues
show
Deprecated Code introduced by
The class ONGR\FilterManagerBundle...ingleRequestValueFilter has been deprecated with message: Will be renamed to AbstractFilter.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
40
    FieldAwareInterface,
0 ignored issues
show
Deprecated Code introduced by
The interface ONGR\FilterManagerBundle...per\FieldAwareInterface has been deprecated with message: FieldAwareInterface will be changed to DocumentFieldAwareInterface in 2.0

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
41
    ViewDataFactoryInterface
42
{
43
    use FieldAwareTrait;
0 ignored issues
show
Deprecated Code introduced by
The trait ONGR\FilterManagerBundle...\Helper\FieldAwareTrait has been deprecated with message: FieldAwareTrait will be changed to DocumentFieldAwareTrait in 2.0

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
44
45
    /**
46
     * @var array
47
     */
48
    private $sortType;
49
50
    /**
51
     * @var string
52
     */
53
    private $nameField;
54
55
    /**
56
     * @var bool
57
     */
58
    private $showZeroChoices;
59
60
    /**
61
     * @param array $sortType
62
     */
63
    public function setSortType($sortType)
64
    {
65
        $this->sortType = $sortType;
66
    }
67
68
    /**
69
     * @return array
70
     */
71
    public function getSortType()
72
    {
73
        return $this->sortType;
74
    }
75
76
    /**
77
     * @return string
78
     */
79
    public function getNameField()
80
    {
81
        return $this->nameField;
82
    }
83
84
    /**
85
     * @param string $nameField
86
     */
87
    public function setNameField($nameField)
88
    {
89
        $this->nameField = $nameField;
90
    }
91
92
    /**
93
     * @return bool
94
     */
95
    public function getShowZeroChoices()
96
    {
97
        return $this->showZeroChoices;
98
    }
99
100
    /**
101
     * @param bool $showZeroChoices
102
     */
103
    public function setShowZeroChoices($showZeroChoices)
104
    {
105
        $this->showZeroChoices = $showZeroChoices;
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     */
111
    public function getState(Request $request)
112
    {
113
        $state = new FilterState();
114
        $value = $request->get($this->getRequestField());
115
116
        if (isset($value) && is_array($value)) {
117
            $state->setActive(true);
118
            $state->setValue($value);
119
            $state->setUrlParameters([$this->getRequestField() => $value]);
120
        }
121
122
        return $state;
123
    }
124
125
    /**
126
     * {@inheritdoc}
127
     */
128
    public function modifySearch(Search $search, FilterState $state = null, SearchRequest $request = null)
129
    {
130
        list($path, $field) = explode('>', $this->getField());
131
132
        if ($state && $state->isActive()) {
133
            $boolQuery = new BoolQuery();
134
            foreach ($state->getValue() as $groupName => $value) {
135
                $innerBoolQuery = new BoolQuery();
136
                $nestedQuery = new NestedQuery($path, $innerBoolQuery);
137
                $innerBoolQuery->add(
138
                    new TermQuery($field, $value)
139
                );
140
                $innerBoolQuery->add(
141
                    new TermQuery($this->getNameField(), $groupName)
142
                );
143
                $boolQuery->add($nestedQuery);
144
            }
145
            $search->addPostFilter($boolQuery);
146
        }
147
    }
148
149
    /**
150
     * {@inheritdoc}
151
     */
152
    public function preProcessSearch(Search $search, Search $relatedSearch, FilterState $state = null)
153
    {
154
        list($path, $field) = explode('>', $this->getField());
155
        $filter = !empty($filter = $relatedSearch->getPostFilters()) ? $filter : new MatchAllQuery();
156
        $aggregation = new NestedAggregation($state->getName(), $path);
0 ignored issues
show
Bug introduced by
It seems like $state is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
Deprecated Code introduced by
The class ONGR\ElasticsearchDSL\Ag...ation\NestedAggregation has been deprecated with message: Aggregations was moved to it's type namespace. Add `Metric` or `Bucketing` after `Aggregation`. This class will be removed in 3.0.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
157
        $nameAggregation = new TermsAggregation('name', $this->getNameField());
0 ignored issues
show
Deprecated Code introduced by
The class ONGR\ElasticsearchDSL\Aggregation\TermsAggregation has been deprecated with message: Aggregations was moved to it's type namespace. Add `Metric` or `Bucketing` after `Aggregation`. This class will be removed in 3.0.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
158
        $valueAggregation = new TermsAggregation('value', $field);
0 ignored issues
show
Deprecated Code introduced by
The class ONGR\ElasticsearchDSL\Aggregation\TermsAggregation has been deprecated with message: Aggregations was moved to it's type namespace. Add `Metric` or `Bucketing` after `Aggregation`. This class will be removed in 3.0.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
159
        $filterAggregation = new FilterAggregation($state->getName() . '-filter');
0 ignored issues
show
Deprecated Code introduced by
The class ONGR\ElasticsearchDSL\Ag...ation\FilterAggregation has been deprecated with message: Aggregations was moved to it's type namespace. Add `Metric` or `Bucketing` after `Aggregation`. This class will be removed in 3.0.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
160
        $nameAggregation->addAggregation($valueAggregation);
161
        $aggregation->addAggregation($nameAggregation);
162
        $filterAggregation->setFilter($filter);
163
164
        if ($this->getSortType()) {
165
            $valueAggregation->addParameter('order', [$this->getSortType()['type'] => $this->getSortType()['order']]);
166
        }
167
168
        if ($state->isActive()) {
169
            foreach ($state->getValue() as $key => $term) {
170
                $terms = $state->getValue();
171
                unset($terms[$key]);
172
173
                $this->addSubFilterAggregation(
174
                    $filterAggregation,
175
                    $aggregation,
176
                    $terms,
177
                    $key
178
                );
179
            }
180
        }
181
182
        $this->addSubFilterAggregation(
183
            $filterAggregation,
184
            $aggregation,
185
            $state->getValue() ? $state->getValue() : [],
186
            'all-selected'
187
        );
188
189
        $search->addAggregation($filterAggregation);
190
191
        if ($this->getShowZeroChoices()) {
192
            $search->addAggregation($aggregation);
193
        }
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199
    public function createViewData()
200
    {
201
        return new AggregateViewData();
202
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207
    public function getViewData(DocumentIterator $result, ViewData $data)
208
    {
209
        $unsortedChoices = [];
210
        $activeNames = $data->getState()->isActive() ? array_keys($data->getState()->getValue()) : [];
211
        $filterAggregations = $this->fetchAggregation($result, $data->getName(), $data->getState()->getValue());
212
213
        if ($this->getShowZeroChoices()) {
214
            $unsortedChoices = $this->formInitialUnsortedChoices($result, $data);
215
        }
216
217
        /** @var AggregationValue $bucket */
218
        foreach ($filterAggregations as $activeName => $filterAggregation) {
219
            foreach ($filterAggregation as $nameAggregation) {
220
                $name = $nameAggregation['key'];
221
222
                if (($name != $activeName && $activeName != 'all-selected') ||
223
                    ($activeName == 'all-selected' && in_array($name, $activeNames))) {
224
                    continue;
225
                }
226
227
                foreach ($nameAggregation['value']['buckets'] as $bucket) {
228
                    $choice = $this->createChoice($data, $name, $activeName, $bucket);
0 ignored issues
show
Compatibility introduced by
$data of type object<ONGR\FilterManagerBundle\Filter\ViewData> is not a sub-type of object<ONGR\FilterManage...Data\AggregateViewData>. It seems like you assume a child class of the class ONGR\FilterManagerBundle\Filter\ViewData to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
229
                    $unsortedChoices[$name][$bucket['key']] = $choice;
230
                }
231
232
                $this->addViewDataItem($data, $name, $unsortedChoices[$name]);
0 ignored issues
show
Compatibility introduced by
$data of type object<ONGR\FilterManagerBundle\Filter\ViewData> is not a sub-type of object<ONGR\FilterManage...Data\AggregateViewData>. It seems like you assume a child class of the class ONGR\FilterManagerBundle\Filter\ViewData to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
233
                unset($unsortedChoices[$name]);
234
            }
235
        }
236
237
        if ($this->getShowZeroChoices() && !empty($unsortedChoices)) {
238
            foreach ($unsortedChoices as $name => $choices) {
239
                $this->addViewDataItem($data, $name, $unsortedChoices[$name]);
0 ignored issues
show
Compatibility introduced by
$data of type object<ONGR\FilterManagerBundle\Filter\ViewData> is not a sub-type of object<ONGR\FilterManage...Data\AggregateViewData>. It seems like you assume a child class of the class ONGR\FilterManagerBundle\Filter\ViewData to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
240
            }
241
        }
242
243
        /** @var AggregateViewData $data */
244
        $data->sortItems();
245
246
        return $data;
247
    }
248
249
    /**
250
     * {@inheritdoc}
251
     */
252
    public function isRelated()
253
    {
254
        return true;
255
    }
256
257
    /**
258
     * Fetches buckets from search results.
259
     *
260
     * @param DocumentIterator $result     Search results.
261
     * @param string           $filterName Filter name.
262
     * @param array            $values     Values from the state object
263
     *
264
     * @return array Buckets.
265
     */
266
    protected function fetchAggregation(DocumentIterator $result, $filterName, $values)
267
    {
268
        $data = [];
269
        $values = empty($values) ? [] : $values;
270
        $aggregation = $result->getAggregation(sprintf('%s-filter', $filterName));
271
272
        foreach ($values as $name => $value) {
273
            $data[$name] = $aggregation->find(sprintf('%s.%s.name', $name, $filterName));
274
        }
275
276
        $data['all-selected'] = $aggregation->find(sprintf('all-selected.%s.name', $filterName));
277
278
        return $data;
279
    }
280
281
    /**
282
     * A method used to add an additional filter to the aggregations
283
     * in preProcessSearch
284
     *
285
     * @param FilterAggregation $filterAggregation
286
     * @param NestedAggregation $deepLevelAggregation
287
     * @param array             $terms Terms of additional filter
288
     * @param string            $aggName
289
     *
290
     * @return BuilderInterface
291
     */
292
    protected function addSubFilterAggregation(
293
        $filterAggregation,
294
        &$deepLevelAggregation,
295
        $terms,
296
        $aggName
297
    ) {
298
        list($path, $field) = explode('>', $this->getField());
299
        $boolQuery = new BoolQuery();
300
301 View Code Duplication
        foreach ($terms as $groupName => $term) {
0 ignored issues
show
Duplication introduced by
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...
302
            $nestedBoolQuery = new BoolQuery();
303
            $nestedBoolQuery->add(new TermQuery($field, $term));
304
            $nestedBoolQuery->add(new TermQuery($this->getNameField(), $groupName));
305
            $boolQuery->add(new NestedQuery($path, $nestedBoolQuery));
306
        }
307
308
        $boolQuery = !empty($boolQuery->getQueries()) ? $boolQuery : new MatchAllQuery();
309
        $innerFilterAggregation = new FilterAggregation($aggName, $boolQuery);
0 ignored issues
show
Deprecated Code introduced by
The class ONGR\ElasticsearchDSL\Ag...ation\FilterAggregation has been deprecated with message: Aggregations was moved to it's type namespace. Add `Metric` or `Bucketing` after `Aggregation`. This class will be removed in 3.0.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
310
        $innerFilterAggregation->addAggregation($deepLevelAggregation);
311
        $filterAggregation->addAggregation($innerFilterAggregation);
312
    }
313
314
    /**
315
     * @param string   $key
316
     * @param string   $name
317
     * @param ViewData $data
318
     * @param bool     $active True when the choice is active
319
     *
320
     * @return array
321
     */
322
    protected function getOptionUrlParameters($key, $name, ViewData $data, $active)
323
    {
324
        $value = $data->getState()->getValue();
325
        $parameters = $data->getResetUrlParameters();
326
327 View Code Duplication
        if (!empty($value)) {
0 ignored issues
show
Duplication introduced by
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...
328
            if ($active) {
329
                unset($value[array_search($key, $value)]);
330
                $parameters[$this->getRequestField()] = $value;
331
332
                return $parameters;
333
            }
334
335
            $parameters[$this->getRequestField()] = $value;
336
        }
337
338
        $parameters[$this->getRequestField()][$name] = $key;
339
340
        return $parameters;
341
    }
342
343
    /**
344
     * Returns whether choice with the specified key is active.
345
     *
346
     * @param string   $key
347
     * @param ViewData $data
348
     * @param string   $activeName
349
     *
350
     * @return bool
351
     */
352 View Code Duplication
    protected function isChoiceActive($key, ViewData $data, $activeName)
0 ignored issues
show
Duplication introduced by
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...
353
    {
354
        if ($data->getState()->isActive()) {
355
            $value = $data->getState()->getValue();
356
357
            if (isset($value[$activeName]) && $key == $value[$activeName]) {
358
                return true;
359
            }
360
        }
361
362
        return false;
363
    }
364
365
    /**
366
     * Forms $unsortedChoices array with all possible choices.
367
     * 0 is assigned to the document count of the choices.
368
     *
369
     * @param DocumentIterator $result
370
     * @param ViewData         $data
371
     *
372
     * @return array
373
     */
374
    protected function formInitialUnsortedChoices($result, $data)
375
    {
376
        $unsortedChoices = [];
377
        $urlParameters = array_merge(
378
            $data->getResetUrlParameters(),
379
            $data->getState()->getUrlParameters()
380
        );
381
382
        foreach ($result->getAggregation($data->getName())->getAggregation('name') as $nameBucket) {
383
            $groupName = $nameBucket['key'];
384
385
            foreach ($nameBucket->getAggregation('value') as $bucket) {
386
                $bucketArray = ['key' => $bucket['key'], 'doc_count' => 0];
387
                $choice = $this->createChoice($data, $bucket['key'], '', $bucketArray, $urlParameters);
0 ignored issues
show
Compatibility introduced by
$data of type object<ONGR\FilterManagerBundle\Filter\ViewData> is not a sub-type of object<ONGR\FilterManage...Data\AggregateViewData>. It seems like you assume a child class of the class ONGR\FilterManagerBundle\Filter\ViewData to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Documentation introduced by
$bucketArray is of type array<string,?,{"key":"?","doc_count":"integer"}>, but the function expects a object<ONGR\Elasticsearc...ation\AggregationValue>.

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...
388
                $unsortedChoices[$groupName][$bucket['key']] = $choice;
389
            }
390
        }
391
392
        return $unsortedChoices;
393
    }
394
395
    /**
396
     * @param AggregateViewData $data
397
     * @param string            $name
398
     * @param string            $activeName
399
     * @param AggregationValue  $bucket
400
     * @param array             $urlParameters
401
     * @return ViewData\Choice
402
     */
403
    protected function createChoice($data, $name, $activeName, $bucket, $urlParameters = null)
404
    {
405
        $active = $this->isChoiceActive($bucket['key'], $data, $activeName);
406
407
        if (empty($urlParameters)) {
408
            $urlParameters = $this->getOptionUrlParameters($bucket['key'], $name, $data, $active);
409
        }
410
411
        $choice = new ViewData\Choice();
412
        $choice->setLabel($bucket['key']);
413
        $choice->setCount($bucket['doc_count']);
414
        $choice->setActive($active);
415
        $choice->setUrlParameters($urlParameters);
416
417
        return $choice;
418
    }
419
420
    /**
421
     * @param AggregateViewData $data
422
     * @param string            $name
423
     * @param ViewData\Choice[] $choices
424
     */
425
    protected function addViewDataItem($data, $name, $choices)
426
    {
427
        $choiceViewData = new ViewData\ChoicesAwareViewData();
428
        $choiceViewData->setName($name);
429
        $choiceViewData->setChoices($choices);
430
        $choiceViewData->setUrlParameters([]);
431
        $choiceViewData->setResetUrlParameters($data->getResetUrlParameters());
432
        $data->addItem($choiceViewData);
433
    }
434
}
435