Completed
Pull Request — master (#195)
by
unknown
63:20
created

DynamicAggregate   C

Complexity

Total Complexity 48

Size/Duplication

Total Lines 385
Duplicated Lines 9.35 %

Coupling/Cohesion

Components 1
Dependencies 17

Importance

Changes 4
Bugs 1 Features 3
Metric Value
wmc 48
c 4
b 1
f 3
lcom 1
cbo 17
dl 36
loc 385
rs 5.8841

17 Methods

Rating   Name   Duplication   Size   Complexity  
A setSortType() 0 4 1
A getSortType() 0 4 1
A getNameField() 0 4 1
A setNameField() 0 4 1
A getState() 0 13 3
A modifySearch() 0 17 4
A getShowZeroChoices() 0 4 1
A setShowZeroChoices() 0 4 1
B preProcessSearch() 0 55 7
A createViewData() 0 4 1
C getViewData() 0 63 13
A isRelated() 0 4 1
A fetchAggregation() 0 14 3
B addSubFilterAggregation() 26 26 3
A getOptionUrlParameters() 10 20 3
A isChoiceActive() 0 4 2
A formInitialUnsortedChoices() 0 21 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like DynamicAggregate often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DynamicAggregate, and based on these observations, apply Extract Interface, too.

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
        $valueArray = $state->getValue() ? $state->getValue() : [];
191
        $this->addSubFilterAggregation(
192
            $filterAggregation,
193
            $aggregation,
194
            $valueArray,
195
            'all-selected'
196
        );
197
198
        $search->addAggregation($filterAggregation);
199
200
        if ($this->getShowZeroChoices()) {
201
            $search->addAggregation($aggregation);
202
        }
203
    }
204
205
    /**
206
     * {@inheritdoc}
207
     */
208
    public function createViewData()
209
    {
210
        return new AggregateViewData();
211
    }
212
213
    /**
214
     * {@inheritdoc}
215
     */
216
    public function getViewData(DocumentIterator $result, ViewData $data)
217
    {
218
        $unsortedChoices = [];
219
        $activeNames = $data->getState()->isActive() ? array_keys($data->getState()->getValue()) : [];
220
        $filterAggregations = $this->fetchAggregation($result, $data->getName(), $data->getState()->getValue());
221
222
        if ($this->getShowZeroChoices() && $data->getState()->isActive()) {
223
            $unsortedChoices = $this->formInitialUnsortedChoices($result, $data);
224
        }
225
226
        /** @var AggregationValue $bucket */
227
        foreach ($filterAggregations as $activeName => $aggregation) {
228
            foreach ($aggregation as $bucket) {
229
                $name = $bucket->getAggregation('name')->getBuckets()[0]['key'];
230
231
                if ($name != $activeName && $activeName != 'all-selected') {
232
                    continue;
233
                }
234
235
                $active = $this->isChoiceActive($bucket['key'], $data, $activeName);
236
                $choice = new ViewData\Choice();
237
                $choice->setLabel($bucket->getValue('key'));
238
                $choice->setCount($bucket['doc_count']);
239
                $choice->setActive($active);
240
241
                $choice->setUrlParameters(
242
                    $this->getOptionUrlParameters($bucket['key'], $name, $data, $active)
243
                );
244
245
                if ($activeName == 'all-selected') {
246
                    $unsortedChoices[$activeName][$name][$bucket['key']] = $choice;
247
                } else {
248
                    $unsortedChoices[$activeName][$bucket['key']] = $choice;
249
                }
250
            }
251
        }
252
253
        if (isset($unsortedChoices['all-selected'])) {
254
            foreach ($unsortedChoices['all-selected'] as $name => $buckets) {
255
                if (in_array($name, $activeNames)) {
256
                    continue;
257
                }
258
259
                $unsortedChoices[$name] = $buckets;
260
            }
261
262
            unset($unsortedChoices['all-selected']);
263
        }
264
265
        ksort($unsortedChoices);
266
267
        /** @var AggregateViewData $data */
268
        foreach ($unsortedChoices as $name => $choices) {
269
            $choiceViewData = new ViewData\ChoicesAwareViewData();
270
            $choiceViewData->setName($name);
271
            $choiceViewData->setChoices($choices);
272
            $choiceViewData->setUrlParameters([]);
273
            $choiceViewData->setResetUrlParameters([]);
274
            $data->addItem($choiceViewData);
275
        }
276
277
        return $data;
278
    }
279
280
    /**
281
     * {@inheritdoc}
282
     */
