Completed
Pull Request — master (#34)
by Rasmus
11:05
created

Result::firstCol()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

Changes 0
Metric Value
dl 0
loc 10
c 0
b 0
f 0
ccs 4
cts 5
cp 0.8
rs 9.4285
cc 2
eloc 5
nc 2
nop 0
crap 2.032
1
<?php
2
3
namespace mindplay\sql\framework;
4
5
use Iterator;
6
use IteratorAggregate;
7
use RuntimeException;
8
9
/**
10
 * This class represents the result of fetching a `PreparedStatement`, e.g. the results of
11
 * a `SELECT` SQL query, and with Mappers being applied on-the-fly, in batches.
12
 *
13
 * It implements `IteratorAggregate`, allowing you to execute the query and iterate
14
 * over the result set with a `foreach` statement.
15
 */
16
class Result implements IteratorAggregate
17
{
18
    /**
19
     * @var PreparedStatement
20
     */
21
    private $statement;
22
23
    /**
24
     * @var int
25
     */
26
    private $batch_size;
27
28
    /**
29
     * @var Mapper[] list of Mappers to apply when fetching results
30
     */
31
    private $mappers;
32
33
    /**
34
     * @var Indexer|null
35
     */
36
    private $indexer;
37
38
    /**
39
     * @param PreparedStatement $statement  prepared statement
40
     * @param int               $batch_size batch-size (when fetching large result sets)
41
     * @param Mapper[]          $mappers    list of Mappers to apply while fetching results
42
     * @param Indexer|null      $indexer    optional Indexer (used to customize Generator keys)
43
     */
44 1
    public function __construct(PreparedStatement $statement, $batch_size, array $mappers, Indexer $indexer = null)
45
    {
46 1
        $this->statement = $statement;
47 1
        $this->batch_size = $batch_size;
48 1
        $this->mappers = $mappers;
49 1
        $this->indexer = $indexer;
50 1
    }
51
52
    /**
53
     * @return mixed|null first record of the record-set (or NULL, if the record-set is empty)
54
     */
55 1
    public function firstRow()
56
    {
57 1
        foreach ($this->createIterator(1) as $record) {
58 1
            return $record; // break from loop immediately after fetching the first record
59
        }
60
61
        return null;
62
    }
63
64
    /**
65
     * @return mixed|null first column value of the first record of the record-set (or NULL, if the record-set is empty)
66
     */
67 1
    public function firstCol()
68
    {
69 1
        foreach ($this->createIterator(1) as $record) {
70 1
            $keys = array_keys($record);
71
72 1
            return $record[$keys[0]]; // break from loop immediately after fetching the first record
73
        }
74
75
        return null;
76
    }
77
78
    /**
79
     * @return array all the records of the record-set
80
     */
81 1
    public function all()
82
    {
83 1
        return iterator_to_array($this->getIterator());
84
    }
85
    
86
    /**
87
     * Execute this Statement and return a Generator, so you can iterate over the results.
88
     *
89
     * This method implements `IteratorAggregate`, permitting you to iterate directly over
90
     * the resulting records (or objects) without explicitly having to call this method.
91
     *
92
     * @return Iterator
93
     */
94 1
    public function getIterator()
95
    {
96 1
        return $this->createIterator($this->batch_size);
97
    }
98
99
    /**
100
     * Create an Iterator with a given batch-size.
101
     *
102
     * @param int $batch_size batch-size when processing the result set
103
     *
104
     * @return Iterator
105
     */
106 1
    private function createIterator($batch_size)
107
    {
108 1
        $fetching = true;
109
110
        do {
111
            // fetch a batch of records:
112
113 1
            $batch = [];
114
115
            do {
116 1
                $record = $this->statement->fetch();
117
118 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...
119 1
                    $batch[] = $record;
120
                } else {
121 1
                    if (count($batch) === 0) {
122 1
                        return; // last batch of records fetched
123
                    }
124
125 1
                    $fetching = false; // last record of batch fetched
126
                }
127 1
            } while ($fetching && (count($batch) < $batch_size));
128
129
            // apply Mappers to current batch of records:
130
131 1
            $num_records = count($batch);
132
133 1
            foreach ($this->mappers as $index => $mapper) {
134 1
                $batch = $mapper->map($batch);
135
136 1
                if (count($batch) !== $num_records) {
137
                    $count = count($batch);
138
139 1
                    throw new RuntimeException("Mapper #{$index} returned {$count} records, expected: {$num_records}");
140
                }
141
            }
142
143
            // return each record from the current batch:
144
145 1
            if ($this->indexer) {
146 1
                foreach ($batch as $record) {
147 1
                    $index = $this->indexer->index($record);
148
149 1
                    yield $index => $record;
150
                }
151
            } else {
152 1
                foreach ($batch as $record) {
153 1
                    yield $record;
154
                }
155
            }
156 1
        } while ($fetching);
157 1
    }
158
}
159