Passed
Push — master ( d35d1c...c70304 )
by Andreas
03:50
created

query::get_doctrine()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
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\QueryBuilder;
13
use Doctrine\ORM\Query\Expr\Join;
14
use Doctrine\ORM\Query\Expr;
15
16
abstract class query
17
{
18
    /**
19
     *
20
     * @var \Doctrine\ORM\QueryBuilder
21
     */
22
    protected $qb;
23
24
    /**
25
     *
26
     * @var boolean
27
     */
28
    protected $include_deleted = false;
29
30
    /**
31
     *
32
     * @var int
33
     */
34
    protected $parameters = 0;
35
36
    /**
37
     *
38
     * @var string
39
     */
40
    protected $classname = null;
41
42
    /**
43
     *
44
     * @var array
45
     */
46
    protected $groupstack = array();
47
48
    /**
49
     *
50
     * @var array
51
     */
52
    protected $join_tables = array();
53
54 57
    public function __construct($class)
55
    {
56 57
        $this->classname = $class;
57 57
        $this->qb = connection::get_em()->createQueryBuilder();
58 57
        $this->qb->from($class, 'c');
59 57
    }
60
61
    abstract public function execute();
62
63
    /**
64
     * @return \Doctrine\ORM\QueryBuilder
65
     */
66
    public function get_doctrine()
67
    {
68
        return $this->qb;
69
    }
70
71 1
    public function add_constraint_with_property($name, $operator, $property)
72 1
    {
73
        //TODO: INTREE & IN operator functionality ?
74 1
        $parsed = $this->parse_constraint_name($name);
75 1
        $parsed_property = $this->parse_constraint_name($property);
76 1
        $constraint = $parsed['name'] . ' ' . $operator . ' ' . $parsed_property['name'];
77
78 1
        $this->get_current_group()->add($constraint);
79
80 1
        return true;
81
    }
82
83 48
    public function add_constraint($name, $operator, $value)
84
    {
85 48
        if ($operator === 'INTREE') {
86 1
            $operator = 'IN';
87 1
            $targetclass = $this->classname;
88 1
            $fieldname = $name;
89
90 1
            if (strpos($name, '.') !== false) {
91 1
                $parsed = $this->parse_constraint_name($name);
92 1
                $fieldname = $parsed['column'];
93 1
                $targetclass = $parsed['targetclass'];
94 1
            }
95
96 1
            $mapping = connection::get_em()->getClassMetadata($targetclass)->getAssociationMapping($fieldname);
97 1
            $parentfield = $name;
98
99 1
            if ($mapping['targetEntity'] !== get_class($this)) {
100 1
                $cm = connection::get_em()->getClassMetadata($mapping['targetEntity']);
101 1
                $parentfield = $cm->midgard['upfield'];
0 ignored issues
show
Bug introduced by
The property midgard does not seem to exist in Doctrine\ORM\Mapping\ClassMetadata.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
102 1
            }
103
104 1
            $value = (array) $value;
105 1
            $value = array_merge($value, $this->get_child_ids($mapping['targetEntity'], $parentfield, $value));
106 1
        } elseif (   $operator === 'IN'
107 47
                 || $operator === 'NOT IN') {
108 1
            $value = array_values($value);
109 47
        } elseif (!in_array($operator, array('=', '>', '<', '<>', '<=', '>=', 'LIKE', 'NOT LIKE'))) {
110 4
            return false;
111
        }
112 47
        $this->parameters++;
113 47
        $this->get_current_group()->add($this->build_constraint($name, $operator, $value));
114 46
        $this->qb->setParameter($this->parameters, $value);
115
116 46
        return true;
117
    }
118
119 3
    public function add_order($name, $direction = 'ASC')
120
    {
121 3
        if (!in_array($direction, array('ASC', 'DESC'))) {
122 1
            return false;
123
        }
124
        try {
125 3
            $parsed = $this->parse_constraint_name($name);
126 3
        } catch (exception $e) {
127 1
            return false;
128
        }
129
130 3
        $this->qb->addOrderBy($parsed['name'], $direction);
131 3
        return true;
132
    }
133
134 9
    public function count()
135
    {
136 9
        $select = $this->qb->getDQLPart('select');
137 9
        $this->check_groups();
138 9
        $this->qb->select("count(c.id)");
139 9
        $this->pre_execution();
140 9
        $count = intval($this->qb->getQuery()->getSingleScalarResult());
141
142 9
        $this->post_execution();
143 9
        if (empty($select)) {
144 9
            $this->qb->resetDQLPart('select');
145 9
        } else {
146
            $this->qb->add('select', $select);
147
        }
148 9
        return $count;
149
    }
150
151 2
    public function set_limit($limit)
152
    {
153 2
        $this->qb->setMaxResults($limit);
154 2
    }
155
156 1
    public function set_offset($offset)
157
    {
158 1
        $this->qb->setFirstResult($offset);
159 1
    }
160
161 12
    public function include_deleted()
162
    {
163 12
        $this->include_deleted = true;
164 12
    }
165
166 50
    public function begin_group($operator = 'OR')
167
    {
168 50
        if ($operator === 'OR') {
169 2
            $this->groupstack[] = $this->qb->expr()->orX();
170 50
        } elseif ($operator === 'AND') {
171 48
            $this->groupstack[] = $this->qb->expr()->andX();
172 48
        } else {
173 1
            return false;
174
        }
175
176 49
        return true;
177
    }
178
179 46
    public function end_group()
180
    {
181 46
        if (empty($this->groupstack)) {
182 1
            return false;
183
        }
184 45
        $group = array_pop($this->groupstack);
185 45
        if ($group->count() > 0) {
186 44
            if (!empty($this->groupstack)) {
187 1
                $this->get_current_group()->add($group);
188 1
            } else {
189 44
                $this->qb->andWhere($group);
190
            }
191 44
        }
192 45
        return true;
193
    }
194
195
    /**
196
     * @return Doctrine\ORM\Query\Expr:
0 ignored issues
show
Documentation introduced by
The doc-type Doctrine\ORM\Query\Expr: could not be parsed: Unknown type name "Doctrine\ORM\Query\Expr:" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
197
     */
198 48
    public function get_current_group()
199
    {
200 48
        if (empty($this->groupstack)) {
201 48
            $this->begin_group('AND');
202 48
        }
203
204 48
        return $this->groupstack[(count($this->groupstack) - 1)];
205
    }
206
207 49
    protected function pre_execution()
208
    {
209 49
        if ($this->include_deleted) {
210 12
            connection::get_em()->getFilters()->disable('softdelete');
211 12
        }
212 49
    }
213
214 49
    protected function post_execution()
215
    {
216 49
        if ($this->include_deleted) {
217 12
            connection::get_em()->getFilters()->enable('softdelete');
218 12
        }
219 49
    }
220
221 1
    protected function add_collection_join($current_table, $targetclass)
222
    {
223 1
        if (!array_key_exists($targetclass, $this->join_tables)) {
224 1
            $this->join_tables[$targetclass] = 'j' . count($this->join_tables);
225 1
            $c = $this->join_tables[$targetclass] . ".parentguid = " . $current_table . ".guid";
226 1
            $this->qb->innerJoin("midgard:" . $targetclass, $this->join_tables[$targetclass], Join::WITH, $c);
227 1
        }
228 1
        return $this->join_tables[$targetclass];
229
    }
230
231 6
    protected function add_join($current_table, $mrp, $property)
232
    {
233 6
        $targetclass = $mrp->get_link_name($property);
234 6
        if (!array_key_exists($targetclass, $this->join_tables)) {
235 6
            $this->join_tables[$targetclass] = 'j' . count($this->join_tables);
236
237
            // custom join
238 6
            if ($mrp->is_special_link($property)) {
239 1
                $c = $this->join_tables[$targetclass] . "." . $mrp->get_link_target($property) . " = " . $current_table . "." . $property;
240 1
                $this->qb->innerJoin("midgard:" . $targetclass, $this->join_tables[$targetclass], Join::WITH, $c);
241 1
            } else {
242 5
                $this->qb->join($current_table . '.' . $property, $this->join_tables[$targetclass]);
243
            }
244 6
        }
245 6
        return $this->join_tables[$targetclass];
246
    }
247
248 50
    protected function parse_constraint_name($name)
249
    {
250 50
        $current_table = 'c';
251 50
        $targetclass = $this->classname;
252
253
        // metadata
254 50
        $name = str_replace('metadata.', 'metadata_', $name);
255 50
        $column = $name;
256 50
        if (strpos($name, ".") !== false) {
257 7
            $parts = explode('.', $name);
258 7
            $column = array_pop($parts);
259 7
            foreach ($parts as $part) {
260
                if (   $part === 'parameter'
261 7
                    || $part === 'attachment') {
262 1
                    $targetclass = 'midgard_' . $part;
263 1
                    $current_table = $this->add_collection_join($current_table, $targetclass);
264 1
                } else {
265 6
                    $mrp = new \midgard_reflection_property($targetclass);
266
267 6
                    if (   !$mrp->is_link($part)
268 6
                        && !$mrp->is_special_link($part)) {
269
                        throw exception::ok();
270
                    }
271 6
                    $targetclass = $mrp->get_link_name($part);
272 6
                    $current_table = $this->add_join($current_table, $mrp, $part);
273
                }
274 7
            }
275
            // mrp only gives us non-namespaced classnames, so we make it an alias
276 7
            $targetclass = 'midgard:' . $targetclass;
277 7
        }
278
279 50
        $cm = connection::get_em()->getClassMetadata($targetclass);
280 50
        if (array_key_exists($column, $cm->midgard['field_aliases'])) {
281 2
            $column = $cm->midgard['field_aliases'][$column];
0 ignored issues
show
Bug introduced by
The property midgard does not seem to exist in Doctrine\ORM\Mapping\ClassMetadata.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
282 2
        }
283
284 50
        if (   !$cm->hasField($column)
285 50
            && !$cm->hasAssociation($column)) {
286 3
            throw exception::ok();
287
        }
288
289
        return array(
290 49
            'name' => $current_table . '.' . $column,
291 49
            'column' => $column,
292
            'targetclass' => $targetclass
293 49
        );
294
    }
295
296 47
    protected function build_constraint($name, $operator, $value)
297
    {
298 47
        $parsed = $this->parse_constraint_name($name);
299 46
        $expression = $operator . ' ?' . $this->parameters;
300
301
        if (   $operator === 'IN'
302 46
            || $operator === 'NOT IN') {
303 2
            $expression = $operator . '( ?' . $this->parameters . ')';
304 2
        }
305
306
        if (   $value === 0
307 46
            || $value === null
308 46
            || is_array($value)) {
309 7
            $cm = connection::get_em()->getClassMetadata($parsed['targetclass']);
310 7
            if ($cm->hasAssociation($parsed['column'])) {
311 7
                $group = false;
312
                // TODO: there seems to be no way to make Doctrine accept default values for association fields,
313
                // so we need a silly workaorund for existing DBs
314 7
                if ($operator === '<>' || $operator === '>') {
315 3
                    $group = $this->qb->expr()->andX();
316 3
                    $group->add($parsed['name'] . ' IS NOT NULL');
317 7
                } elseif ($operator === 'IN') {
318 2
                    if (array_search(0, $value) !== false) {
319 1
                        $group = $this->qb->expr()->orX();
320 1
                        $group->add($parsed['name'] . ' IS NULL');
321 1
                    }
322 6
                } elseif ($operator === 'NOT IN') {
323 1
                    if (array_search(0, $value) === false) {
324 1
                        $group = $this->qb->expr()->orX();
325 1
                        $group->add($parsed['name'] . ' IS NULL');
326 1
                    }
327 1
                } else {
328 4
                    $group = $this->qb->expr()->orX();
329 4
                    $group->add($parsed['name'] . ' IS NULL');
330
                }
331 7
                if ($group) {
332 6
                    $group->add($parsed['name'] . ' ' . $expression);
333 6
                    return $group;
334 1
                }
335 1
            }
336 2
        }
337
338 42
        return $parsed['name'] . ' ' . $expression;
339
    }
340
341 49
    protected function check_groups()
342
    {
343 49
        while (!empty($this->groupstack)) {
344 44
            $this->end_group();
345 44
        }
346 49
    }
347
348 1
    private function get_child_ids($targetclass, $fieldname, array $parent_values)
349
    {
350 1
        $qb = connection::get_em()->createQueryBuilder();
351 1
        $qb->from($targetclass, 'c')
352 1
            ->where('c.' . $fieldname . ' IN (?0)')
353 1
            ->setParameter(0, $parent_values)
354 1
            ->select("c.id");
355
356 1
        $this->pre_execution();
357 1
        $results = $qb->getQuery()->getScalarResult();
358 1
        $this->post_execution();
359
360 1
        $ids = array_map('current', $results);
361 1
        if (!empty($ids)) {
362 1
            $ids = array_merge($ids, $this->get_child_ids($targetclass, $fieldname, $ids));
363 1
        }
364
365 1
        return $ids;
366
    }
367
}
368