Completed
Push — 5.1 ( 8db6c9...fe8887 )
by ARP
18s queued 13s
created

InnerResultIterator::next()   D

Complexity

Conditions 15
Paths 229

Size

Total Lines 82
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 42
c 1
b 1
f 0
dl 0
loc 82
rs 4.7208
cc 15
nc 229
nop 0

How to fix   Long Method    Complexity   

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:

1
<?php
2
declare(strict_types=1);
3
4
namespace TheCodingMachine\TDBM;
5
6
use Doctrine\DBAL\Platforms\MySqlPlatform;
7
use Doctrine\DBAL\Statement;
8
use Mouf\Database\MagicQuery;
9
use Psr\Log\LoggerInterface;
10
use TheCodingMachine\TDBM\Utils\DbalUtils;
11
12
/*
13
 Copyright (C) 2006-2017 David Négrier - THE CODING MACHINE
14
15
 This program is free software; you can redistribute it and/or modify
16
 it under the terms of the GNU General Public License as published by
17
 the Free Software Foundation; either version 2 of the License, or
18
 (at your option) any later version.
19
20
 This program is distributed in the hope that it will be useful,
21
 but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 GNU General Public License for more details.
24
25
 You should have received a copy of the GNU General Public License
26
 along with this program; if not, write to the Free Software
27
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
28
 */
29
30
/**
31
 * Iterator used to retrieve results.
32
 */
