Completed
Push — master ( 126036...3fa63d )
by Maik
07:56
created

OrmStatement::createQuery()   B

Complexity

Conditions 3
Paths 2

Size

Total Lines 34
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 3

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 34
ccs 18
cts 18
cp 1
rs 8.8571
cc 3
eloc 25
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 The class for which the query will be created
22
     * @param array  $criteria Array of criterias in form of "property" => "value"
23
     * @param array  $columns The columns to retrieve
24
     * @param string $orderBy An order-by statement in form of "property ASC|DESC"
25
     * @param number $limit The maximum amount of results
26
     * @param number $startFrom The offset where to get results of
27
     *
28
     * @return string The query as sql statement
29
     *
30
     * @throws OrmException
31
     */
32 28
    private static function createQuery(
33
        $class,
34
        $tableName,
35
        array &$criteria,
36
        array $columns,
37
        $orderBy = '',
38
        $limit = 0,
39
        $startFrom = 0,
40
        $escapeSign = ""
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 2
        }
51
52 27
        $query = sprintf(
53 27
            "SELECT %s FROM %s%s%s %s %s %s %s",
54 27
            implode(',', $columns),
55 27
            $escapeSign,
56 27
            $tableName,
57 27
            $escapeSign,
58 27
            $joins,
59 27
            $wheres,
60 27
            $orderBy,
61
            $limits
62 27
        );
63
64 27
        return $query;
65
    }
66
67
    /**
68
     * Create a insert or update statement
69
     *
70
     * @param string    $class              The class of entity
71
     * @param array     $pairs              The pairs of columns and its corresponding values
72
     * @param string    $primaryKeyCol      The name of column which represents the primary key
73
     * @param mixed     $primaryKeyValue    The primary key value
74
     *
75
     * @return string
76
     */
77 12
    private static function createUpdateStatement($class, $pairs, $primaryKeyCol, $primaryKeyValue, $escapeSign)
78
    {
79 12
        $tableName = self::getTableName($class);
80
81 12
        $query = sprintf("INSERT INTO %s%s%s ", $escapeSign, $tableName, $escapeSign);
82 12
        if ($primaryKeyValue) {
83 6
            $query = sprintf("UPDATE %s%s%s ", $escapeSign, $tableName, $escapeSign);
84 6
        }
85
86 12
        $query .= self::persistenceQueryParams($pairs, $primaryKeyCol, is_null($primaryKeyValue), $escapeSign);
87
88 12
        if ($primaryKeyValue) {
89 6
            $query .= sprintf(" WHERE %s%s%s = :%s", $escapeSign, $primaryKeyCol, $escapeSign, $primaryKeyCol);
90 6
        }
91
92 12
        return $query;
93
    }
94
95
    /**
96
     * Escape all parts of a criterion
97
     *
98
     * @param string $criterion The criterion pattern
99
     * @param string $escapeSign The escape sign
100
     */
101 25
    private static function escapeCriterion($criterion, $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 25
        }
110
111 25
        return $criterionEscaped;
112
    }
113
114
    /**
115
     * Parse criteria into where conditions
116
     *
117
     * @param array $criteria The criteria to parse
118
     * @return string The where conditions
119
     *
120
     * @throws OrmException
121
     */
122 28
    private static function parseCriteria(array &$criteria, $escapeSign)
123
    {
124 28
        $wheres = array();
125
126 28
        $criterias = array_keys($criteria);
127
128 28
        foreach ($criterias as $criterion) {
129 26
            $placeHolder = str_replace('.', '_', $criterion);
130 26
            $placeHolder = str_replace('OR ', 'OR_', $placeHolder);
131 26
            if (strtoupper(substr($criteria[$criterion], 0, 4)) == 'LIKE') {
132 2
                $wheres[] = sprintf("%s LIKE :%s", self::escapeCriterion($criterion, $escapeSign), $placeHolder);
133 26
            } elseif (strtoupper(substr($criteria[$criterion], 0, 7)) == 'BETWEEN') {
134 2
                $start = $end = null;
135 2
                sscanf(strtoupper($criteria[$criterion]), "BETWEEN %s AND %s", $start, $end);
136 2
                if (!$start || !$end) {
137 1
                    throw new OrmException("Invalid range for between");
138
                }
139 1
                $wheres[] = sprintf(
140 1
                    "%s BETWEEN %s AND %s",
141 1
                    self::escapeCriterion($criterion, $escapeSign),
142 1
                    $start,
143
                    $end
144 1
                );
145 1
                unset($criteria[$criterion]);
146 1
            } else {
147 22
                $wheres[] = sprintf("%s = :%s", self::escapeCriterion($criterion, $escapeSign), $placeHolder);
148
            }
149 27
        }
150
151 27
        return self::whereConditionsAsString($wheres);
152
    }
