Completed
Push — master ( 6eeb89...aa2dbb )
by
unknown
02:52
created

Filterer::buildCondition()   C

Complexity

Conditions 22
Paths 22

Size

Total Lines 51
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 51
rs 5.5531
cc 22
eloc 40
nc 22
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Netdudes\DataSourceryBundle\DataSource\Driver\Doctrine\QueryBuilder;
4
5
use Doctrine\ORM\Query\Expr;
6
use Doctrine\ORM\Query\Expr\Andx;
7
use Doctrine\ORM\Query\Expr\Composite;
8
use Doctrine\ORM\Query\Expr\Orx;
9
use Doctrine\ORM\QueryBuilder;
10
use Netdudes\DataSourceryBundle\DataSource\Configuration\Field;
11
use Netdudes\DataSourceryBundle\DataSource\Driver\Doctrine\Exception\ColumnNotFoundException;
12
use Netdudes\DataSourceryBundle\Query\Filter;
13
use Netdudes\DataSourceryBundle\Query\FilterCondition;
14
15
class Filterer
16
{
17
    /**
18
     * @var array
19
     */
20
    private $uniqueNameToQueryFieldMap;
21
22
    /**
23
     * Apply the filters to the query builders
24
     *
25
     * @param QueryBuilder $queryBuilder
26
     * @param Filter       $filterDefinition
27
     * @param array        $uniqueNameToQueryFieldMap
28
     *
29
     * @return QueryBuilder
30
     * @throws \Exception
31
     */
32
    public function filter(QueryBuilder $queryBuilder, Filter $filterDefinition, $uniqueNameToQueryFieldMap)
33
    {
34
        $this->uniqueNameToQueryFieldMap = $uniqueNameToQueryFieldMap;
35
        $expressions = $this->buildFilterGroup($queryBuilder, $filterDefinition);
36
        if ($expressions->count() > 0) {
37
            $queryBuilder->andWhere($expressions);
38
        }
39
40
        return $queryBuilder;
41
    }
42
43
    /**
44
     * Builds a filter group and, recursively, constructs a tree of conditions for the filters
45
     *
46
     * @param QueryBuilder $queryBuilder
47
     * @param Filter       $filterDefinition
48
     *
49
     * @throws \Exception
50
     * @return Andx|Orx
51
     */
52
    protected function buildFilterGroup(QueryBuilder $queryBuilder, Filter $filterDefinition)
53
    {
54
        // Container for the expressions to add to the $queryBuilder
55
        $filterConditionType = $filterDefinition->getConditionType();
56
        if ($filterConditionType == Filter::CONDITION_TYPE_AND) {
57
            $expressions = $queryBuilder->expr()->andX();
58
        } elseif ($filterConditionType == Filter::CONDITION_TYPE_OR) {
59
            $expressions = $queryBuilder->expr()->orX();
60
        } else {
61
            throw new \Exception("Unknown condition type $filterConditionType on the filter.");
62
        }
63
64
        // Loop through all the filters in the collection
65
        $this->addExpressionsForFilter($expressions, $filterDefinition, $queryBuilder);
66
67
        return $expressions;
68
    }
69
70
    /**
71
     * @param Composite    $expressions
72
     * @param Filter       $filterDefinition
73
     * @param QueryBuilder $queryBuilder
74
     *
75
     * @throws \Exception
76
     */
77
    protected function addExpressionsForFilter(Composite $expressions, Filter $filterDefinition, QueryBuilder $queryBuilder)
78
    {
79
        foreach ($filterDefinition as $subFilterDefinition) {
80
            if ($subFilterDefinition instanceof Filter) {
81
                // If the element is itself a Collection, recursively build it
82
                $expressions->add($this->buildFilterGroup($queryBuilder, $subFilterDefinition));
83
            } elseif ($subFilterDefinition instanceof FilterCondition) {
84
                $this->addExpressionsForFilterCondition($expressions, $subFilterDefinition, $queryBuilder);
85
            }
86
        }
87
    }
88
89
    /**
90
     * @param Composite       $expressions
91
     * @param FilterCondition $filterCondition
92
     * @param QueryBuilder    $queryBuilder
93
     *
94
     * @throws ColumnNotFoundException
95
     * @throws \Exception
96
     */
97
    protected function addExpressionsForFilterCondition(Composite $expressions, FilterCondition $filterCondition, QueryBuilder $queryBuilder)
98
    {
99
        // Build an unique token name for parameter substitution
100
        $token = $this->buildUniqueToken($filterCondition, $queryBuilder);
101
        $filterMethod = $filterCondition->getMethod();
102
103
        // Add the filtering statement
104
        $valueInDatabase = $filterCondition->getValueInDatabase();
105
106
        // Flag to not insert the parameter if the logic requires it
107
        $ignoreParameter = false;
108
109
        // Depending on the filter type, create a condition
110
        $condition = $this->buildCondition($filterCondition, $token, $queryBuilder);
111
        $expressions->add($condition);
112
113
        // Ignore value if needed
114
        if (
115
            ($filterMethod == FilterCondition::METHOD_IN && count($valueInDatabase) <= 0) ||
116
            ($filterMethod == FilterCondition::METHOD_IS_NULL)
117
        ) {
118
            $ignoreParameter = true;
119
        }
120
121
        // Modify the value if needed
122
        if ($filterMethod == FilterCondition::METHOD_STRING_LIKE) {
123
            $valueInDatabase = str_replace('*', '%', $valueInDatabase);
124
        }
125
126
        // Insert the value substituting the token
127
        if (!$ignoreParameter) {
128
            $queryBuilder->setParameter($token, $valueInDatabase);
129
        }
130
    }
131
132
    /**
133
     * @param FilterCondition $filterCondition
134
     * @param string          $token
135
     * @param QueryBuilder    $queryBuilder
136
     *
137
     * @throws ColumnNotFoundException
138
     * @throws \Exception
139
     *
140
     * @return Expr|string
141
     */
142
    protected function buildCondition(FilterCondition $filterCondition, $token, QueryBuilder $queryBuilder)
143
    {
144
        $filterMethod = $filterCondition->getMethod();
145
        $identifier = $this->uniqueNameToQueryFieldMap[$filterCondition->getFieldName()];
146
        $value = $filterCondition->getValue();
147
148
        switch ($filterMethod) {
149
            case FilterCondition::METHOD_STRING_LIKE:
150
                return $queryBuilder->expr()->like($identifier, $token);
151
            case FilterCondition::METHOD_STRING_EQ:
152
            case FilterCondition::METHOD_NUMERIC_EQ:
153
            case FilterCondition::METHOD_BOOLEAN:
154
            case FilterCondition::METHOD_DATETIME_EQ:
155
                return $queryBuilder->expr()->eq($identifier, $token);
156
            case FilterCondition::METHOD_NUMERIC_GT:
157
            case FilterCondition::METHOD_DATETIME_GT:
158
                return $queryBuilder->expr()->gt($identifier, $token);
159
            case FilterCondition::METHOD_NUMERIC_GTE:
160
            case FilterCondition::METHOD_DATETIME_GTE:
161
                return $queryBuilder->expr()->gte($identifier, $token);
162
            case FilterCondition::METHOD_NUMERIC_LTE:
163
            case FilterCondition::METHOD_DATETIME_LTE:
164
                return $queryBuilder->expr()->lte($identifier, $token);
165
            case FilterCondition::METHOD_NUMERIC_LT:
166
            case FilterCondition::METHOD_DATETIME_LT:
167
                return $queryBuilder->expr()->lt($identifier, $token);
168
            case FilterCondition::METHOD_STRING_NEQ:
169
            case FilterCondition::METHOD_NUMERIC_NEQ:
170
            case FilterCondition::METHOD_DATETIME_NEQ:
171
                return $queryBuilder->expr()->neq($identifier, $token);
172
            case FilterCondition::METHOD_IN:
173
                if (!is_array($value)) {
174
                    throw new \Exception('Only arrays can be arguments of a METHOD_IN filter');
175
                }
176
177
                if (count($filterCondition->getValue()) > 0) {
178
                    return $queryBuilder->expr()->in($identifier, $token);
179
                }
180
                // The array is empty, therefore this will always be "false". We use an always-false expression
181
                // to emulate this without actually using an invalid empty array in the IN statement.
182
                return '1=2';
183
            case FilterCondition::METHOD_IS_NULL:
184
                if ($filterCondition->getValue()) {
185
                    return $queryBuilder->expr()->isNull($identifier);
186
                }
187
188
                return $queryBuilder->expr()->isNotNull($identifier);
189
            default:
190
                throw new \Exception("Unknown filtering method \"$filterMethod\" for column \"" . $filterCondition->getFieldName() . '"');
191
        }
192
    }
193
194
    /**
195
     * @param FilterCondition $filterCondition
196
     * @param QueryBuilder    $queryBuilder
197
     *
198
     * @return string
199
     * @throws ColumnNotFoundException
200
     */
201
    protected function buildUniqueToken(FilterCondition $filterCondition, QueryBuilder $queryBuilder)
202
    {
203
        return ':token_'
204
            . strtolower(str_replace(['.', '-'], '_', $filterCondition->getFieldName()))
205
            . '_' . $queryBuilder->getParameters()->count();
206
    }
207
208
    /**
209
     * Helper method: transforms a column identifier to a database field for use
210
     * in filtering and sorting
211
     *
212
     * @param array|Field[] $fields
213
     * @param string        $dataSourceFieldUniqueName
214
     *
215
     * @throws ColumnNotFoundException
216
     * @return mixed
217
     */
218
    protected function getDatabaseFilterQueryFieldByDataSourceFieldUniqueName(array $fields, $dataSourceFieldUniqueName)
219
    {
220
        $dataSourceField = null;
221
        foreach ($fields as $field) {
222
            if ($field->getUniqueName() == $dataSourceFieldUniqueName) {
223
                $dataSourceField = $field;
224
                break;
225
            }
226
        }
227
228
        if (is_null($dataSourceField)) {
229
            throw new ColumnNotFoundException("Could not find column \"$dataSourceFieldUniqueName\" in the data source");
230
        }
231
232
        return $dataSourceField->getDatabaseFilterQueryField();
233
    }
234
}
235