Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
99.07% covered (success)
99.07%
107 / 108
96.43% covered (success)
96.43%
27 / 28
CRAP
0.00% covered (danger)
0.00%
0 / 1
Base
99.07% covered (success)
99.07%
107 / 108
96.43% covered (success)
96.43%
27 / 28
51
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
30 / 30
100.00% covered (success)
100.00%
1 / 1
9
 __toString
n/a
0 / 0
n/a
0 / 0
4
 getName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 addSelect
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 setDistinctSelect
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setResolveLevel
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getResolveLevel
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getAlias
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getTablename
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 addTable
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 addTableAlias
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 addRequiredJoins
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addResolvedReferences
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 setWithEntities
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addJoin
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 leftJoin
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getSql
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getParameters
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getReferenceMapping
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 expression
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addEntityMapping
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getPrefixed
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPrefixedList
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 addReferenceMapping
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 addLimit
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 addValues
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 postProcess
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 postProcessJoins
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 shouldLoadEntity
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
1<?php
2
3namespace BO\Zmsdb\Query;
4
5use BO\Zmsdb\Query\Builder\Select;
6use BO\Zmsdb\Query\Builder\Insert;
7use BO\Zmsdb\Query\Builder\Update;
8use BO\Zmsdb\Query\Builder\Delete;
9use BO\Zmsdb\Query\Builder\Dialect\MySQL;
10use BO\Zmsdb\Query\Builder\Expression;
11
12/**
13 * Base class to construct entity specific queries
14 * Usually used with the interface MappingInterface
15 * Further, it allows to react to resolveReferences as parameter to calling methods
16 */
17
18/**
19 * @SuppressWarnings(NumberOfChildren)
20 * @SuppressWarnings(Complexity)
21 *
22 */
23abstract class Base
24{
25    /**
26     * Identifier for the type of query
27     */
28    const SELECT = 'SELECT';
29    const INSERT = 'INSERT';
30    const UPDATE = 'UPDATE';
31    const REPLACE = 'REPLACE';
32    const DELETE = 'DELETE';
33
34    /**
35     * Name of table in DB
36     */
37    const TABLE = null;
38    /**
39     * Alias used to access TABLE
40     */
41    const ALIAS = null;
42
43    /**
44     * @var \BO\Zmsdb\Query\Builder\Query $query
45     */
46    protected $query = null;
47
48    /**
49     * @var String $query
50     */
51    protected $prefix = '';
52
53    /**
54     * Name of the query used for caching
55     *
56     */
57    protected $name = false;
58
59    /**
60     * Level given ususally by parameter resolveReferences
61     *
62     */
63    protected $resolveLevel = null;
64
65    protected static $sqlCache = [];
66
67    protected $currentSqlString = null;
68
69    /**
70     * List of joined aliasnames to avoid double joins
71     *
72     */
73    protected $joinedAliasList = [];
74
75    /**
76     * List of joined queries to avoid double joins
77     *
78     */
79    protected $joinedQueryList = [];
80
81    protected $withEntities = [];
82
83    /**
84     * Create query builder if necessary
85     *
86     * @param Mixed $queryType one of the constants for a query type or of instance \BO\Zmsdb\Query\Builder\Query
87     * @param String $prefix If used in a subquery, prefix results with this string
88     * @param String $name A named query has a cached SQL as soon as called first
89     */
90    public function __construct($queryType, $prefix = '', $name = false, $resolveLevel = null, $withEntities = [])
91    {
92        $this->prefix = $prefix;
93        $this->name = $name;
94        $this->withEntities = $withEntities;
95        $this->setResolveLevel($resolveLevel);
96        $dialect = new MySQL();
97        if (self::SELECT === $queryType) {
98            $this->query = new Select($dialect);
99            $this->addSelect();
100        } elseif (self::INSERT === $queryType) {
101            $this->query = new Insert($dialect);
102            $this->addTable();
103        } elseif (self::UPDATE === $queryType) {
104            $this->query = new Update($dialect);
105            $this->addTableAlias();
106        } elseif (self::REPLACE === $queryType) {
107            $this->query = new INSERT($dialect);
108            $this->query->queryBaseStatement('REPLACE INTO');
109            $this->addTable();
110        } elseif (self::DELETE === $queryType) {
111            $this->query = new Delete($dialect);
112            $this->query->queryBaseStatement('DELETE ' . $this::getAlias() . ' FROM');
113            $this->addTableAlias();
114        } elseif ($queryType instanceof self) {
115            $this->query = $queryType->query;
116            $this->joinedAliasList =& $queryType->joinedAliasList;
117            $this->resolveLevel = $queryType->resolveLevel - 1;
118        } elseif ($queryType instanceof \BO\Zmsdb\Query\Builder\Query) {
119            $this->query = $queryType;
120        }
121        if ($this->query instanceof Select) {
122            $this->addRequiredJoins();
123        }
124    }
125
126    /**
127     * @codeCoverageIgnore
128     */
129    public function __toString()
130    {
131        if ($this->name) {
132            $name = $this->name . '_' . $this->prefix . (string) $this->resolveLevel;
133            if (!isset(static::$sqlCache[$name])) {
134                static::$sqlCache[$name] = $this->getSql();
135            }
136            return static::$sqlCache[$name];
137        }
138        if ($this->currentSqlString) {
139            $sql = $this->currentSqlString;
140        } else {
141            $sql = $this->getSql();
142        }
143        return $sql;
144    }
145
146    public function getName()
147    {
148        return $this->name ? $this->name : get_class($this);
149    }
150
151    /**
152     * Add the from part to the queryBaseStatement
153     * This implementation tries to guess the syntax using the constant TABLE in the class
154     * Override the method for a special implementation or required joins
155     *
156     * @return self
157     */
158    protected function addSelect()
159    {
160        $table = $this::getTablename();
161        $alias = $this::getAlias();
162        $this->query->from($table, $alias);
163        return $this;
164    }
165
166    public function setDistinctSelect()
167    {
168        $this->query->queryBaseStatement('SELECT DISTINCT');
169    }
170
171    public function setResolveLevel($resolveLevel)
172    {
173        if ($resolveLevel !== null) {
174            $this->resolveLevel = $resolveLevel;
175        }
176        return $this;
177    }
178
179    public function getResolveLevel()
180    {
181        if (null === $this->resolveLevel) {
182            throw new \Exception("Required setting for resolveReferenceLevel missing in " . get_class($this));
183        }
184        return $this->resolveLevel;
185    }
186
187    /**
188     * Add the alias part to the queryBaseStatement
189     * This implementation tries to guess the syntax using the constant TABLE in the class
190     * Override the method for a special implementation or required joins
191     *
192     * @return self
193     */
194    public static function getAlias()
195    {
196        $class = get_called_class();
197        $alias = constant($class . '::ALIAS');
198        if (null === $alias) {
199            $alias = lcfirst(preg_replace('#^.*\\\#', '', $class));
200        }
201        return $alias;
202    }
203
204    /**
205     * Get the table name for the query
206     *
207     * @return string
208     */
209    public static function getTablename()
210    {
211        $class = get_called_class();
212        $table = constant($class . '::TABLE');
213        return $table;
214    }
215
216    /**
217     * Add the from part to the queryBaseStatement
218     * This implementation tries to guess the syntax using the constant TABLE in the class
219     * Override the method for a special implementation or required joins
220     *
221     * @return self
222     */
223    protected function addTable()
224    {
225        $table = $this::getTablename();
226        $alias = $this::getAlias();
227        $this->query->table($table, $alias);
228        return $this;
229    }
230
231    /**
232     * Add the from part to the queryBaseStatement
233     * This implementation tries to guess the syntax using the constant TABLE in the class
234     * Override the method for a special implementation or required joins
235     *
236     * @return self
237     */
238    protected function addTableAlias()
239    {
240        $table = $this::getTablename();
241        $alias = $this::getAlias();
242        $this->query->table(self::expression($table . ' ' . $alias));
243        return $this;
244    }
245
246    /**
247     * Add joins to table if required
248     * Override this method if join are required for a select
249     */
250    protected function addRequiredJoins()
251    {
252    }
253
254    /**
255     * resolves references by joining tables defined in the method addJoin()
256     *
257     * @param  Int $depth Number of levels of sub references to resolve
258     * @return self
259     */
260    public function addResolvedReferences($depth)
261    {
262        $this->setResolveLevel($depth);
263        if ($depth > 0) {
264            $queryList = $this->addJoin();
265            foreach ($queryList as $query) {
266                $query->setResolveLevel($depth);
267                $query->setWithEntities($this->withEntities);
268                $query->addResolvedReferences($depth - 1);
269                $query->addEntityMapping();
270            }
271            $this->joinedQueryList = $queryList;
272        } else {
273            $this->addReferenceMapping();
274        }
275        return $this;
276    }
277
278    public function setWithEntities($withEntities = [])
279    {
280        $this->withEntities = $withEntities;
281    }
282
283    /**
284     * If resolveReferences is required, override this method
285     *
286     * @return Array of self
287     */
288    protected function addJoin()
289    {
290        return [];
291    }
292
293    protected function leftJoin($alias, $left = null, $operator = null, $right = null)
294    {
295        $aliasId = $alias->getAliasIdentifier();
296        //error_log(get_class($this) . " JOIN $aliasId CHECK " . implode(',', $this->joinedAliasList));
297        if (!in_array($aliasId, $this->joinedAliasList)) {
298            $this->joinedAliasList[] = $aliasId;
299            $this->query->leftJoin($alias, $left, $operator, $right);
300        } else {
301            //throw new \Exception("Tried to add Alias ".$aliasId);
302        }
303        return $this->query;
304    }
305
306    /**
307     * get SQL-String
308     * Implement a simple caching routine to prevent multiple rebuilds
309     *
310     * @return String
311     */
312    public function getSql()
313    {
314        $this->currentSqlString = (string)$this->query;
315        return $this->currentSqlString;
316    }
317
318    /**
319     * List of parameters to use for a prepared statement
320     *
321     * @return Array
322     */
323    public function getParameters()
324    {
325        return $this->query->params();
326    }
327
328    public function getReferenceMapping()
329    {
330        return [
331        ];
332    }
333
334    /**
335     * Shortcut to create an SQL-Expression without quoting
336     *
337     * @return \BO\Zmsdb\Query\Builder\Expression
338     */
339    protected static function expression($string)
340    {
341        return new Expression($string);
342    }
343
344    /**
345     * Add a select part to the query containing a mapping from the db schema to the entity schema
346     *
347     * @return self
348     */
349    public function addEntityMapping($type = null)
350    {
351        $entityMapping = $this->getPrefixedList($this->getEntityMapping($type));
352        $this->query->select($entityMapping);
353        return $this;
354    }
355
356    protected function getPrefixed($prefix)
357    {
358        return $this->prefix . $prefix;
359    }
360
361    protected function getPrefixedList($unprefixedList)
362    {
363        $prefixed = [];
364        foreach ($unprefixedList as $key => $value) {
365            $prefixed[$this->getPrefixed($key)] = $value;
366        }
367        return $prefixed;
368    }
369
370    /**
371     * Add a select part to the query containing references if no resolveReferences is given
372     *
373     * @return self
374     */
375    protected function addReferenceMapping()
376    {
377        $referenceMapping = $this->getPrefixedList($this->getReferenceMapping());
378        $this->query->select($referenceMapping);
379        return $this;
380    }
381
382    public function addLimit($count, $offset = null)
383    {
384        $this->query->limit($count);
385        if ($offset) {
386            $this->query->offset($offset);
387        }
388        return $this;
389    }
390
391    /**
392     * Add values to a insert or update query
393     *
394     * @return self
395     */
396    public function addValues($values)
397    {
398        $this->query->values($values);
399        return $this;
400    }
401
402    /**
403     * postProcess data if necessary
404     *
405     */
406    public function postProcess($data)
407    {
408        return $data;
409    }
410
411    /**
412     * postProcess data including joined queries if necessary
413     *
414     */
415    public function postProcessJoins($data)
416    {
417        $data = $this->postProcess($data);
418        foreach ($this->joinedQueryList as $query) {
419            $data = $query->postProcess($data);
420        }
421        return $data;
422    }
423
424    public function shouldLoadEntity($name)
425    {
426        if (empty($this->withEntities)) {
427            return true;
428        }
429
430        return in_array($name, $this->withEntities);
431    }
432}