Completed
Push — master ( 72aa74...afd9e7 )
by Todd
02:20
created

TableQuery::fetchAll()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 5.0488

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 7
cts 8
cp 0.875
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 8
nc 3
nop 2
crap 5.0488
1
<?php
2
/**
3
 * @author Todd Burry <[email protected]>
4
 * @copyright 2009-2017 Vanilla Forums Inc.
5
 * @license MIT
6
 */
7
8
namespace Garden\Db;
9
10
use PDO;
11
use Traversable;
12
13
/**
14
 * Represents a dataset on a delayed query.
15
 *
16
 * This class is analogous to the {@link PDOStatement} except the query is only executed when the data is first accessed.
17
 */
18
class TableQuery implements \IteratorAggregate, DatasetInterface {
19
    use Utils\FetchModeTrait, Utils\DatasetTrait {
20
        fetchAll as protected fetchAllTrait;
21
        setFetchMode as protected;
22
    }
23
24
    /**
25
     * @var Db
26
     */
27
    private $db;
28
29
    /**
30
     * @var array|null
31
     */
32
    private $data;
33
34
    /**
35
     * @var string
36
     */
37
    private $table;
38
39
    /**
40
     * @var array
41
     */
42
    private $where;
43
44
    /**
45
     * @var array
46
     */
47
    private $options;
48
49
    /**
50
     * @var callable A callback used to unserialize rows from the database.
51
     */
52
    private $rowCallback;
53
54
    /**
55
     * Construct a new {@link TableQuery} object.
56
     *
57
     * Note that this class is not meant to be modified after being constructed so it's important to pass everything you
58
     * need early.
59
     *
60
     * @param string $table The name of the table to query.
61
     * @param array $where The filter information for the query.
62
     * @param Db $db The database to fetch the data from. This can be changed later.
63
     * @param array $options Additional options for the object:
64
     *
65
     * fetchMode
66
     * : The PDO fetch mode. This can be one of the **PDO::FETCH_** constants, a class name or an array of arguments for
67
     * {@link PDOStatement::fetchAll()}
68
     * rowCallback
69
     * : A callable that will be applied to each row of the result set.
70
     */
71 17
    public function __construct($table, array $where, Db $db, array $options = []) {
72 17
        $this->table = $table;
73 17
        $this->where = $where;
74 17
        $this->db = $db;
75
76
        $options += [
77 17
            Db::OPTION_FETCH_MODE => $db->getFetchArgs(),
78
            'rowCallback' => null
79
        ];
80
81 17
        $fetchMode = (array)$options[Db::OPTION_FETCH_MODE];
82 17
        if (in_array($fetchMode[0], [PDO::FETCH_OBJ | PDO::FETCH_NUM | PDO::FETCH_FUNC])) {
83
            throw new \InvalidArgumentException("Fetch mode not supported.", 500);
84 17
        } elseif ($fetchMode[0] == PDO::FETCH_CLASS && !is_a($fetchMode[1], \ArrayAccess::class, true)) {
85
            throw new \InvalidArgumentException("The {$fetchMode[1]} class must implement ArrayAccess.", 500);
86
        }
87
88 17
        $this->setFetchMode(...$fetchMode);
89 17
        $this->rowCallback = $options['rowCallback'];
90 17
    }
91
92
    /**
93
     * {@inheritdoc}
94
     */
95 1
    public function getOrder() {
96 1
        return $this->getOption('order', []);
97
    }
98
99
    /**
100
     * {@inheritdoc}
101
     */
102 16
    public function setOrder(...$columns) {
103 16
        $this->setOption('order', $columns, true);
104 16
        return $this;
105
    }
106
107
    /**
108
     * {@inheritdoc}
109
     */
110 1
    public function getOffset() {
111 1
        return $this->getOption('offset', 0);
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117 2
    public function setOffset($offset) {
118 2
        if (!is_numeric($offset) || $offset < 0) {
119
            throw new \InvalidArgumentException("Invalid offset '$offset.'", 500);
120
        }
121
122 2
        $this->setOption('offset', (int)$offset, true);
123 2
        return $this;
124
    }
125
126
    /**
127
     * Get the limit.
128
     *
129
     * @return int Returns the limit.
130
     */
131 3
    public function getLimit() {
132 3
        return $this->getOption('limit', Model::DEFAULT_LIMIT);
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     */
138 17
    public function setLimit($limit) {
139 17
        if (!is_numeric($limit) || $limit < 0) {
140
            throw new \InvalidArgumentException("Invalid limit '$limit.'", 500);
141
        }
142
143
144 17
        $reset = true;
145
146 17
        if (is_array($this->data) && $limit < $this->getLimit()) {
147 2
            $this->data = array_slice($this->data, 0, $limit);
148 2
            $reset = false;
149
        }
150
151 17
        $this->setOption('limit', (int)$limit, $reset);
152
153 17
        return $this;
154
    }
155
156 3
    protected function getOption($name, $default = null) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
157 3
        return isset($this->options[$name]) ? $this->options[$name] : $default;
158
    }
159
160
    /**
161
     * Set a query option.
162
     *
163
     * @param string $name The name of the option to set.
164
     * @param mixed $value The new value of the option.
165
     * @param bool $reset Pass **true** and the data will be queried again if the option value has changed.
166
     * @return $this
167
     */
168 17
    protected function setOption($name, $value, $reset = false) {
169 17
        $changed = !isset($this->options[$name]) || $this->options[$name] !== $value;
170
171 17
        if ($changed && $reset) {
172 17
            $this->data = null;
173
        }
174
175 17
        $this->options[$name] = $value;
176 17
        return $this;
177
    }
178
179
    /**
180
     * Get the data.
181
     *
182
     * @return array Returns the data.
183
     */
184 16
    protected function getData() {
185 16
        if ($this->data === null) {
186 16
            $stmt = $this->db->get(
187 16
                $this->table,
188 16
                $this->where,
189 16
                $this->options + [Db::OPTION_FETCH_MODE => $this->getFetchArgs()]
190
            );
191
192 16
            $data = $stmt->fetchAll(...(array)$this->getFetchArgs());
193
194 16
            if (is_callable($this->rowCallback)) {
195 15
                array_walk($data, function (&$row) {
196 15
                    $row = call_user_func($this->rowCallback, $row);
197 15
                });
198
            }
199
200 16
            $this->data = $data;
201
        }
202
203 16
        return $this->data;
204
    }
205
206 12
    public function fetchAll($mode = 0, ...$args) {
207 12
        $thisMode = $this->getFetchMode();
208 12
        if ($mode === 0 || $mode === $thisMode || $this->data === []) {
209 3
            return $this->getData();
210
        }
211
212 9
        $result = $this->fetchAllTrait($mode, ...$args);
213
214 9
        if ($result === null) {
215
            // Don't know what to do, fetch from the database again.
216
            $result = $this->db->get($this->table, $this->where, $this->options)->fetchAll($mode, ...$args);
217
        }
218 9
        return $result;
219
    }
220
}
221