33
class InnerResultIterator implements \Iterator, \Countable, \ArrayAccess
34
{
35
    /**
36
     * @var Statement
37
     */
38
    protected $statement;
39
40
    protected $fetchStarted = false;
41
    private $objectStorage;
42
    private $className;
43
44
    private $tdbmService;
45
    private $magicSql;
46
    private $parameters;
47
    private $limit;
48
    private $offset;
49
    private $columnDescriptors;
50
    private $magicQuery;
51
52
    /**
53
     * The key of the current retrieved object.
54
     *
55
     * @var int
56
     */
57
    protected $key = -1;
58
59
    protected $current = null;
60
61
    private $databasePlatform;
62
63
    /**
64
     * @var LoggerInterface
65
     */
66
    private $logger;
67
68
    /**
69
     * @param mixed[] $parameters
70
     * @param array[] $columnDescriptors
71
     */
72
    public function __construct(string $magicSql, array $parameters, ?int $limit, ?int $offset, array $columnDescriptors, ObjectStorageInterface $objectStorage, ?string $className, TDBMService $tdbmService, MagicQuery $magicQuery, LoggerInterface $logger)
73
    {
74
        $this->magicSql = $magicSql;
75
        $this->objectStorage = $objectStorage;
76
        $this->className = $className;
77
        $this->tdbmService = $tdbmService;
78
        $this->parameters = $parameters;
79
        $this->limit = $limit;
80
        $this->offset = $offset;
81
        $this->columnDescriptors = $columnDescriptors;
82
        $this->magicQuery = $magicQuery;
83
        $this->databasePlatform = $this->tdbmService->getConnection()->getDatabasePlatform();
84
        $this->logger = $logger;
85
    }
86
87
    private function getQuery(): string
88
    {
89
        $sql = $this->magicQuery->buildPreparedStatement($this->magicSql, $this->parameters);
90
        $sql = $this->tdbmService->getConnection()->getDatabasePlatform()->modifyLimitQuery($sql, $this->limit, $this->offset);
91
        return $sql;
92
    }
93
94
    protected function executeQuery(): void
95
    {
96
        $sql = $this->getQuery();
97
98
        $this->logger->debug('Running SQL request: '.$sql);
99
100
        $this->statement = $this->tdbmService->getConnection()->executeQuery($sql, $this->parameters, DbalUtils::generateArrayTypes($this->parameters));
101
102
        $this->fetchStarted = true;
103
    }
104
105
    private $count = null;
106
    /**
107
     * Counts found records (this is the number of records fetched, taking into account the LIMIT and OFFSET settings).
108
     *
109
     * @return int
110
     */
111
    public function count()
112
    {
113
        if ($this->count !== null) {
114
            return $this->count;
115
        }
116
117
        if ($this->fetchStarted && $this->tdbmService->getConnection()->getDatabasePlatform() instanceof MySqlPlatform) {
118
            // Optimisation: we don't need a separate "count" SQL request in MySQL.
119
            $this->count = $this->statement->rowCount();
120
            return $this->count;
121
        }
122
        return $this->getRowCountViaSqlQuery();
123
    }
124
125
    /**
126
     * Makes a separate SQL query to compute the row count.
127
     * (not needed in MySQL)
128
     */
129
    private function getRowCountViaSqlQuery(): int
130
    {
131
        $countSql = 'SELECT COUNT(1) FROM ('.$this->getQuery().') c';
132
133
        $this->logger->debug('Running count SQL request: '.$countSql);
134
135
        $this->count = (int) $this->tdbmService->getConnection()->fetchColumn($countSql, $this->parameters);
136
        return $this->count;
137
    }
138
139
    /**
140
     * Fetches record at current cursor.
141
     *
142
     * @return AbstractTDBMObject
143
     */
144
    public function current()
145
    {
146
        return $this->current;
147
    }
148
149
    /**
150
     * Returns the current result's key.
151
     *
152
     * @return int
153
     */
154
    public function key()
155
    {
156
        return $this->key;
157
    }
158
159
    /**
160
     * Advances the cursor to the next result.
161
     * Casts the database result into one (or several) beans.
162
     */
163
    public function next(): void
164
    {
165
        $row = $this->statement->fetch(\PDO::FETCH_ASSOC);
166
        if ($row) {
167
168
            // array<tablegroup, array<table, array<column, value>>>
169
            $beansData = [];
170
            $allNull = true;
171
            foreach ($row as $key => $value) {
172
                if (!isset($this->columnDescriptors[$key])) {
173
                    continue;
174
                }
175
                if ($allNull !== false && $value !== null) {
176
                    $allNull = false;
177
                }
178
179
                $columnDescriptor = $this->columnDescriptors[$key];
180
181
                if ($columnDescriptor['tableGroup'] === null) {
182
                    // A column can have no tableGroup (if it comes from an ORDER BY expression)
183
                    continue;
184
                }
185
186
                // Let's cast the value according to its type
187
                $value = $columnDescriptor['type']->convertToPHPValue($value, $this->databasePlatform);
188
189
                $beansData[$columnDescriptor['tableGroup']][$columnDescriptor['table']][$columnDescriptor['column']] = $value;
190
            }
191
            if ($allNull === true) {
192
                $this->next();
193
                return;
194
            }
195
196
            $reflectionClassCache = [];
197
            $firstBean = true;
198
            foreach ($beansData as $beanData) {
199
200
                // Let's find the bean class name associated to the bean.
201
202
                list($actualClassName, $mainBeanTableName, $tablesUsed) = $this->tdbmService->_getClassNameFromBeanData($beanData);
203
204
                if ($this->className !== null) {
205
                    $actualClassName = $this->className;
206
                }
207
208
                // Let's filter out the beanData that is not used (because it belongs to a part of the hierarchy that is not fetched:
209
                foreach ($beanData as $tableName => $descriptors) {
210
                    if (!in_array($tableName, $tablesUsed)) {
211
                        unset($beanData[$tableName]);
212
                    }
213
                }
214
215
                // Must we create the bean? Let's see in the cache if we have a mapping DbRow?
216
                // Let's get the first object mapping a row:
217
                // We do this loop only for the first table
218
219
                $primaryKeys = $this->tdbmService->_getPrimaryKeysFromObjectData($mainBeanTableName, $beanData[$mainBeanTableName]);
220
                $hash = $this->tdbmService->getObjectHash($primaryKeys);
221
222
                $dbRow = $this->objectStorage->get($mainBeanTableName, $hash);
223
                if ($dbRow !== null) {
224
                    $bean = $dbRow->getTDBMObject();
225
                } else {
226
                    // Let's construct the bean
227
                    if (!isset($reflectionClassCache[$actualClassName])) {
228
                        $reflectionClassCache[$actualClassName] = new \ReflectionClass($actualClassName);
229
                    }
230
                    // Let's bypass the constructor when creating the bean!
231
                    $bean = $reflectionClassCache[$actualClassName]->newInstanceWithoutConstructor();
232
                    $bean->_constructFromData($beanData, $this->tdbmService);
233
                }
234
235
                // The first bean is the one containing the main table.
236
                if ($firstBean) {
237
                    $firstBean = false;
238
                    $this->current = $bean;
239
                }
240
            }
241
242
            ++$this->key;
243
        } else {
244
            $this->current = null;
245
        }
246
    }
247
248
    /**
249
     * Moves the cursor to the beginning of the result set.
250
     */
251
    public function rewind()
252
    {
253
        $this->executeQuery();
254
        $this->key = -1;
255
        $this->next();
256
    }
257
    /**
258
     * Checks if the cursor is reading a valid result.
259
     *
260
     * @return bool
261
     */
262
    public function valid()
263
    {
264
        return $this->current !== null;
265
    }
266
267
    /**
268
     * Whether a offset exists.
269
     *
270
     * @link http://php.net/manual/en/arrayaccess.offsetexists.php
271
     *
272
     * @param mixed $offset <p>
273
     *                      An offset to check for.
274
     *                      </p>
275
     *
276
     * @return bool true on success or false on failure.
277
     *              </p>
278
     *              <p>
279
     *              The return value will be casted to boolean if non-boolean was returned
280
     *
281
     * @since 5.0.0
282
     */
283
    public function offsetExists($offset)
284
    {
285
        throw new TDBMInvalidOperationException('You cannot access this result set via index because it was fetched in CURSOR mode. Use ARRAY_MODE instead.');
286
    }
287
288
    /**
289
     * Offset to retrieve.
290
     *
291
     * @link http://php.net/manual/en/arrayaccess.offsetget.php
292
     *
293
     * @param mixed $offset <p>
294
     *                      The offset to retrieve.
295
     *                      </p>
296
     *
297
     * @return mixed Can return all value types
298
     *
299
     * @since 5.0.0
300
     */
301
    public function offsetGet($offset)
302
    {
303
        throw new TDBMInvalidOperationException('You cannot access this result set via index because it was fetched in CURSOR mode. Use ARRAY_MODE instead.');
304
    }
305
306
    /**
307
     * Offset to set.
308
     *
309
     * @link http://php.net/manual/en/arrayaccess.offsetset.php
310
     *
311
     * @param mixed $offset <p>
312
     *                      The offset to assign the value to.
313
     *                      </p>
314
     * @param mixed $value  <p>
315
     *                      The value to set.
316
     *                      </p>
317
     *
318
     * @since 5.0.0
319
     */
320
    public function offsetSet($offset, $value)
321
    {
322
        throw new TDBMInvalidOperationException('You can set values in a TDBM result set.');
323
    }
324
325
    /**
326
     * Offset to unset.
327
     *
328
     * @link http://php.net/manual/en/arrayaccess.offsetunset.php
329
     *
330
     * @param mixed $offset <p>
331
     *                      The offset to unset.
332
     *                      </p>
333
     *
334
     * @since 5.0.0
335
     */
336
    public function offsetUnset($offset)
337
    {
338
        throw new TDBMInvalidOperationException('You can unset values in a TDBM result set.');
339
    }
340
}
341