Completed
Pull Request — master (#185)
by
unknown
66:54
created

Dynamic::getState()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 39
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 3
Metric Value
c 5
b 0
f 3
dl 0
loc 39
rs 6.7272
cc 7
eloc 22
nc 7
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\ElasticsearchBundle\Mapping\Caser;
16
use ONGR\ElasticsearchDSL\Search;
17
use ONGR\FilterManagerBundle\Filter\FilterInterface;
18
use ONGR\FilterManagerBundle\Filter\FilterState;
19
use ONGR\FilterManagerBundle\Filter\Helper\ViewDataFactoryInterface;
20
use ONGR\FilterManagerBundle\Filter\Relation\RelationAwareTrait;
21
use ONGR\FilterManagerBundle\Filter\ViewData;
22
use ONGR\FilterManagerBundle\Filter\ViewData\ChoicesAwareViewData;
23
use ONGR\FilterManagerBundle\Search\SearchRequest;
24
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
25
use Symfony\Component\HttpFoundation\Request;
26
27
/**
28
 * This class provides sorting filter.
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 (empty($this->filters)) {
70
                throw new InvalidConfigurationException('No filters provided to dynamic filter');
71
            }
72
73
            if (isset($value['filter'])) {
74
                if (isset($this->filters[$value['filter']])) {
75
                    $this->filter = clone $this->filters[$value['filter']];
76
                } else {
77
                    throw new InvalidConfigurationException(
78
                        sprintf('Filter `%s`, requested in dynamic filter not defined', $value['filter'])
79
                    );
80
                }
81
            } else {
82
                $this->filter = clone reset($this->filters);
0 ignored issues
show
Documentation Bug introduced by
It seems like clone reset($this->filters) can also be of type false. However, the property $filter is declared as type object<ONGR\FilterManage...Filter\FilterInterface>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
83
            }
84
85
            $this->filter->setRequestField($this->getRequestField());
86
87
            if (isset($value['field'])) {
88
                $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...
89
            }
90
91
            $request = new Request(
92
                [
93
                    $this->getRequestField() => $this->urlParameters[$this->getRequestField()]['value']
94
                ]
95
            );
96
            $state = $this->filter->getState($request);
97
        }
98
99
        return $state;
100
    }
101
102
    /**
103
     * {@inheritdoc}
104
     */
105
    public function modifySearch(Search $search, FilterState $state = null, SearchRequest $request = null)
106
    {
107
        if ($state && $state->isActive()) {
108
            $this->filter->modifySearch($search, $state, $request);
109
        }
110
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115
    public function preProcessSearch(Search $search, Search $relatedSearch, FilterState $state = null)
116
    {
117
        $out = null;
118
119
        if ($state && $state->isActive()) {
120
            $out = $this->filter->preProcessSearch($search, $relatedSearch, $state);
121
            $state->setUrlParameters($this->urlParameters);
122
        }
123
124
        return $out;
125
    }
126
127
    /**
128
     * {@inheritdoc}
129
     */
130
    public function getViewData(DocumentIterator $result, ViewData $data)
131
    {
132
        if ($this->filter) {
133
            $data = $this->filter->getViewData($result, $data);
134
135
            if ($data instanceof ChoicesAwareViewData) {
136
                $choices = [];
137
138
                foreach ($data->getChoices() as $choice) {
139
                    $urlParameters = $this->urlParameters;
140
                    $choiceParameters = $choice->getUrlParameters();
141
                    $value = null;
142
143
                    if (isset($choiceParameters[$this->getRequestField()])) {
144
                        $value = $choiceParameters[$this->getRequestField()];
145
                    }
146
147
                    $urlParameters[$this->getRequestField()]['value'] = $value;
148
                    $choiceParameters[$this->getRequestField()] = $urlParameters[$this->getRequestField()];
149
150
                    $choice->setUrlParameters($choiceParameters);
151
                    $choices[] = $choice;
152
                }
153
154
                $data->setChoices($choices);
155
            }
156
        }
157
158
        return $data;
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164
    public function createViewData()
165
    {
166
        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...
167
            $data = $this->filter->createViewData();
168
        } else {
169
            $data = new ViewData();
170
        }
171
172
        return $data;
173
    }
174
175
    /**
176
     * {@inheritdoc}
177
     */
178
    public function getTags()
179
    {
180
        return $this->tags;
181
    }
182
183
    /**
184
     * @param array $tags
185
     */
186
    public function setTags(array $tags)
187
    {
188
        $this->tags = $tags;
189
    }
190
191
    /**
192
     * @return mixed
193
     */
194
    public function getRequestField()
195
    {
196
        return $this->requestField;
197
    }
198
199
    /**
200
     * @param mixed $requestField
201
     */
202
    public function setRequestField($requestField)
203
    {
204
        $this->requestField = $requestField;
205
    }
206
207
    /**
208
     * @return FilterInterface[]
209
     */
210
    public function getFilters()
211
    {
212
        return $this->filters;
213
    }
214
215
    /**
216
     * @param FilterInterface[] $filters
217
     */
218
    public function setFilters($filters)
219
    {
220
        $this->filters = $filters;
221
    }
222
}
223