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