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

OrmStatement   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 420
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 100%

Importance

Changes 4
Bugs 1 Features 0
Metric Value
wmc 49
c 4
b 1
f 0
lcom 1
cbo 2
dl 0
loc 420
ccs 230
cts 230
cp 1
rs 8.5455

10 Methods

Rating   Name   Duplication   Size   Complexity  
B createQuery() 0 34 3
A createUpdateStatement() 0 17 3
A escapeCriterion() 0 12 3
B parseCriteria() 0 31 6
B whereConditionsAsString() 0 18 5
A parseLimits() 0 16 4
C persistenceQueryParams() 0 28 8
B getAnnotatedCriterion() 0 22 4
C getAnnotatedQuery() 0 128 11
A getSimpleCriterionName() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like OrmStatement often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use OrmStatement, and based on these observations, apply Extract Interface, too.

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