Completed
Pull Request — master (#185)
by
unknown
65:53
created

Dynamic::getState()   C

Complexity

Conditions 7
Paths 5

Size

Total Lines 37
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 0 Features 3
Metric Value
c 6
b 0
f 3
dl 0
loc 37
rs 6.7272
cc 7
eloc 22
nc 5
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\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
142
                    if (isset($choice->getUrlParameters()[$this->getRequestField()])) {
143
                        $choiceParameters = $choice->getUrlParameters();
144
                        $choiceParameters[$this->getRequestField()] = $this->urlParameters[$this->getRequestField()];
145
                        $choiceParameters[$this->getRequestField()]['value'] =
146
                            $choice->getUrlParameters()[$this->getRequestField()];
147
                        $choice->setUrlParameters($choiceParameters);
148
                    }
149
150
                    $choices[] = $choice;
151
                }
152
153
                $data->setChoices($choices);
154
            }
155
        }
156
157
        return $data;
158
    }
159
160
    /**
161
     * {@inheritdoc}
162
     */
163
    public function createViewData()
164
    {
165
        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...
166
            $data = $this->filter->createViewData();
167
        } else {
168
            $data = new ViewData();
169
        }
170
171
        return $data;
172
    }
173
174
    /**
175
     * {@inheritdoc}
176
     */
177
    public function getTags()
178
    {
179
        return $this->tags;
180
    }
181
182
    /**
183
     * @param array $tags
184
     */
185
    public function setTags(array $tags)
186
    {
187
        $this->tags = $tags;
188
    }
189
190
    /**
191
     * @return mixed
192
     */
193
    public function getRequestField()
194
    {
195
        return $this->requestField;
196
    }
197
198
    /**
199
     * @param mixed $requestField
200
     */
201
    public function setRequestField($requestField)
202
    {
203
        $this->requestField = $requestField;
204
    }
205
206
    /**
207
     * @return FilterInterface[]
208
     */
209
    public function getFilters()
210
    {
211
        return $this->filters;
212
    }
213
214
    /**
215
     * @param FilterInterface[] $filters
216
     */
217
    public function setFilters($filters)
218
    {
219
        $this->filters = $filters;
220
    }
221
}
222