Completed
Push — master ( a8e426...cdcd92 )
by Simonas
62:46
created

Filter/Widget/Dynamic/DynamicAggregate.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 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\Bucketing\FilterAggregation;
16
use ONGR\ElasticsearchDSL\Aggregation\Bucketing\NestedAggregation;
17
use ONGR\ElasticsearchDSL\Aggregation\Bucketing\TermsAggregation;
18
use ONGR\ElasticsearchDSL\BuilderInterface;
19
use ONGR\ElasticsearchDSL\Query\Compound\BoolQuery;
20
use ONGR\ElasticsearchDSL\Query\MatchAllQuery;
21
use ONGR\ElasticsearchDSL\Query\Joining\NestedQuery;
22
use ONGR\ElasticsearchDSL\Query\TermLevel\TermQuery;
23
use ONGR\ElasticsearchDSL\Search;
24
use ONGR\ElasticsearchBundle\Result\DocumentIterator;
25
use ONGR\FilterManagerBundle\Filter\FilterState;
26
use ONGR\FilterManagerBundle\Filter\Helper\SortAwareTrait;
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\AbstractFilter;
31
use ONGR\FilterManagerBundle\Search\SearchRequest;
32
use Symfony\Component\HttpFoundation\Request;
33
34
/**
35
 * This class provides single terms choice.
36
 */
