OrmStatement::createQuery()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 8
cts 8
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 2
nop 8
crap 3

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
namespace Nkey\Caribu\Orm;
3
4
/**
5
 * Statement provider for Caribu Orm
6
 *
7
 * This class is part of Caribu package
8
 *
9
 * @author Maik Greubel <[email protected]>
10
 */
11
trait OrmStatement
12
{
13
    /**
14
     * Include entity analyzing related functionality
15
     */
16
    use OrmEntityAnalyzer;
17
18
    /**
19
     * Create a query for selection
20
     *
21
     * @param string $class
22
     *            The class for which the query will be created
23
     * @param array $criteria
24
     *            Array of criterias in form of "property" => "value"
25
     * @param array $columns
26
     *            The columns to retrieve
27
     * @param string $orderBy
28
     *            An order-by statement in form of "property ASC|DESC"
29
     * @param int $limit
30
     *            The maximum amount of results
31
     * @param int $startFrom
32
     *            The offset where to get results of
33
     * @param string $escapeSign
34
     *            The string which represents the escape character
35
     *            
36
     * @return string The query as sql statement
37
     *        
38
     * @throws OrmException
39
     */
40 28
    private static function createQuery(string $class, string $tableName, array &$criteria, array $columns, string $orderBy = '', int $limit = 0, int $startFrom = 0, string $escapeSign = ""): string
41
    {
42 28
        $joins = self::getAnnotatedQuery($class, $tableName, $criteria, $columns, $escapeSign);
43
        
44 28
        $wheres = self::parseCriteria($criteria, $escapeSign);
45
        
46 27
        $limits = self::parseLimits($limit, $startFrom);
47
        
48 27
        if ($orderBy && ! stristr($orderBy, 'ORDER BY ')) {
49 2
            $orderBy = sprintf("ORDER BY %s%s%s", $escapeSign, $orderBy, $escapeSign);
50
        }
51
        
52 27
        $query = sprintf("SELECT %s FROM %s%s%s %s %s %s %s", implode(',', $columns), $escapeSign, $tableName, $escapeSign, $joins, $wheres, $orderBy, $limits);
53
        
54 27
        return $query;
55
    }
56
57
    /**
58
     * Create a insert or update statement
59
     *
60
     * @param string $class
61
     *            The class of entity
62
     * @param array $pairs
63
     *            The pairs of columns and its corresponding values
64
     * @param string $primaryKeyCol
65
     *            The name of column which represents the primary key
66
     * @param mixed $primaryKeyValue
67
     *            The primary key value
68
     * @param string $escapeSign
69
     *            The string which represents the escape character
70
     *            
71
     * @return string The update statement as SQL string
72
     */
73 12
    private static function createUpdateStatement(string $class, array $pairs, string $primaryKeyCol, $primaryKeyValue, string $escapeSign): string
74
    {
75 12
        $tableName = self::getTableName($class);
76
        
77 12
        $query = sprintf("INSERT INTO %s%s%s ", $escapeSign, $tableName, $escapeSign);
78 12
        if ($primaryKeyValue) {
79 6
            $query = sprintf("UPDATE %s%s%s ", $escapeSign, $tableName, $escapeSign);
80
        }
81
        
82 12
        $query .= self::persistenceQueryParams($pairs, $primaryKeyCol, is_null($primaryKeyValue), $escapeSign);
83
        
84 12
        if ($primaryKeyValue) {
85 6
            $query .= sprintf(" WHERE %s%s%s = :%s", $escapeSign, $primaryKeyCol, $escapeSign, $primaryKeyCol);
86
        }
87
        
88 12
        return $query;
89
    }
90
91
    /**
92
     * Escape all parts of a criterion
93
     *
94
     * @param string $criterion
95
     *            The criterion pattern
96
     * @param string $escapeSign
97
     *            The escape sign
98
     *            
99
     * @return string The escaped criterion
100
     */
101 25
    private static function escapeCriterion(string $criterion, string $escapeSign)
102
    {
103 25
        $criterionEscaped = '';
104 25
        $criterionParts = explode('.', $criterion);
105
        
106 25
        foreach ($criterionParts as $part) {
107 25
            $criterionEscaped .= $criterionEscaped ? '.' : '';
108 25
            $criterionEscaped .= sprintf("%s%s%s", $escapeSign, $part, $escapeSign);
109
        }
110
        
111 25
        return $criterionEscaped;
112
    }
113
114
    /**
115
     * Parse criteria into where conditions
116
     *
117
     * @param array $criteria
118
     *            The criteria to parse
119
     * @param string $escapeSign
120
     *            The escape sign
121
     *            
122
     * @return string The parsed criterias as where condition string
123
     *        
124
     * @throws OrmException
125
     */
126 28
    private static function parseCriteria(array &$criteria, string $escapeSign): string
127
    {
128 28
        $wheres = array();
129
        
130 28
        $criterias = array_keys($criteria);
131
        
132 28
        foreach ($criterias as $criterion) {
133 26
            $placeHolder = str_replace('.', '_', $criterion);
134 26
            $placeHolder = str_replace('OR ', 'OR_', $placeHolder);
135 26
            if (strtoupper(substr($criteria[$criterion], 0, 4)) == 'LIKE') {
136 2
                $wheres[] = sprintf("%s LIKE :%s", self::escapeCriterion($criterion, $escapeSign), $placeHolder);
137 24
            } elseif (strtoupper(substr($criteria[$criterion], 0, 7)) == 'BETWEEN') {
138 2
                $start = $end = null;
139 2
                sscanf(strtoupper($criteria[$criterion]), "BETWEEN %s AND %s", $start, $end);
140 2
                if (! $start || ! $end) {
141 1
                    throw new OrmException("Invalid range for between");
142
                }
143 1
                $wheres[] = sprintf("%s BETWEEN %s AND %s", self::escapeCriterion($criterion, $escapeSign), $start, $end);
144 1
                unset($criteria[$criterion]);
145
            } else {
146 25
                $wheres[] = sprintf("%s = :%s", self::escapeCriterion($criterion, $escapeSign), $placeHolder);
147
            }
148
        }
149
        
150 27
        return self::whereConditionsAsString($wheres);
151
    }
152
153
    /**
154
     * Loops over all where conditions and create a string of it
155
     *
156
     * @param array $wheres            
157
     *
158
     * @return string The where conditions as string
159
     */
160 27
    private static function whereConditionsAsString(array $wheres): string
161
    {
162 27
        if (count($wheres)) {
163 25
            $conditions = "";
164 25
            foreach ($wheres as $where) {
165 25
                $and = "";
166 25
                if ($conditions) {
167 2
                    $and = substr($where, 0, 3) == 'OR ' ? " " : " AND ";
168
                }
169 25
                $conditions .= $and . $where;
170
            }
171 25
            $wheres = sprintf("WHERE %s", $conditions);
172
        } else {
173 2
            $wheres = '';
174
        }
175
        
176 27
        return $wheres;
177
    }
178
179
    /**
180
     * Prepare the limit and offset modifier
181
     *
182
     * @param int $limit            
183
     * @param int $startFrom            
184
     *
185
     * @return string The limit modifier or empty string
186
     */
187 27
    private static function parseLimits(int $limit = 0, int $startFrom = 0): string
188
    {
189 27
        $limits = "";
190 27
        if ($startFrom > 0) {
191 1
            $limits = sprintf("%d,", $startFrom);
192
        }
193 27
        if ($limit > 0) {
194 3
            $limits .= $limit;
195
        }
196
        
197 27
        if ($limits) {
198 3
            $limits = sprintf("LIMIT %s", $limits);
199
        }
200
        
201 27
        return $limits;
202
    }
203
204
    /**
205
     * Retrieve the persistence parameters via reflection
206
     *
207
     * @param array $pairs
208
     *            The pairs of column names => values
209
     * @param string $primaryKeyCol
210
     *            The name of primary key column
211
     * @param bool $insert
212
     *            Whether to insert or update the data
213
     * @param string $escapeSign
214
     *            The escape sign
215
     *            
216
     * @return string The prepared statement parameters for persistence
217
     *        
218
     * @throws OrmException
219
     */
220 12
    private static function persistenceQueryParams(array $pairs, string $primaryKeyCol, bool $insert, string $escapeSign): string
221
    {
222 12
        $query = "";
223
        
224 12
        $columns = array_keys($pairs);
225
        
226 12
        if ($insert) {
227 11
            $cols = "";
228 11
            $vals = "";
229 11
            foreach ($columns as $column) {
230 11
                $cols .= ($cols ? ',' : '');
231 11
                $cols .= sprintf("%s%s%s", $escapeSign, $column, $escapeSign);
232 11
                $vals .= ($vals ? ',' : '');
233 11
                $vals .= sprintf(':%s', $column);
234
            }
235 11
            $query = sprintf("(%s) VALUES (%s)", $cols, $vals);
236
        } else {
237 6
            foreach ($columns as $column) {
238 6
                if ($column == $primaryKeyCol) {
239 6
                    continue;
240
                }
241 6
                $query .= ($query ? ", " : "SET ");
242 6
                $query .= sprintf("%s%s%s = :%s", $escapeSign, $column, $escapeSign, $column);
243
            }
244
        }
245
        
246 12
        return $query;
247
    }
248
249
    /**
250
     * When criterion is a property but annotated column name differs, we take the column name
251
     *
252
     * @param string $className
253
     *            The entity class name
254
     * @param string $criterion
255
     *            The criterion
256
     *            
257
     * @return string The criterion
258
     */
259 26
    private static function getAnnotatedCriterion(string $className, string $criterion): string
260
    {
261 26
        $class = new \ReflectionClass($className);
262 26
        $simpleCriterion = self::getSimpleCriterionName($criterion);
263 26
        if (! $class->hasProperty($simpleCriterion)) {
264 12
            return $criterion;
265
        }
266
        
267 19
        $property = $class->getProperty($simpleCriterion);
268
        
269 19
        if (strcmp($criterion, $simpleCriterion) != 0) {
270 5
            return $criterion;
271
        }
272
        
273 17
        $column = self::getAnnotatedColumn($property->getDocComment());
274
        
275 17
        if ("" !== $column) {
276 3
            $criterion = str_replace($simpleCriterion, $column, $criterion);
277
        }
278
        
279 17
        return $criterion;
280
    }
281
282
    /**
283
     * Get the annotated join query
284
     *
285
     * @param string $class
286
     *            The name of class to use as left class
287
     * @param string $table
288
     *            The name of table
289
     * @param array $criteria
290
     *            (by reference) List of criterias
291
     * @param array $columns
292
     *            (by reference) List of columns
293
     * @param string $escapeSign
294
     *            The character which escapes special literals
295
     *            
296
     * @return string The join query sql statment
297
     *        
298
     * @throws OrmException
299
     */
300 28
    private static function getAnnotatedQuery(string $class, string $table, array &$criteria, array &$columns, string $escapeSign): string
301
    {
302 28
        $joinQuery = "";
303
        
304 28
        $rf = new \ReflectionClass($class);
305
        
306 28
        $replacedCriteria = array();
307
        
308
        // Example criterion: user.name => 'john'
309 28
        foreach (array_keys($criteria) as $criterion) {
310 26
            $replacedCriteria[self::getAnnotatedCriterion($class, $criterion)] = $criteria[$criterion];
311
            // from example criterionProperty will be 'name', criterion will now be 'user'
312 26
            if (strpos($criterion, '.') !== false) {
313 3
                list ($criterion) = explode('.', $criterion);
314
            }
315
            
316
            // class must have a property named by criterion
317 26
            $rfProperty = $rf->hasProperty($criterion) ? $rf->getProperty($criterion) : null;
318 26
            if ($rfProperty == null) {
319 14
                continue;
320
            }
321
            
322
            // check annotations
323 19
            $propertyClass = "";
324
            // search the type of property value
325 19
            if ("" !== ($type = self::getAnnotatedType($rfProperty->getDocComment(), $rf->getNamespaceName()))) {
326 14
                if (! self::isPrimitive($type) && class_exists($type)) {
327 3
                    $propertyClass = $type;
328
                }
329
            }
330 19
            $inverseTable = $propertyClass ? self::getAnnotatedTableName($propertyClass, $criterion) : $criterion;
331
            
332
            // search the table mapping conditions
333 19
            if ("" !== ($parameters = self::getAnnotatedMappedByParameters($rfProperty->getDocComment())) && !empty($propertyClass)) {
334 2
                $mappedBy = self::parseMappedBy($parameters);
335
                
336 2
                $pkCol = self::getAnnotatedPrimaryKeyColumn($class);
337 2
                $inversePkCol = self::getAnnotatedPrimaryKeyColumn($propertyClass);
338
                
339 2
                $joinQuery = sprintf("JOIN %s%s%s ON %s%s%s.%s%s%s = %s%s%s.%s%s%s ", $escapeSign, $mappedBy['table'], $escapeSign, $escapeSign, $mappedBy['table'], $escapeSign, $escapeSign, $mappedBy['inverseColumn'], $escapeSign, $escapeSign, $table, $escapeSign, $escapeSign, $pkCol, $escapeSign);
340 2
                $joinQuery .= sprintf("JOIN %s%s%s ON %s%s%s.%s%s%s = %s%s%s.%s%s%s", $escapeSign, $inverseTable, $escapeSign, $escapeSign, $inverseTable, $escapeSign, $escapeSign, $inversePkCol, $escapeSign, $escapeSign, $mappedBy['table'], $escapeSign, $escapeSign, $mappedBy['column'], $escapeSign);
341
                
342 2
                $columns[] = sprintf("%s%s%s.%s%s%s AS '%s.%s'", $escapeSign, $inverseTable, $escapeSign, $escapeSign, $inversePkCol, $escapeSign, $inverseTable, $inversePkCol);
343 17
            } elseif ($propertyClass != "") {
344 1
                $inversePkCol = self::getAnnotatedPrimaryKeyColumn($propertyClass);
345 1
                $column = self::getAnnotatedColumnFromProperty($class, $rfProperty->getName());
346 1
                $joinQuery = sprintf("JOIN %s%s%s AS %s%s%s ON %s%s%s.%s%s%s = %s%s%s.%s%s%s", $escapeSign, $inverseTable, $escapeSign, $escapeSign, $criterion, $escapeSign, $escapeSign, $criterion, $escapeSign, $escapeSign, $inversePkCol, $escapeSign, $escapeSign, $table, $escapeSign, $escapeSign, $column, $escapeSign);
347 19
                $columns[] = sprintf("%s%s%s.%s%s%s AS '%s.%s'", $escapeSign, $criterion, $escapeSign, $escapeSign, $inversePkCol, $escapeSign, $criterion, $inversePkCol);
348
            }
349
        }
350 28
        $criteria = $replacedCriteria;
351
        
352 28
        return $joinQuery;
353
    }
354
355
    /**
356
     * Parse a complex crition into simple criterion
357
     *
358
     * @param string $criterion
359
     *            The full criterion pattern
360
     *            
361
     * @return string The simple criterion name
362
     */
363 26
    private static function getSimpleCriterionName(string $criterion): string
364
    {
365 26
        $criterion = str_ireplace('OR ', '', $criterion);
366 26
        if (strpos($criterion, '.')) {
367 3
            list ($criterion) = explode('.', $criterion);
368
        }
369 26
        return $criterion;
370
    }
371
}
372