153
154
    /**
155
     * Loops over all where conditions and create a string of it
156
     *
157
     * @param array $wheres
158
     * @return string The where conditions as string
159
     */
160 27
    private static function whereConditionsAsString(array $wheres)
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 2
                }
169 25
                $conditions .= $and . $where;
170 25
            }
171 25
            $wheres = sprintf("WHERE %s", $conditions);
172 25
        } 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($limit = 0, $startFrom = 0)
188
    {
189 27
        $limits = "";
190 27
        if ($startFrom > 0) {
191 1
            $limits = sprintf("%d,", $startFrom);
192 1
        }
193 27
        if ($limit > 0) {
194 3
            $limits .= $limit;
195 3
        }
196
197 27
        if ($limits) {
198 3
            $limits = sprintf("LIMIT %s", $limits);
199 3
        }
200
201 27
        return $limits;
202
    }
203
204
    /**
205
     * Retrieve the persistence parameters via reflection
206
     *
207
     * @param array $pairs The pairs of column names => values
208
     *
209
     * @return string The prepared statement parameters for persistence
210
     *
211
     * @throws OrmException
212
     */
213 12
    private static function persistenceQueryParams($pairs, $primaryKeyCol, $insert = true, $escapeSign = "")
214
    {
215 12
        $query = "";
216
217 12
        $columns = array_keys($pairs);
218
219 12
        if ($insert) {
220 11
            $cols = "";
221 11
            $vals = "";
222 11
            foreach ($columns as $column) {
223 11
                $cols .= ($cols ? ',' : '');
224 11
                $cols .= sprintf("%s%s%s", $escapeSign, $column, $escapeSign);
225 11
                $vals .= ($vals ? ',' : '');
226 11
                $vals .= sprintf(':%s', $column);
227 11
            }
228 11
            $query = sprintf("(%s) VALUES (%s)", $cols, $vals);
229 11
        } else {
230 6
            foreach ($columns as $column) {
231 6
                if ($column == $primaryKeyCol) {
232 6
                    continue;
233
                }
234 6
                $query .= ($query ? ", " : "SET ");
235 6
                $query .= sprintf("%s%s%s = :%s", $escapeSign, $column, $escapeSign, $column);
236 6
            }
237
        }
238
239 12
        return $query;
240
    }
241
242
    /**
243
     * When criterion is a property but annotated column name differs, we take the column name
244
     *
245
     * @param string $className The entity class name
246
     * @param string $criterion The criterion
247
     */
248 26
    private static function getAnnotatedCriterion($className, $criterion)
249
    {
250 26
        $class = new \ReflectionClass($className);
251 26
        $simpleCriterion = self::getSimpleCriterionName($criterion);
252 26
        if (!$class->hasProperty($simpleCriterion)) {
253 12
            return $criterion;
254
        }
255
256 19
        $property = $class->getProperty($simpleCriterion);
257
258 19
        if (strcmp($criterion, $simpleCriterion) != 0) {
259 5
            return $criterion;
260
        }
261
262 17
        $column = self::getAnnotatedColumn($property->getDocComment());
263
264 17
        if (null !== $column) {
265 3
            $criterion = str_replace($simpleCriterion, $column, $criterion);
266 3
        }
267
268 17
        return $criterion;
269
    }
270
271
    /**
272
     * Get the annotated join query
273
     *
274
     * @param string $class The name of class to use as left class
275
     * @param string $table The name of table
276
     * @param array $criteria
277
     *            (by reference) List of criterias
278
     * @param array $columns
279
     *            (by reference) List of columns
280
     * @param string $escapeSign The character which escapes special literals
281
     *
282
     * @return string The join query sql statment
283
     *
284
     * @throws OrmException
285
     */
286 28
    private static function getAnnotatedQuery($class, $table, &$criteria, &$columns, $escapeSign)