37
class DynamicAggregate extends AbstractFilter implements ViewDataFactoryInterface
38
{
39
    use SortAwareTrait;
40
41
    /**
42
     * @return string
43
     */
44
    public function getNameField()
45
    {
46
        return $this->getOption('name_field', false);
0 ignored issues
show
false is of type boolean, but the function expects a string|null.

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...
47
    }
48
49
    /**
50
     * @return bool
51
     */
52
    public function getShowZeroChoices()
53
    {
54
        return $this->getOption('show_zero_choices', false);
0 ignored issues
show
false is of type boolean, but the function expects a string|null.

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...
55
    }
56
57
    /**
58
     * {@inheritdoc}
59
     */
60
    public function getState(Request $request)
61
    {
62
        $state = new FilterState();
63
        $value = $request->get($this->getRequestField());
64
65
        if (isset($value) && is_array($value)) {
66
            $state->setActive(true);
67
            $state->setValue($value);
68
            $state->setUrlParameters([$this->getRequestField() => $value]);
69
        }
70
71
        return $state;
72
    }
73
74
    /**
75
     * {@inheritdoc}
76
     */
77
    public function modifySearch(Search $search, FilterState $state = null, SearchRequest $request = null)
78
    {
79
        list($path, $field) = explode('>', $this->getDocumentField());
80
81
        if ($state && $state->isActive()) {
82
            $boolQuery = new BoolQuery();
83
            foreach ($state->getValue() as $groupName => $value) {
84
                $innerBoolQuery = new BoolQuery();
85
                $nestedQuery = new NestedQuery($path, $innerBoolQuery);
86
                $innerBoolQuery->add(
87
                    new TermQuery($field, $value)
88
                );
89
                $innerBoolQuery->add(
90
                    new TermQuery($this->getNameField(), $groupName)
91
                );
92
                $boolQuery->add($nestedQuery);
93
            }
94
            $search->addPostFilter($boolQuery);
95
        }
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101
    public function preProcessSearch(Search $search, Search $relatedSearch, FilterState $state = null)
102
    {
103
        list($path, $field) = explode('>', $this->getDocumentField());
104
        $filter = !empty($filter = $relatedSearch->getPostFilters()) ? $filter : new MatchAllQuery();
105
        $aggregation = new NestedAggregation($state->getName(), $path);
106
        $nameAggregation = new TermsAggregation('name', $this->getNameField());
107
        $valueAggregation = new TermsAggregation('value', $field);
108
        $filterAggregation = new FilterAggregation($state->getName() . '-filter');
109
        $nameAggregation->addAggregation($valueAggregation);
110
        $aggregation->addAggregation($nameAggregation);
111
        $filterAggregation->setFilter($filter);
112
113
        if ($this->getSortType()) {
114
            $valueAggregation->addParameter('order', [$this->getSortType() => $this->getSortOrder()]);
115
        }
116
117
        if ($state->isActive()) {
118
            foreach ($state->getValue() as $key => $term) {
119
                $terms = $state->getValue();
120
                unset($terms[$key]);
121
122
                $this->addSubFilterAggregation(
123
                    $filterAggregation,
124
                    $aggregation,
125
                    $terms,
126
                    $key
127
                );
128
            }
129
        }
130
131
        $this->addSubFilterAggregation(
132
            $filterAggregation,
133
            $aggregation,
134
            $state->getValue() ? $state->getValue() : [],
135
            'all-selected'
136
        );
137
138
        $search->addAggregation($filterAggregation);
139
140
        if ($this->getShowZeroChoices()) {
141
            $search->addAggregation($aggregation);
142
        }
143
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148
    public function createViewData()
149
    {
150
        return new AggregateViewData();
151
    }
152
153
    /**
154
     * {@inheritdoc}
155
     */
156
    public function getViewData(DocumentIterator $result, ViewData $data)
157
    {
158
        $unsortedChoices = [];
159
        $activeNames = $data->getState()->isActive() ? array_keys($data->getState()->getValue()) : [];
160
        $filterAggregations = $this->fetchAggregation($result, $data->getName(), $data->getState()->getValue());
161
162
        if ($this->getShowZeroChoices()) {
163
            $unsortedChoices = $this->formInitialUnsortedChoices($result, $data);
164
        }
165
166
        /** @var AggregationValue $bucket */
167
        foreach ($filterAggregations as $activeName => $filterAggregation) {
168
            foreach ($filterAggregation as $nameAggregation) {
169
                $name = $nameAggregation['key'];
170
171
                if (($name != $activeName && $activeName != 'all-selected') ||
172
                    ($activeName == 'all-selected' && in_array($name, $activeNames))) {
173
                    continue;
174
                }
175
176
                foreach ($nameAggregation['value']['buckets'] as $bucket) {
177
                    $choice = $this->createChoice($data, $name, $activeName, $bucket);
178
                    $unsortedChoices[$name][$bucket['key']] = $choice;
179
                }
180
181
                $this->addViewDataItem($data, $name, $unsortedChoices[$name]);
182
                unset($unsortedChoices[$name]);
183
            }
184
        }
185
186
        if ($this->getShowZeroChoices() && !empty($unsortedChoices)) {
187
            foreach ($unsortedChoices as $name => $choices) {
188
                $this->addViewDataItem($data, $name, $unsortedChoices[$name]);
189
            }
190
        }
191
192
        /** @var AggregateViewData $data */
193
        $data->sortItems();
194
195
        return $data;
196
    }
197
198
    /**
199
     * {@inheritdoc}
200
     */
201
    public function isRelated()
202
    {
203
        return true;
204
    }
205
206
    /**
207
     * Fetches buckets from search results.
208
     *
209
     * @param DocumentIterator $result     Search results.
210
     * @param string           $filterName Filter name.
211
     * @param array            $values     Values from the state object
212
     *
213
     * @return array Buckets.
214
     */
215
    protected function fetchAggregation(DocumentIterator $result, $filterName, $values)
216
    {
217
        $data = [];
218
        $values = empty($values) ? [] : $values;
219
        $aggregation = $result->getAggregation(sprintf('%s-filter', $filterName));
220
221
        foreach ($values as $name => $value) {
222
            $data[$name] = $aggregation->find(sprintf('%s.%s.name', $name, $filterName));
223
        }
224
225
        $data['all-selected'] = $aggregation->find(sprintf('all-selected.%s.name', $filterName));
226
227
        return $data;
228
    }
229
230
    /**
231
     * A method used to add an additional filter to the aggregations
232
     * in preProcessSearch
233
     *
234
     * @param FilterAggregation $filterAggregation
235
     * @param NestedAggregation $deepLevelAggregation
236
     * @param array             $terms Terms of additional filter
237
     * @param string            $aggName
238
     *
239
     * @return BuilderInterface
240
     */
241
    protected function addSubFilterAggregation(
242
        $filterAggregation,
243
        &$deepLevelAggregation,
244
        $terms,
245
        $aggName
246
    ) {
247
        list($path, $field) = explode('>', $this->getDocumentField());
248
        $boolQuery = new BoolQuery();
249
250 View Code Duplication
        foreach ($terms as $groupName => $term) {
251
            $nestedBoolQuery = new BoolQuery();
252
            $nestedBoolQuery->add(new TermQuery($field, $term));
253
            $nestedBoolQuery->add(new TermQuery($this->getNameField(), $groupName));
254
            $boolQuery->add(new NestedQuery($path, $nestedBoolQuery));
255
        }
256
257
        $boolQuery = !empty($boolQuery->getQueries()) ? $boolQuery : new MatchAllQuery();
258
        $innerFilterAggregation = new FilterAggregation($aggName, $boolQuery);
259
        $innerFilterAggregation->addAggregation($deepLevelAggregation);
260
        $filterAggregation->addAggregation($innerFilterAggregation);
261
    }
262
263
    /**
264
     * @param string   $key
265
     * @param string   $name
266
     * @param ViewData $data
267
     * @param bool     $active True when the choice is active
268
     *
269
     * @return array
270
     */
271
    protected function getOptionUrlParameters($key, $name, ViewData $data, $active)
272
    {
273
        $value = $data->getState()->getValue();
274
        $parameters = $data->getResetUrlParameters();
275
276 View Code Duplication
        if (!empty($value)) {
277
            if ($active) {
278
                unset($value[array_search($key, $value)]);
279
                $parameters[$this->getRequestField()] = $value;
280
281
                return $parameters;
282
            }
283
284
            $parameters[$this->getRequestField()] = $value;
285
        }
286
287
        $parameters[$this->getRequestField()][$name] = $key;
288
289
        return $parameters;
290
    }
291
292
    /**
293
     * Returns whether choice with the specified key is active.
294
     *
295
     * @param string   $key
296
     * @param ViewData $data
297
     * @param string   $activeName
298
     *
299
     * @return bool
300
     */
301 View Code Duplication
    protected function isChoiceActive($key, ViewData $data, $activeName)
302
    {
303
        if ($data->getState()->isActive()) {
304
            $value = $data->getState()->getValue();
305
306
            if (isset($value[$activeName]) && $key == $value[$activeName]) {
307
                return true;
308
            }
309
        }
310
311
        return false;
312
    }
313
314
    /**
315
     * Forms $unsortedChoices array with all possible choices.
316
     * 0 is assigned to the document count of the choices.
317
     *
318
     * @param DocumentIterator $result
319
     * @param ViewData         $data
320
     *
321
     * @return array
322
     */
323
    protected function formInitialUnsortedChoices($result, $data)
324
    {
325
        $unsortedChoices = [];
326
        $urlParameters = array_merge(
327
            $data->getResetUrlParameters(),
328
            $data->getState()->getUrlParameters()
329
        );
330
331
        foreach ($result->getAggregation($data->getName())->getAggregation('name') as $nameBucket) {
332
            $groupName = $nameBucket['key'];
333
334
            foreach ($nameBucket->getAggregation('value') as $bucket) {
335
                $bucketArray = ['key' => $bucket['key'], 'doc_count' => 0];
336
                $choice = $this->createChoice($data, $bucket['key'], '', $bucketArray, $urlParameters);
0 ignored issues
show
$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...
337
                $unsortedChoices[$groupName][$bucket['key']] = $choice;
338
            }
339
        }
340
341
        return $unsortedChoices;
342
    }
343
344
    /**
345
     * @param AggregateViewData $data
346
     * @param string            $name
347
     * @param string            $activeName
348
     * @param AggregationValue  $bucket
349
     * @param array             $urlParameters
350
     *
351
     * @return ViewData\ChoiceAwareViewData
352
     */
353
    protected function createChoice($data, $name, $activeName, $bucket, $urlParameters = null)
354
    {
355
        $active = $this->isChoiceActive($bucket['key'], $data, $activeName);
356
357
        if (empty($urlParameters)) {
358
            $urlParameters = $this->getOptionUrlParameters($bucket['key'], $name, $data, $active);
359
        }
360
361
        $choice = new ViewData\ChoiceAwareViewData();
362
        $choice->setLabel($bucket['key']);
363
        $choice->setCount($bucket['doc_count']);
364
        $choice->setActive($active);
365
        $choice->setUrlParameters($urlParameters);
366
367
        return $choice;
368
    }
369
370
    /**
371
     * @param AggregateViewData $data
372
     * @param string            $name
373
     * @param ViewData\ChoiceAwareViewData[] $choices
374
     */
375
    protected function addViewDataItem($data, $name, $choices)
376
    {
377
        $choiceViewData = new ViewData\ChoicesAwareViewData();
378
        $choiceViewData->setName($name);
379
        $choiceViewData->setChoices($choices);
380
        $choiceViewData->setUrlParameters([]);
381
        $choiceViewData->setResetUrlParameters($data->getResetUrlParameters());
382
        $data->addItem($choiceViewData);
383
    }
384
}
385