Completed
Push — master ( 3fa18f...cc5dde )
by Rasmus
01:48
created

Result   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 132
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 92.5%

Importance

Changes 0
Metric Value
wmc 17
lcom 1
cbo 2
dl 0
loc 132
ccs 37
cts 40
cp 0.925
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A firstRow() 0 8 2
A firstCol() 0 10 2
A all() 0 4 1
A getIterator() 0 4 1
B createIterator() 0 48 10
1
<?php
2
3
namespace mindplay\sql\framework;
4
5
use Iterator;
6
use IteratorAggregate;
7
use RuntimeException;
8
use Traversable;
9
10
/**
11
 * This class represents the result of fetching a `PreparedStatement`, e.g. the results of
12
 * a `SELECT` SQL query, and with Mappers being applied on-the-fly, in batches.
13
 *
14
 * It implements `IteratorAggregate`, allowing you to execute the query and iterate
15
 * over the result set with a `foreach` statement.
16
 */
17
class Result implements IteratorAggregate
18
{
19
    /**
20
     * @var PreparedStatement
21
     */
22
    private $statement;
23
24
    /**
25
     * @var int
26
     */
27
    private $batch_size;
28
29
    /**
30
     * @var Mapper[] list of Mappers to apply when fetching results
31
     */
32
    private $mappers;
33
34
    /**
35
     * @param PreparedStatement $statement  prepared statement
36
     * @param int               $batch_size batch-size (when fetching large result sets)
37
     * @param Mapper[]          $mappers    list of Mappers to apply while fetching results
38
     */
39 1
    public function __construct(PreparedStatement $statement, $batch_size, array $mappers)
40
    {
41 1
        $this->statement = $statement;
42 1
        $this->batch_size = $batch_size;
43 1
        $this->mappers = $mappers;
44 1
    }
45
46
    /**
47
     * @return mixed|null first record of the record-set (or NULL, if the record-set is empty)
48
     */
49 1
    public function firstRow()
50
    {
51 1
        foreach ($this->createIterator(1) as $record) {
52 1
            return $record; // break from loop immediately after fetching the first record
53
        }
54
55
        return null;
56
    }
57
58
    /**
59
     * @return mixed|null first column value of the first record of the record-set (or NULL, if the record-set is empty)
60
     */
61 1
    public function firstCol()
62
    {
63 1
        foreach ($this->createIterator(1) as $record) {
64 1
            $keys = array_keys($record);
65
66 1
            return $record[$keys[0]]; // break from loop immediately after fetching the first record
67
        }
68
69
        return null;
70
    }
71
72
    /**
73
     * @return array all the records of the record-set
74
     */
75 1
    public function all()
76
    {
77 1
        return iterator_to_array($this->getIterator());
78
    }
79
    
80
    /**
81
     * Execute this Statement and return a Generator, so you can iterate over the results.
82
     *
83
     * This method implements `IteratorAggregate`, permitting you to iterate directly over
84
     * the resulting records (or objects) without explicitly having to call this method.
85
     *
86
     * @return Iterator
87
     */
88 1
    public function getIterator()
89
    {
90 1
        return $this->createIterator($this->batch_size);
91
    }
92
93
    /**
94
     * Create an Iterator with a given batch-size.
95
     *
96
     * @param int $batch_size batch-size when processing the result set
97
     *
98
     * @return Iterator
99
     */
100 1
    private function createIterator($batch_size)
101
    {
102 1
        $fetching = true;
103
104
        do {
105
            // fetch a batch of records:
106
107 1
            $batch = [];
108
109
            do {
110 1
                $record = $this->statement->fetch();
111
112 1
                if ($record) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $record of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
113 1
                    $batch[] = $record;
114
                } else {
115 1
                    if (count($batch) === 0) {
116 1
                        return; // last batch of records fetched
117
                    }
118
119 1
                    $fetching = false; // last record of batch fetched
120
                }
121 1
            } while ($fetching && (count($batch) < $batch_size));
122
123
            // apply Mappers to current batch of records:
124
125 1
            $num_records = count($batch);
126
127 1
            foreach ($this->mappers as $index => $mapper) {
128 1
                $batch = $mapper->map($batch);
129
130 1
                if ($batch instanceof Traversable) {
131 1
                    $batch = iterator_to_array($batch);
132
                }
133
134 1
                if (count($batch) !== $num_records) {
135
                    $count = count($batch);
136
137 1
                    throw new RuntimeException("Mapper #{$index} returned {$count} records, expected: {$num_records}");
138
                }
139
            }
140
141
            // return each record from the current batch:
142
143 1
            foreach ($batch as $record) {
144 1
                yield $record;
145
            }
146 1
        } while ($fetching);
147 1
    }
148
}
149