287
    {
288 28
        $joinQuery = "";
289
290 28
        $rf = new \ReflectionClass($class);
291
292 28
        $replacedCriteria = array();
293
294
        // Example criterion: user.name => 'john'
295 28
        foreach (array_keys($criteria) as $criterion) {
296 26
            $replacedCriteria[self::getAnnotatedCriterion($class, $criterion)] = $criteria[$criterion];
297
            // from example criterionProperty will be 'name', criterion will now be 'user'
298 26
            if (strpos($criterion, '.') !== false) {
299 3
                list ($criterion) = explode('.', $criterion);
300 3
            }
301
302
            // class must have a property named by criterion
303 26
            $rfProperty = $rf->hasProperty($criterion) ? $rf->getProperty($criterion) : null;
304 26
            if ($rfProperty == null) {
305 14
                continue;
306
            }
307
308
            // check annotations
309 19
            $propertyClass = "";
310
            // search the type of property value
311 19
            if (null !== ($type = self::getAnnotatedType($rfProperty->getDocComment(), $rf->getNamespaceName()))) {
312 14
                if (!self::isPrimitive($type) && class_exists($type)) {
313 3
                    $propertyClass = $type;
314 3
                }
315 14
            }
316 19
            $inverseTable = $propertyClass ? self::getAnnotatedTableName($propertyClass, $criterion) : $criterion;
317
318
            // search the table mapping conditions
319 19
            if (null !== ($parameters = self::getAnnotatedMappedByParameters($rfProperty->getDocComment()))) {
320 2
                $mappedBy = self::parseMappedBy($parameters);
321
322 2
                $pkCol = self::getAnnotatedPrimaryKeyColumn($class);
323 2
                $inversePkCol = self::getAnnotatedPrimaryKeyColumn($propertyClass);
324
325 2
                $joinQuery = sprintf(
326 2
                    "JOIN %s%s%s ON %s%s%s.%s%s%s = %s%s%s.%s%s%s ",
327 2
                    $escapeSign,
328 2
                    $mappedBy['table'],
329 2
                    $escapeSign,
330 2
                    $escapeSign,
331 2
                    $mappedBy['table'],
332 2
                    $escapeSign,
333 2
                    $escapeSign,
334 2
                    $mappedBy['inverseColumn'],
335 2
                    $escapeSign,
336 2
                    $escapeSign,
337 2
                    $table,
338 2
                    $escapeSign,
339 2
                    $escapeSign,
340 2
                    $pkCol,
341
                    $escapeSign
342 2
                );
343 2
                $joinQuery .= sprintf(
344 2
                    "JOIN %s%s%s ON %s%s%s.%s%s%s = %s%s%s.%s%s%s",
345 2
                    $escapeSign,
346 2
                    $inverseTable,
347 2
                    $escapeSign,
348 2
                    $escapeSign,
349 2
                    $inverseTable,
350 2
                    $escapeSign,
351 2
                    $escapeSign,
352 2
                    $inversePkCol,
353 2
                    $escapeSign,
354 2
                    $escapeSign,
355 2
                    $mappedBy['table'],
356 2
                    $escapeSign,
357 2
                    $escapeSign,
358 2
                    $mappedBy['column'],
359
                    $escapeSign
360 2
                );
361
362 2
                $columns[] = sprintf(
363 2
                    "%s%s%s.%s%s%s AS '%s.%s'",
364 2
                    $escapeSign,
365 2
                    $inverseTable,
366 2
                    $escapeSign,
367 2
                    $escapeSign,
368 2
                    $inversePkCol,
369 2
                    $escapeSign,
370 2
                    $inverseTable,
371
                    $inversePkCol
372 2
                );
373 19
            } elseif ($propertyClass != "") {
374 1
                $inversePkCol = self::getAnnotatedPrimaryKeyColumn($propertyClass);
375 1
                $column = self::getAnnotatedColumnFromProperty($class, $rfProperty->getName());
376 1
                $joinQuery = sprintf(
377 1
                    "JOIN %s%s%s AS %s%s%s ON %s%s%s.%s%s%s = %s%s%s.%s%s%s",
378 1
                    $escapeSign,
379 1
                    $inverseTable,
380 1
                    $escapeSign,
381 1
                    $escapeSign,
382 1
                    $criterion,
383 1
                    $escapeSign,
384 1
                    $escapeSign,
385 1
                    $criterion,
386 1
                    $escapeSign,
387 1
                    $escapeSign,
388 1
                    $inversePkCol,
389 1
                    $escapeSign,
390 1
                    $escapeSign,
391 1
                    $table,
392 1
                    $escapeSign,
393 1
                    $escapeSign,
394 1
                    $column,
395
                    $escapeSign
396 1
                );
397 1
                $columns[] = sprintf(
398 1
                    "%s%s%s.%s%s%s AS '%s.%s'",
399 1
                    $escapeSign,
400 1
                    $criterion,
401 1
                    $escapeSign,
402 1
                    $escapeSign,
403 1
                    $inversePkCol,
404 1
                    $escapeSign,
405 1
                    $criterion,
406
                    $inversePkCol
407 1
                );
408 1
            }
409 28
        }
410 28
        $criteria = $replacedCriteria;
411
412 28
        return $joinQuery;
413
    }
414
415
    /**
416
     * Parse a complex crition into simple criterion
417
     *
418
     * @param string $criterion The full criterion pattern
419
     *
420
     * @return string The simple criterion name
421
     */
422 26
    private static function getSimpleCriterionName($criterion)
423
    {
424 26
        $criterion = str_ireplace('OR ', '', $criterion);
425 26
        if (strpos($criterion, '.')) {
426 3
            list($criterion) = explode('.', $criterion);
427 3
        }
428 26
        return $criterion;
429
    }
430
}
431