Passed
Branch master (056094)
by Andreas
04:47
created

query::parse_constraint_name()   D

Complexity

Conditions 10
Paths 9

Size

Total Lines 45
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 10.0033

Importance

Changes 0
Metric Value
cc 10
eloc 30
c 0
b 0
f 0
nc 9
nop 1
dl 0
loc 45
ccs 30
cts 31
cp 0.9677
crap 10.0033
rs 4.8196

How to fix   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
 * @author CONTENT CONTROL http://www.contentcontrol-berlin.de/
4
 * @copyright CONTENT CONTROL http://www.contentcontrol-berlin.de/
5
 * @license http://www.gnu.org/licenses/gpl.html GNU General Public License
6
 */
7
8
namespace midgard\portable;
9
10
use midgard\portable\storage\connection;
11
use midgard\portable\api\error\exception;
12
use Doctrine\ORM\Query\Expr\Join;
13
14
abstract class query
15
{
16
    /**
17
     *
18
     * @var \Doctrine\ORM\QueryBuilder
19
     */
20
    protected $qb;
21
22
    /**
23
     *
24
     * @var boolean
25
     */
26
    protected $include_deleted = false;
27
28
    /**
29
     *
30
     * @var int
31
     */
32
    protected $parameters = 0;
33
34
    /**
35
     *
36
     * @var string
37
     */
38
    protected $classname = null;
39
40
    /**
41
     *
42
     * @var array
43
     */
44
    protected $groupstack = [];
45
46
    /**
47
     *
48
     * @var array
49
     */
50
    protected $join_tables = [];
51
52 58
    public function __construct($class)
53 1
    {
54 58
        $this->classname = $class;
55 58
        $this->qb = connection::get_em()->createQueryBuilder();
56 58
        $this->qb->from($class, 'c');
57 58
    }
58
59
    abstract public function execute();
60
61
    /**
62
     * @return \Doctrine\ORM\QueryBuilder
63
     */
64
    public function get_doctrine()
65
    {
66
        return $this->qb;
67
    }
68
69 2
    public function add_constraint_with_property($name, $operator, $property)
70
    {
71
        //TODO: INTREE & IN operator functionality ?
72 2
        $parsed = $this->parse_constraint_name($name);
73 1
        $parsed_property = $this->parse_constraint_name($property);
74 1
        $constraint = $parsed['name'] . ' ' . $operator . ' ' . $parsed_property['name'];
75
76 1
        $this->get_current_group()->add($constraint);
77
78 1
        return true;
79
    }
80
81 48
    public function add_constraint($name, $operator, $value)
82
    {
83 48
        if ($operator === 'INTREE') {
84 1
            $operator = 'IN';
85 1
            $targetclass = $this->classname;
86 1
            $fieldname = $name;
87
88 1
            if (strpos($name, '.') !== false) {
89 1
                $parsed = $this->parse_constraint_name($name);
90 1
                $fieldname = $parsed['column'];
91 1
                $targetclass = $parsed['targetclass'];
92 1
            }
93
94 1
            $mapping = connection::get_em()->getClassMetadata($targetclass)->getAssociationMapping($fieldname);
95 1
            $parentfield = $name;
96
97 1
            if ($mapping['targetEntity'] !== get_class($this)) {
98 1
                $cm = connection::get_em()->getClassMetadata($mapping['targetEntity']);
99 1
                $parentfield = $cm->midgard['upfield'];
100 1
            }
101
102 1
            $value = (array) $value;
103 1
            $value = array_merge($value, $this->get_child_ids($mapping['targetEntity'], $parentfield, $value));
104 1
        } elseif (   $operator === 'IN'
105 47
                 || $operator === 'NOT IN') {
106 1
            $value = array_values($value);
107 47
        } elseif (!in_array($operator, ['=', '>', '<', '<>', '<=', '>=', 'LIKE', 'NOT LIKE'])) {
108 1
            return false;
109
        }
110 47
        $this->parameters++;
111 47
        $this->get_current_group()->add($this->build_constraint($name, $operator, $value));
112 46
        $this->qb->setParameter($this->parameters, $value);
113
114 46
        return true;
115
    }
116
117 4
    public function add_order($name, $direction = 'ASC')
118
    {
119 4
        if (!in_array($direction, ['ASC', 'DESC'])) {
120 1
            return false;
121
        }
122
        try {
123 4
            $parsed = $this->parse_constraint_name($name);
124 4
        } catch (exception $e) {
125 1
            return false;
126
        }
127
128 4
        $this->qb->addOrderBy($parsed['name'], $direction);
129 4
        return true;
130
    }
131
132 9
    public function count()
133
    {
134 9
        $select = $this->qb->getDQLPart('select');
135 9
        $this->check_groups();
136 9
        $this->qb->select("count(c.id)");
137 9
        $this->pre_execution();
138 9
        $count = intval($this->qb->getQuery()->getSingleScalarResult());
139
140 9
        $this->post_execution();
141 9
        if (empty($select)) {
142 9
            $this->qb->resetDQLPart('select');
143 9
        } else {
144
            $this->qb->add('select', $select);
145
        }
146 9
        return $count;
147
    }
148
149 2
    public function set_limit($limit)
150
    {
151 2
        $this->qb->setMaxResults($limit);
152 2
    }
153
154 1
    public function set_offset($offset)
155
    {
156 1
        $this->qb->setFirstResult($offset);
157 1
    }
158
159 12
    public function include_deleted()
160
    {
161 12
        $this->include_deleted = true;
162 12
    }
163
164 50
    public function begin_group($operator = 'OR')
165
    {
166 50
        if ($operator === 'OR') {
167 2
            $this->groupstack[] = $this->qb->expr()->orX();
168 50
        } elseif ($operator === 'AND') {
169 48
            $this->groupstack[] = $this->qb->expr()->andX();
170 48
        } else {
171 1
            return false;
172
        }
173
174 49
        return true;
175
    }
176
177 46
    public function end_group()
178
    {
179 46
        if (empty($this->groupstack)) {
180 1
            return false;
181
        }
182 45
        $group = array_pop($this->groupstack);
183 45
        if ($group->count() > 0) {
184 44
            if (!empty($this->groupstack)) {
185 1
                $this->get_current_group()->add($group);
186 1
            } else {
187 44
                $this->qb->andWhere($group);
188
            }
189 44
        }
190 45
        return true;
191
    }
192
193
    /**
194
     * @return \Doctrine\ORM\Query\Expr\Composite
195
     */
196 48
    public function get_current_group()
197
    {
198 48
        if (empty($this->groupstack)) {
199 48
            $this->begin_group('AND');
200 48
        }
201
202 48
        return $this->groupstack[(count($this->groupstack) - 1)];
203
    }
204
205 50
    protected function pre_execution()
206
    {
207 50
        if ($this->include_deleted) {
208 12
            connection::get_em()->getFilters()->disable('softdelete');
209 12
        }
210 50
    }
211
212 50
    protected function post_execution()
213
    {
214 50
        if ($this->include_deleted) {
215 12
            connection::get_em()->getFilters()->enable('softdelete');
216 12
        }
217 50
    }
218
219 1
    protected function add_collection_join($current_table, $targetclass)
220
    {
221 1
        if (!array_key_exists($targetclass, $this->join_tables)) {
222 1
            $this->join_tables[$targetclass] = 'j' . count($this->join_tables);
223 1
            $c = $this->join_tables[$targetclass] . ".parentguid = " . $current_table . ".guid";
224 1
            $this->qb->innerJoin("midgard:" . $targetclass, $this->join_tables[$targetclass], Join::WITH, $c);
225 1
        }
226 1
        return $this->join_tables[$targetclass];
227
    }
228
229 6
    protected function add_join($current_table, $mrp, $property)
230
    {
231 6
        $targetclass = $mrp->get_link_name($property);
232 6
        if (!array_key_exists($targetclass, $this->join_tables)) {
233 6
            $this->join_tables[$targetclass] = 'j' . count($this->join_tables);
234
235
            // custom join
236 6
            if ($mrp->is_special_link($property)) {
237 1
                $c = $this->join_tables[$targetclass] . "." . $mrp->get_link_target($property) . " = " . $current_table . "." . $property;
238 1
                $this->qb->innerJoin("midgard:" . $targetclass, $this->join_tables[$targetclass], Join::WITH, $c);
239 1
            } else {
240 5
                $this->qb->leftJoin($current_table . '.' . $property, $this->join_tables[$targetclass]);
241
            }
242 6
        }
243 6
        return $this->join_tables[$targetclass];
244
    }
245
246 51
    protected function parse_constraint_name($name)
247
    {
248 51
        $current_table = 'c';
249 51
        $targetclass = $this->classname;
250
251
        // metadata
252 51
        $name = str_replace('metadata.', 'metadata_', $name);
253 51
        $column = $name;
254 51
        if (strpos($name, ".") !== false) {
255 7
            $parts = explode('.', $name);
256 7
            $column = array_pop($parts);
257 7
            foreach ($parts as $part) {
258
                if (   $part === 'parameter'
259 7
                    || $part === 'attachment') {
260 1
                    $targetclass = 'midgard_' . $part;
261 1
                    $current_table = $this->add_collection_join($current_table, $targetclass);
262 1
                } else {
263 6
                    $mrp = new \midgard_reflection_property($targetclass);
264
265 6
                    if (   !$mrp->is_link($part)
266 6
                        && !$mrp->is_special_link($part)) {
267
                        throw exception::ok();
268
                    }
269 6
                    $targetclass = $mrp->get_link_name($part);
270 6
                    $current_table = $this->add_join($current_table, $mrp, $part);
271
                }
272 7
            }
273
            // mrp only gives us non-namespaced classnames, so we make it an alias
274 7
            $targetclass = 'midgard:' . $targetclass;
275 7
        }
276
277 51
        $cm = connection::get_em()->getClassMetadata($targetclass);
278 51
        if (array_key_exists($column, $cm->midgard['field_aliases'])) {
279 2
            $column = $cm->midgard['field_aliases'][$column];
280 2
        }
281
282 51
        if (   !$cm->hasField($column)
283 51
            && !$cm->hasAssociation($column)) {
284 3
            throw exception::ok();
285
        }
286
287
        return [
288 50
            'name' => $current_table . '.' . $column,
289 50
            'column' => $column,
290
            'targetclass' => $targetclass
291 50
        ];
292
    }
293
294 47
    protected function build_constraint($name, $operator, $value)
295
    {
296 47
        $parsed = $this->parse_constraint_name($name);
297 46
        $expression = $operator . ' ?' . $this->parameters;
298
299
        if (   $operator === 'IN'
300 46
            || $operator === 'NOT IN') {
301 2
            $expression = $operator . '( ?' . $this->parameters . ')';
302 2
        }
303
304
        if (   $value === 0
305 46
            || $value === null
306 46
            || is_array($value)) {
307 7
            $cm = connection::get_em()->getClassMetadata($parsed['targetclass']);
308 7
            if ($cm->hasAssociation($parsed['column'])) {
309 7
                $group = false;
310
                // TODO: there seems to be no way to make Doctrine accept default values for association fields,
311
                // so we need a silly workaorund for existing DBs
312 7
                if ($operator === '<>' || $operator === '>') {
313 3
                    $group = $this->qb->expr()->andX();
314 3
                    $group->add($parsed['name'] . ' IS NOT NULL');
315 7
                } elseif ($operator === 'IN') {
316 2
                    if (array_search(0, $value) !== false) {
317 1
                        $group = $this->qb->expr()->orX();
318 1
                        $group->add($parsed['name'] . ' IS NULL');
319 1
                    }
320 6
                } elseif ($operator === 'NOT IN') {
321 1
                    if (array_search(0, $value) === false) {
322 1
                        $group = $this->qb->expr()->orX();
323 1
                        $group->add($parsed['name'] . ' IS NULL');
324 1
                    }
325 1
                } else {
326 4
                    $group = $this->qb->expr()->orX();
327 4
                    $group->add($parsed['name'] . ' IS NULL');
328
                }
329 7
                if ($group) {
330 6
                    $group->add($parsed['name'] . ' ' . $expression);
331 6
                    return $group;
332
                }
333 1
            }
334 3
        }
335
336 42
        return $parsed['name'] . ' ' . $expression;
337
    }
338
339 50
    protected function check_groups()
340
    {
341 50
        while (!empty($this->groupstack)) {
342 44
            $this->end_group();
343 44
        }
344 50
    }
345
346 1
    private function get_child_ids($targetclass, $fieldname, array $parent_values)
347
    {
348 1
        $qb = connection::get_em()->createQueryBuilder();
349 1
        $qb->from($targetclass, 'c')
350 1
            ->where('c.' . $fieldname . ' IN (?0)')
351 1
            ->setParameter(0, $parent_values)
352 1
            ->select("c.id");
353
354 1
        $this->pre_execution();
355 1
        $results = $qb->getQuery()->getScalarResult();
356 1
        $this->post_execution();
357
358 1
        $ids = array_map('current', $results);
359 1
        if (!empty($ids)) {
360 1
            $ids = array_merge($ids, $this->get_child_ids($targetclass, $fieldname, $ids));
361 1
        }
362
363 1
        return $ids;
364
    }
365
}
366