283
    public function isRelated()
284
    {
285
        return true;
286
    }
287
288
    /**
289
     * Fetches buckets from search results.
290
     *
291
     * @param DocumentIterator $result     Search results.
292
     * @param string           $filterName Filter name.
293
     * @param array            $values     Values from the state object
294
     *
295
     * @return array Buckets.
296
     */
297
    protected function fetchAggregation(DocumentIterator $result, $filterName, $values)
298
    {
299
        $data = [];
300
        $values = empty($values) ? [] : $values;
301
        $aggregation = $result->getAggregation(sprintf('%s-filter', $filterName));
302
303
        foreach ($values as $name => $value) {
304
            $data[$name] = $aggregation->find(sprintf('%s.%s.query', $name, $filterName));
305
        }
306
307
        $data['all-selected'] = $aggregation->find(sprintf('all-selected.%s.query', $filterName));
308
309
        return $data;
310
    }
311
312
    /**
313
     * A method used to add an additional filter to the aggregations
314
     * in preProcessSearch
315
     *
316
     * @param FilterAggregation $filterAggregation
317
     * @param NestedAggregation $deepLevelAggregation
318
     * @param array             $terms Terms of additional filter
319
     * @param string            $aggName
320
     *
321
     * @return BuilderInterface
322
     */
323 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...
324
        $filterAggregation,
325
        $deepLevelAggregation,
326
        $terms,
327
        $aggName
328
    ) {
329
        list($path, $field) = explode('>', $this->getField());
330
        $boolQuery = new BoolQuery();
331
332
        foreach ($terms as $term) {
333
            $boolQuery->add(
334
                new NestedQuery($path, new TermQuery($field, $term))
335
            );
336
        }
337
338
        if ($boolQuery->getQueries() == []) {
339
            $boolQuery->add(new MatchAllQuery());
340
        }
341
342
        $innerFilterAggregation = new FilterAggregation(
343
            $aggName,
344
            $boolQuery
345
        );
346
        $innerFilterAggregation->addAggregation($deepLevelAggregation);
347
        $filterAggregation->addAggregation($innerFilterAggregation);
348
    }
349
350
    /**
351
     * @param string   $key
352
     * @param string   $name
353
     * @param ViewData $data
354
     * @param bool     $active True when the choice is active
355
     *
356
     * @return array
357
     */
358
    protected function getOptionUrlParameters($key, $name, ViewData $data, $active)
359
    {
360
        $value = $data->getState()->getValue();
361
        $parameters = $data->getResetUrlParameters();
362
363 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...
364
            if ($active) {
365
                unset($value[array_search($key, $value)]);
366
                $parameters[$this->getRequestField()] = $value;
367
368
                return $parameters;
369
            }
370
371
            $parameters[$this->getRequestField()] = $value;
372
        }
373
374
        $parameters[$this->getRequestField()][$name] = $key;
375
376
        return $parameters;
377
    }
378
379
    /**
380
     * Returns whether choice with the specified key is active.
381
     *
382
     * @param string   $key
383
     * @param ViewData $data
384
     * @param string   $activeName
385
     *
386
     * @return bool
387
     */
388
    protected function isChoiceActive($key, ViewData $data, $activeName)
389
    {
390
        return $data->getState()->isActive() && in_array($key, $data->getState()->getValue());
391
    }
392
393
    /**
394
     * Forms $unsortedChoices array with all possible choices.
395
     * 0 is assigned to the document count of the choices.
396
     *
397
     * @param DocumentIterator $result
398
     * @param ViewData         $data
399
     *
400
     * @return array
401
     */
402
    private function formInitialUnsortedChoices($result, $data)
403
    {
404
        $unsortedChoices = [];
405
        $urlParameters = array_merge(
406
            $data->getResetUrlParameters(),
407
            $data->getState()->getUrlParameters()
408
        );
409
410
        foreach ($result->getAggregation($data->getName())->getAggregation('query') as $bucket) {
411
            $groupName = $bucket->getAggregation('name')->getBuckets()[0]['key'];
412
            $choice = new ViewData\Choice();
413
            $choice->setActive(false);
414
            $choice->setUrlParameters($urlParameters);
415
            $choice->setLabel($bucket['key']);
416
            $choice->setCount(0);
417
            $unsortedChoices[$groupName][$bucket['key']] = $choice;
418
            $unsortedChoices['all-selected'][$groupName][$bucket['key']] = $choice;
419
        }
420
421
        return $unsortedChoices;
422
    }
423
}
424