Completed
Push — master ( c4d609...60216c )
by Iqbal
03:34
created

PdoFinder::computeQuery()   C

Complexity

Conditions 9
Paths 64

Size

Total Lines 53
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 2
Metric Value
c 4
b 0
f 2
dl 0
loc 53
rs 6.8963
cc 9
eloc 35
nc 64
nop 8

How to fix   Long Method    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
/*
3
 * This file is part of the Borobudur-Cqrs package.
4
 *
5
 * (c) Hexacodelabs <http://hexacodelabs.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Borobudur\Cqrs\ReadModel\Storage\Pdo;
12
13
use Borobudur\Cqrs\Collection;
14
use Borobudur\Cqrs\Exception\InvalidArgumentException;
15
use Borobudur\Cqrs\ReadModel\ReadModelInterface;
16
use Borobudur\Cqrs\ReadModel\Storage\Finder\Expression\CompositeExpressionInterface;
17
use Borobudur\Cqrs\ReadModel\Storage\Finder\FinderInterface;
18
use Borobudur\Cqrs\ReadModel\Storage\Pdo\Expression\PdoCompositeExpression;
19
use Borobudur\Cqrs\ReadModel\Storage\Pdo\Expression\PdoExpression;
20
use Borobudur\Cqrs\ReadModel\Storage\Pdo\Parser\ParserInterface;
21
use PDO as PhpPdo;
22
23
/**
24
 * @author      Iqbal Maulana <[email protected]>
25
 * @created     8/18/15
26
 */
