Completed
Pull Request — master (#185)
by
unknown
126:17 queued 60:07
created

Dynamic::getFilters()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
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\DocumentIterator;
15
use ONGR\ElasticsearchDSL\Search;
16
use ONGR\FilterManagerBundle\Filter\FilterInterface;
17
use ONGR\FilterManagerBundle\Filter\FilterState;
18
use ONGR\FilterManagerBundle\Filter\Helper\ViewDataFactoryInterface;
19
use ONGR\FilterManagerBundle\Filter\Relation\RelationAwareTrait;
20
use ONGR\FilterManagerBundle\Filter\ViewData;
21
use ONGR\FilterManagerBundle\Filter\ViewData\ChoicesAwareViewData;
22
use ONGR\FilterManagerBundle\Search\SearchRequest;
23
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
24
use Symfony\Component\HttpFoundation\Request;
25
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
26
27
/**
28
 * This class a dynamic filter that can change the filter, field and value per request
29
 */
30
class Dynamic implements FilterInterface, ViewDataFactoryInterface
31
{
32
    use RelationAwareTrait;
33
34
    /**
35
     * @var string
36
     */
37
    private $requestField;
38
39
    /**
40
     * @var FilterInterface
41
     */
42
    private $filter;
43
44
    /**
45
     * @var FilterInterface[]
46
     */
47
    private $filters;
48
49
    /**
50
     * @var array
51
     */
52
    private $urlParameters;
53
54
    /**
55
     * @var array
56
     */
57
    private $tags = [];
58
59
    /**
60
     * {@inheritdoc}
61
     */
62
    public function getState(Request $request)
63
    {
64
        $state = new FilterState();
65
        $value = $request->get($this->getRequestField());
66
        $this->urlParameters[$this->getRequestField()] = $value;
67
68
        if (isset($value) && is_array($value)) {
69
            if (!isset($value['field']) || !isset($value['filter'])) {
70
                throw new BadRequestHttpException(
71
                    '`field` and `filter` values must be provided to the dynamic filter'
72
                );
73
            }
74
75
            if (isset($this->filters[$value['filter']])) {
76
                $this->filter = clone $this->filters[$value['filter']];
77
            } else {
78
                throw new InvalidConfigurationException(
79
                    sprintf('Filter `%s`, requested in dynamic filter is not defined', $value['filter'])
80
                );
81
            }
82
83
            $this->filter->setRequestField($this->getRequestField());
84
            $this->filter->setField($value['field']);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface ONGR\FilterManagerBundle\Filter\FilterInterface as the method setField() does only exist in the following implementations of said interface: ONGR\FilterManagerBundle...\Choice\MultiTermChoice, ONGR\FilterManagerBundle...Choice\SingleTermChoice, ONGR\FilterManagerBundle...get\Range\AbstractRange, ONGR\FilterManagerBundle...\Widget\Range\DateRange, ONGR\FilterManagerBundle\Filter\Widget\Range\Range, ONGR\FilterManagerBundle...rch\AbstractSingleValue, ONGR\FilterManagerBundle...et\Search\DocumentValue, ONGR\FilterManagerBundle...idget\Search\FieldValue, ONGR\FilterManagerBundle...dget\Search\FuzzySearch, ONGR\FilterManagerBundle...dget\Search\MatchSearch.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
85
            $requestValue = [];
86
87
            if (isset($value['value'])) {
88
                $requestValue = [
89
                    $this->getRequestField() => $this->urlParameters[$this->getRequestField()]['value']
90
                ];
91
            }
92
93
            $request = new Request($requestValue);
94
            $state = $this->filter->getState($request);
95
        }
96
97
        return $state;
98
    }
99
100
    /**
101
     * {@inheritdoc}
102
     */
103
    public function modifySearch(Search $search, FilterState $state = null, SearchRequest $request = null)
104
    {
105
        if ($state && $state->isActive()) {
106
            $this->filter->modifySearch($search, $state, $request);
107
        }
108
    }
109
110
    /**
111
     * {@inheritdoc}
112
     */
113
    public function preProcessSearch(Search $search, Search $relatedSearch, FilterState $state = null)
114
    {
115
        $out = null;
116
117
        if ($this->filter) {
118
            $out = $this->filter->preProcessSearch($search, $relatedSearch, $state);
119
            $state->setUrlParameters($this->urlParameters);
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...
120
        }
121
122
        return $out;
123
    }
124
125
    /**
126
     * {@inheritdoc}
127
     */
128
    public function getViewData(DocumentIterator $result, ViewData $data)
129
    {
130
        if ($this->filter) {
131
            $data = $this->filter->getViewData($result, $data);
132
133
            if ($data instanceof ChoicesAwareViewData) {
134
                $choices = [];
135
                $resetUrlParameters = $data->getResetUrlParameters();
136
                $resetUrlParameters[$this->getRequestField()] = $this->urlParameters[$this->getRequestField()];
137
                unset($resetUrlParameters[$this->getRequestField()]['value']);
138
                $data->setResetUrlParameters($resetUrlParameters);
139
140
                foreach ($data->getChoices() as $choice) {
141
                    if (isset($choice->getUrlParameters()[$this->getRequestField()])) {
142
                        $choiceParameters = $choice->getUrlParameters();
143
                        $choiceParameters[$this->getRequestField()] = $this->urlParameters[$this->getRequestField()];
144
                        $choiceParameters[$this->getRequestField()]['value'] =
145
                            $choice->getUrlParameters()[$this->getRequestField()];
146
                        $choice->setUrlParameters($choiceParameters);
147
                    }
148
149
                    $choices[] = $choice;
150
                }
151
152
                $data->setChoices($choices);
153
            }
154
        }
155
156
        return $data;
157
    }
158
159
    /**
160
     * {@inheritdoc}
161
     */
162
    public function createViewData()
163
    {
164
        if ($this->filter and $this->filter instanceof ViewDataFactoryInterface) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
165
            $data = $this->filter->createViewData();
166
        } else {
167
            $data = new ViewData();
168
        }
169
170
        return $data;
171
    }
172
173
    /**
174
     * {@inheritdoc}
175
     */
176
    public function getTags()
177
    {
178
        return $this->tags;
179
    }
180
181
    /**
182
     * @param array $tags
183
     */
184
    public function setTags(array $tags)
185
    {
186
        $this->tags = $tags;
187
    }
188
189
    /**
190
     * @return mixed
191
     */
192
    public function getRequestField()
193
    {
194
        return $this->requestField;
195
    }
196
197
    /**
198
     * @param mixed $requestField
199
     */
200
    public function setRequestField($requestField)
201
    {
202
        $this->requestField = $requestField;
203
    }
204
205
    /**
206
     * @return FilterInterface[]
207
     */
208
    public function getFilters()
209
    {
210
        return $this->filters;
211
    }
212
213
    /**
214
     * @param FilterInterface[] $filters
215
     */
216
    public function setFilters($filters)
217
    {
218
        $this->filters = $filters;
219
    }
220
}
221