Completed
Push — master ( 4059cd...ef58c8 )
by Simonas
84:03 queued 19:25
created

DynamicAggregate::fetchAggregation()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 14
rs 9.4285
cc 3
eloc 8
nc 4
nop 3
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
40
    FieldAwareInterface,
41
    ViewDataFactoryInterface
42
{
43
    use FieldAwareTrait;
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 $value) {
135
                $boolQuery->add(
136
                    new NestedQuery(
137
                        $path,
138
                        new TermQuery($field, $value)
139
                    )
140
                );
141
            }
142
            $search->addPostFilter($boolQuery);
143
        }
144
    }
145
146
    /**
147
     * {@inheritdoc}
148
     */
149
    public function preProcessSearch(Search $search, Search $relatedSearch, FilterState $state = null)
150
    {
151
        list($path, $field) = explode('>', $this->getField());
152
        $name = $state->getName();
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...
153
        $aggregation = new NestedAggregation(
154
            $name,
155
            $path
156
        );
157
        $termsAggregation = new TermsAggregation('query', $field);
158
        $termsAggregation->addParameter('size', 0);
159
160
        if ($this->getSortType()) {
161
            $termsAggregation->addParameter('order', [$this->getSortType()['type'] => $this->getSortType()['order']]);
162
        }
163
164
        $termsAggregation->addAggregation(
165
            new TermsAggregation('name', $this->getNameField())
166
        );
167
        $aggregation->addAggregation($termsAggregation);
168
        $filterAggregation = new FilterAggregation($name . '-filter');
169
170
        if (!empty($relatedSearch->getPostFilters())) {
171
            $filterAggregation->setFilter($relatedSearch->getPostFilters());
172
        } else {
173
            $filterAggregation->setFilter(new MatchAllQuery());
174
        }
175
176
        if ($state->isActive()) {
177
            foreach ($state->getValue() as $key => $term) {
178
                $terms = $state->getValue();
179
                unset($terms[$key]);
180
181
                $this->addSubFilterAggregation(
182
                    $filterAggregation,
183
                    $aggregation,
184
                    $terms,
185
                    $key
186
                );
187
            }
188
        }
189
190
        $this->addSubFilterAggregation(
191
            $filterAggregation,
192
            $aggregation,
193
            $state->getValue() ? $state->getValue() : [],
194
            'all-selected'
195
        );
196
197
        $search->addAggregation($filterAggregation);
198
199
        if ($this->getShowZeroChoices()) {
200
            $search->addAggregation($aggregation);
201
        }
202
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207
    public function createViewData()
208
    {
209
        return new AggregateViewData();
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     */
215
    public function getViewData(DocumentIterator $result, ViewData $data)
216
    {
217
        $unsortedChoices = [];
218
        $activeNames = $data->getState()->isActive() ? array_keys($data->getState()->getValue()) : [];
219
        $filterAggregations = $this->fetchAggregation($result, $data->getName(), $data->getState()->getValue());
220
221
        if ($this->getShowZeroChoices() && $data->getState()->isActive()) {
222
            $unsortedChoices = $this->formInitialUnsortedChoices($result, $data);
223
        }
224
225
        /** @var AggregationValue $bucket */
226
        foreach ($filterAggregations as $activeName => $aggregation) {
227
            foreach ($aggregation as $bucket) {
228
                $name = $bucket->getAggregation('name')->getBuckets()[0]['key'];
229
230
                if ($name != $activeName && $activeName != 'all-selected') {
231
                    continue;
232
                }
233
234
                $active = $this->isChoiceActive($bucket['key'], $data, $activeName);
235
                $choice = new ViewData\Choice();
236
                $choice->setLabel($bucket->getValue('key'));
237
                $choice->setCount($bucket['doc_count']);
238
                $choice->setActive($active);
239
240
                $choice->setUrlParameters(
241
                    $this->getOptionUrlParameters($bucket['key'], $name, $data, $active)
242
                );
243
244
                if ($activeName == 'all-selected') {
245
                    $unsortedChoices[$activeName][$name][$bucket['key']] = $choice;
246
                } else {
247
                    $unsortedChoices[$activeName][$bucket['key']] = $choice;
248
                }
249
            }
250
        }
251
252
        if (isset($unsortedChoices['all-selected'])) {
253
            foreach ($unsortedChoices['all-selected'] as $name => $buckets) {
254
                if (in_array($name, $activeNames)) {
255
                    continue;
256
                }
257
258
                $unsortedChoices[$name] = $buckets;
259
            }
260
261
            unset($unsortedChoices['all-selected']);
262
        }
263
264
        ksort($unsortedChoices);
265
266
        /** @var AggregateViewData $data */
267
        foreach ($unsortedChoices as $name => $choices) {
268
            $choiceViewData = new ViewData\ChoicesAwareViewData();
269
            $choiceViewData->setName($name);
270
            $choiceViewData->setChoices($choices);
271
            $choiceViewData->setUrlParameters([]);
272
            $choiceViewData->setResetUrlParameters([]);
273
            $data->addItem($choiceViewData);
274
        }
275
276
        return $data;
277
    }
278
279
    /**
280
     * {@inheritdoc}
281
     */
282
    public function isRelated()
283
    {
284
        return true;
285
    }
286
287
    /**
288
     * Fetches buckets from search results.
289
     *
290
     * @param DocumentIterator $result     Search results.
291
     * @param string           $filterName Filter name.
292
     * @param array            $values     Values from the state object
293
     *
294
     * @return array Buckets.
295
     */
296
    protected function fetchAggregation(DocumentIterator $result, $filterName, $values)
297
    {
298
        $data = [];
299
        $values = empty($values) ? [] : $values;
300
        $aggregation = $result->getAggregation(sprintf('%s-filter', $filterName));
301
302
        foreach ($values as $name => $value) {
303
            $data[$name] = $aggregation->find(sprintf('%s.%s.query', $name, $filterName));
304
        }
305
306
        $data['all-selected'] = $aggregation->find(sprintf('all-selected.%s.query', $filterName));
307
308
        return $data;
309
    }
310
311
    /**
312
     * A method used to add an additional filter to the aggregations
313
     * in preProcessSearch
314
     *
315
     * @param FilterAggregation $filterAggregation
316
     * @param NestedAggregation $deepLevelAggregation
317
     * @param array             $terms Terms of additional filter
318
     * @param string            $aggName
319
     *
320
     * @return BuilderInterface
321
     */
322 View Code Duplication
    protected function addSubFilterAggregation(
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...
323
        $filterAggregation,
324
        $deepLevelAggregation,
325
        $terms,
326
        $aggName
327
    ) {
328
        list($path, $field) = explode('>', $this->getField());
329
        $boolQuery = new BoolQuery();
330
331
        foreach ($terms as $term) {
332
            $boolQuery->add(
333
                new NestedQuery($path, new TermQuery($field, $term))
334
            );
335
        }
336
337
        if ($boolQuery->getQueries() == []) {
338
            $boolQuery->add(new MatchAllQuery());
339
        }
340
341
        $innerFilterAggregation = new FilterAggregation(
342
            $aggName,
343
            $boolQuery
344
        );
345
        $innerFilterAggregation->addAggregation($deepLevelAggregation);
346
        $filterAggregation->addAggregation($innerFilterAggregation);
347
    }
348
349
    /**
350
     * @param string   $key
351
     * @param string   $name
352
     * @param ViewData $data
353
     * @param bool     $active True when the choice is active
354
     *
355
     * @return array
356
     */
357
    protected function getOptionUrlParameters($key, $name, ViewData $data, $active)
358
    {
359
        $value = $data->getState()->getValue();
360
        $parameters = $data->getResetUrlParameters();
361
362 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...
363
            if ($active) {
364
                unset($value[array_search($key, $value)]);
365
                $parameters[$this->getRequestField()] = $value;
366
367
                return $parameters;
368
            }
369
370
            $parameters[$this->getRequestField()] = $value;
371
        }
372
373
        $parameters[$this->getRequestField()][$name] = $key;
374
375
        return $parameters;
376
    }
377
378
    /**
379
     * Returns whether choice with the specified key is active.
380
     *
381
     * @param string   $key
382
     * @param ViewData $data
383
     * @param string   $activeName
384
     *
385
     * @return bool
386
     */
387
    protected function isChoiceActive($key, ViewData $data, $activeName)
388
    {
389
        return $data->getState()->isActive() && in_array($key, $data->getState()->getValue());
390
    }
391
392
    /**
393
     * Forms $unsortedChoices array with all possible choices.
394
     * 0 is assigned to the document count of the choices.
395
     *
396
     * @param DocumentIterator $result
397
     * @param ViewData         $data
398
     *
399
     * @return array
400
     */
401
    private function formInitialUnsortedChoices($result, $data)
402
    {
403
        $unsortedChoices = [];
404
        $urlParameters = array_merge(
405
            $data->getResetUrlParameters(),
406
            $data->getState()->getUrlParameters()
407
        );
408
409
        foreach ($result->getAggregation($data->getName())->getAggregation('query') as $bucket) {
410
            $groupName = $bucket->getAggregation('name')->getBuckets()[0]['key'];
411
            $choice = new ViewData\Choice();
412
            $choice->setActive(false);
413
            $choice->setUrlParameters($urlParameters);
414
            $choice->setLabel($bucket['key']);
415
            $choice->setCount(0);
416
            $unsortedChoices[$groupName][$bucket['key']] = $choice;
417
            $unsortedChoices['all-selected'][$groupName][$bucket['key']] = $choice;
418
        }
419
420
        return $unsortedChoices;
421
    }
422
}
423