27
class PdoFinder implements FinderInterface
28
{
29
    /**
30
     * @var Pdo
31
     */
32
    private $conn;
33
34
    /**
35
     * @var string
36
     */
37
    private $table;
38
39
    /**
40
     * @var string
41
     */
42
    private $class;
43
44
    /**
45
     * @var array
46
     */
47
    private $sorts = array();
48
49
    /**
50
     * @var int
51
     */
52
    private $limit;
53
54
    /**
55
     * @var int
56
     */
57
    private $offset = 0;
58
59
    /**
60
     * @var CompositeExpressionInterface[]
61
     */
62
    private $conditions = array();
63
64
    /**
65
     * @var array
66
     */
67
    private $joins = array();
68
69
    /**
70
     * @var ParserInterface
71
     */
72
    private $parser;
73
74
    /**
75
     * @var string
76
     */
77
    private $quote;
78
79
    /**
80
     * @var string
81
     */
82
    private $alias;
83
84
    /**
85
     * @var array
86
     */
87
    private $relations = array();
88
89
    /**
90
     * Constructor.
91
     *
92
     * @param Pdo             $conn
93
     * @param string          $table
94
     * @param string          $class
95
     * @param ParserInterface $parser
96
     * @param string          $quote
97
     * @param string|null     $alias
98
     */
99
    public function __construct(Pdo &$conn, $table, $class, $parser, $quote, $alias = null)
100
    {
101
        if (null === $alias) {
102
            $alias = lcfirst($table);
103
        }
104
105
        $this->conn = $conn;
106
        $this->table = $table;
107
        $this->class = $class;
108
        $this->parser = $parser;
109
        $this->parser->setQuote($quote);
110
        $this->quote = $quote;
111
        $this->alias = $alias;
112
        $this->relations = $class::{'relations'}();
113
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118
    public function sort(array $sorts)
119
    {
120
        $this->sorts = $sorts;
121
122
        return $this;
123
    }
124
125
    /**
126
     * {@inheritdoc}
127
     */
128
    public function limit($limit, $offset = 0)
129
    {
130
        $this->limit = $limit;
131
        $this->offset = $offset;
132
133
        return $this;
134
    }
135
136
    /**
137
     * {@inheritdoc}
138
     */
139
    public function expr()
140
    {
141
        return new PdoExpression($this->parser);
142
    }
143
144
    /**
145
     * {@inheritdoc}
146
     */
147
    public function where($expr)
148
    {
149
        if (!(1 === func_num_args() && $expr instanceof CompositeExpressionInterface)) {
150
            $expr = new PdoCompositeExpression(CompositeExpressionInterface::LOGICAL_AND, func_get_args());
151
        }
152
153
        $this->conditions[] = $expr;
154
155
        return $this;
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161
    public function innerJoin($property, $alias)
162
    {
163
        $this->join($property, $alias, 'INNER JOIN');
164
165
        return $this;
166
    }
167
168
    /**
169
     * {@inheritdoc}
170
     */
171
    public function leftJoin($property, $alias)
172
    {
173
        $this->join($property, $alias, 'LEFT JOIN');
174
175
        return $this;
176
    }
177
178
    /**
179
     * {@inheritdoc}
180
     */
181
    public function rightJoin($property, $alias)
182
    {
183
        $this->join($property, $alias, 'RIGHT JOIN');
184
185
        return $this;
186
    }
187
188
    /**
189
     * {@inheritdoc}
190
     */
191
    public function count()
192
    {
193
        $stmt = $this->conn->query(
194
            $this->computeQuery(
195
                'COUNT(*) AS num',
196
                $this->table,
197
                $this->alias,
198
                $this->conditions,
199
                $this->sorts,
200
                $this->joins,
201
                $this->limit,
202
                $this->offset
203
            ),
204
            PhpPdo::FETCH_ASSOC
205
        );
206
207
        if ($result = $stmt->fetch()) {
208
            return (int) $result['num'];
209
        }
210
211
        return 0;
212
    }
213
214
    /**
215
     * {@inheritdoc}
216
     */
217
    public function first()
218
    {
219
        $this->limit(1);
220
        $stmt = $this->conn->query($this->getSQL(), PhpPdo::FETCH_ASSOC);
221
        $related = $this->buildRelations($this->table, $this->conditions, $this->class);
222
223
        if ($record = $stmt->fetch()) {
224
            return $this->deserialize($this->bindRelation($this->class, $record, $related), $this->class);
225
        }
226
227
        return null;
228
    }
229
230
    /**
231
     * Fetch sets of data.
232
     *
233
     * @return Collection
234
     */
235
    public function get()
236
    {
237
        $stmt = $this->conn->query($this->getSQL(), PhpPdo::FETCH_ASSOC);
238
        $related = $this->buildRelations($this->table, $this->conditions, $this->class);
239
240
        return new Collection(
241
            array_map(
242
                function ($record) use ($related) {
243
                    return $this->bindRelation($this->class, $record, $related);
244
                },
245
                $stmt->fetchAll()
246
            ),
247
            $this->class
248
        );
249
    }
250
251
    /**
252
     * @return string
253
     */
254
    public function getSQL()
255
    {
256
        return $this->computeQuery(
257
            '*',
258
            $this->table,
259
            $this->alias,
260
            $this->conditions,
261
            $this->sorts,
262
            $this->joins,
263
            $this->limit,
264
            $this->offset
265
        );
266
    }
267
268
    /**
269
     * Cast finder to string representation.
270
     *
271
     * @return string
272
     */
273
    public function __toString()
274
    {
275
        return $this->getSQL();
276
    }
277
278
    /**
279
     * Deserialize record to read model.
280
     *
281
     * @param array  $record
282
     * @param string $class
283
     *
284
     * @return ReadModelInterface
285
     */
286
    protected function deserialize(array $record, $class)
287
    {
288
        return $class::{'deserialize'}($record);
289
    }
290
291
    /**
292
     * @param string $table
293
     * @param array  $conditions
294
     * @param string $class
295
     *
296
     * @return array
297
     */
298
    protected function buildRelations($table, array $conditions, $class)
299
    {
300
        $relations = $class::{'relations'}();
301
        $related = array();
302
303
        if (!empty($relations)) {
304
            foreach ($relations as $property => $relation) {
305
                $query = $this->computeQuery($relation['reference'], $table, null, $conditions);
306
                $relationQuery = $this->computeQuery(
307
                    '*',
308
                    $relation['table'],
309
                    null,
310
                    array(sprintf('%s in (%s)', $this->quote('id'), $query))
311
                );
312
313
                $results = $this->conn->query($relationQuery, PhpPdo::FETCH_ASSOC)->fetchAll();
314
                foreach ($results as $result) {
315
                    if (!isset($related[$property])) {
316
                        $related[$property] = array();
317
                    }
318
319
                    $related[$property][$result['id']] = $result;
320
                }
321
            }
322
        }
323
324
        return $related;
325
    }
326
327
    /**
328
     * @param string $class
329
     * @param array  $record
330
     * @param array  $related
331
     *
332
     * @return array
333
     */
334
    protected function bindRelation($class, array $record, array $related)
335
    {
336
        $relations = $class::{'relations'}();
337
        foreach ($relations as $property => $relation) {
338
            if (isset($related[$property]) && isset($related[$property][$record[$relation['reference']]])) {
339
                $record[$property] = $related[$property][$record[$relation['reference']]];
340
            } else {
341
                $record[$property] = null;
342
            }
343
        }
344
345
        return $record;
346
    }
347
348
    /**
349
     * @param string $property
350
     * @param string $alias
351
     * @param string $type
352
     */
353
    protected function join($property, $alias, $type)
354
    {
355
        if (empty($alias)) {
356
            throw new InvalidArgumentException('Missing parameter: $alias');
357
        }
358
359
        if (!isset($this->relations[$property])) {
360
            throw new InvalidArgumentException(
361
                sprintf(
362
                    'Read model "%s" does not have relation "%s"',
363
                    $this->class,
364
                    $property
365
                )
366
            );
367
        }
368
369
        $relation = $this->relations[$property];
370
        $this->joins[] = array(
371
            'table'     => $relation['table'],
372
            'alias'     => $alias,
373
            'reference' => $relation['reference'],
374
            'type'      => $type,
375
        );
376
    }
377
378
    /**
379
     * Compute query language.
380
     *
381
     * @param string $fields
382
     * @param string $table
383
     * @param string $alias
384
     * @param array  $conditions
385
     * @param array  $sorts
386
     * @param array  $joins
387
     * @param int    $limit
388
     * @param int    $offset
389
     *
390
     * @return string
391
     */
392
    protected function computeQuery(
393
        $fields = '*',
394
        $table,
395
        $alias = null,
396
        array $conditions = null,
397
        array $sorts = null,
398
        array $joins = null,
399
        $limit = null,
400
        $offset = null
401
    ) {
402
        if ($fields != '*' && false === strpos($fields, '(')) {
403
            $parts = array_map(function($field) { return $this->quote($field); }, explode(',', $fields));
404
            $fields = implode(',', $parts);
405
        }
406
407
        $parts = array(
408
            'SELECT ' . $fields,
409
            'FROM ' . $this->quote($table),
410
        );
411
412
        if (!empty($alias)) {
413
            $parts[] = 'AS ' . $this->quote($alias);
414
        }
415
416
        if (!empty($joins)) {
417
            foreach ($joins as $join) {
418
                $parts[] = sprintf(
419
                    '%s %s AS %s ON %s.%s = %s.%s',
420
                    $join['type'],
421
                    $this->quote($join['table']),
422
                    $this->quote($join['alias']),
423
                    $this->quote($join['alias']),
424
                    $this->quote('id'),
425
                    $this->quote($alias),
426
                    $this->quote($join['reference'])
427
                );
428
            }
429
        }
430
431
        if (!empty($conditions)) {
432
            $parts[] = 'WHERE ' . $this->normalizeConditions($conditions);
433
        }
434
435
        if (!empty($this->sorts)) {
436
            $parts[] = 'ORDER BY ' . $this->normalizeSorts($sorts);
0 ignored issues
show
Bug introduced by
It seems like $sorts defined by parameter $sorts on line 397 can also be of type null; however, Borobudur\Cqrs\ReadModel...inder::normalizeSorts() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
437
        }
438
439
        if (null !== $limit) {
440
            $parts[] = sprintf('LIMIT %d OFFSET %d', $limit, $offset);
441
        }
442
443
        return implode(' ', $parts);
444
    }
445
446
    /**
447
     * Normalize conditions.
448
     *
449
     * @param array $conditions
450
     *
451
     * @return string
452
     */
453
    protected function normalizeConditions(array $conditions)
454
    {
455
        $joiner = ' ' . CompositeExpressionInterface::LOGICAL_AND . ' ';
456
457
        return implode(
458
            $joiner,
459
            array_map(
460
                function ($item) {
461
                    return (string) $item;
462
                },
463
                $conditions
464
            )
465
        );
466
    }
467
468
    /**
469
     * Normalize sorts.
470
     *
471
     * @param array $sorts
472
     *
473
     * @return string
474
     */
475
    protected function normalizeSorts(array $sorts)
476
    {
477
        $normalized = array();
478
        foreach ($sorts as $field => $direction) {
479
            $normalized[] = $this->quote($field) . ' ' . $direction;
480
        }
481
482
        return implode(', ', $normalized);
483
    }
484
485
    /**
486
     * Quote field.
487
     *
488
     * @param string $field
489
     *
490
     * @return string
491
     */
492 View Code Duplication
    protected function quote($field)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
493
    {
494
        $parts = explode('.', $field);
495
        $quotes = array();
496
497
        foreach ($parts as $part) {
498
            $quotes[] = $this->quote . $part . $this->quote;
499
        }
500
501
        return implode('.', $quotes);
502
    }
503
}
504