Passed
Push — 5.3 ( 4f130d...593c4e )
by
unknown
09:17
created

InnerResultIterator::next()   F

Complexity

Conditions 17
Paths 301

Size

Total Lines 87
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 17
eloc 44
c 1
b 1
f 0
nc 301
nop 0
dl 0
loc 87
rs 3.1208

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
3
declare(strict_types=1);
4
5
namespace TheCodingMachine\TDBM;
6
7
use Doctrine\DBAL\Driver\ResultStatement;
8
use Doctrine\DBAL\ForwardCompatibility\Result;
9
use Doctrine\DBAL\Platforms\AbstractPlatform;
10
use Doctrine\DBAL\Platforms\MySqlPlatform;
11
use Doctrine\DBAL\Statement;
12
use Mouf\Database\MagicQuery;
13
use Psr\Log\LoggerInterface;
14
use TheCodingMachine\TDBM\Utils\DbalUtils;
15
16
/*
17
 Copyright (C) 2006-2017 David Négrier - THE CODING MACHINE
18
19
 This program is free software; you can redistribute it and/or modify
20
 it under the terms of the GNU General Public License as published by
21
 the Free Software Foundation; either version 2 of the License, or
22
 (at your option) any later version.
23
24
 This program is distributed in the hope that it will be useful,
25
 but WITHOUT ANY WARRANTY; without even the implied warranty of
26
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27
 GNU General Public License for more details.
28
29
 You should have received a copy of the GNU General Public License
30
 along with this program; if not, write to the Free Software
31
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
32
 */
33
34
/**
35
 * Iterator used to retrieve results.
36
 */
37
class InnerResultIterator implements \Iterator, InnerResultIteratorInterface
38
{
39
    /** @var ResultStatement|Statement */
40
    protected $statement;
41
42
    /** @var bool */
43
    protected $fetchStarted = false;
44
    /** @var ObjectStorageInterface */
45
    private $objectStorage;
46
    /** @var string|null */
47
    private $className;
48
49
    /** @var TDBMService */
50
    private $tdbmService;
51
    /** @var string */
52
    private $magicSql;
53
    /** @var mixed[] */
54
    private $parameters;
55
    /** @var int|null */
56
    private $limit;
57
    /** @var int|null */
58
    private $offset;
59
    /** @var array[] */
60
    private $columnDescriptors;
61
    /** @var MagicQuery */
62
    private $magicQuery;
63
64
    /**
65
     * The key of the current retrieved object.
66
     *
67
     * @var int
68
     */
69
    protected $key = -1;
70
71
    /** @var AbstractTDBMObject|null */
72
    protected $current = null;
73
74
    /** @var AbstractPlatform */
75
    private $databasePlatform;
76
77
    /** @var LoggerInterface */
78
    private $logger;
79
80
    /** @var int|null */
81
    protected $count = null;
82
83
    final private function __construct()
84
    {
85
    }
86
87
    /**
88
     * @param mixed[] $parameters
89
     * @param array[] $columnDescriptors
90
     */
91
    public static function createInnerResultIterator(string $magicSql, array $parameters, ?int $limit, ?int $offset, array $columnDescriptors, ObjectStorageInterface $objectStorage, ?string $className, TDBMService $tdbmService, MagicQuery $magicQuery, LoggerInterface $logger): self
92
    {
93
        $iterator = new static();
94
        $iterator->magicSql = $magicSql;
95
        $iterator->objectStorage = $objectStorage;
96
        $iterator->className = $className;
97
        $iterator->tdbmService = $tdbmService;
98
        $iterator->parameters = $parameters;
99
        $iterator->limit = $limit;
100
        $iterator->offset = $offset;
101
        $iterator->columnDescriptors = $columnDescriptors;
102
        $iterator->magicQuery = $magicQuery;
103
        $iterator->databasePlatform = $iterator->tdbmService->getConnection()->getDatabasePlatform();
104
        $iterator->logger = $logger;
105
        return $iterator;
106
    }
107
108
    private function getQuery(): string
109
    {
110
        $sql = $this->magicQuery->buildPreparedStatement($this->magicSql, $this->parameters);
111
        $sql = $this->tdbmService->getConnection()->getDatabasePlatform()->modifyLimitQuery($sql, $this->limit, $this->offset);
112
        return $sql;
113
    }
114
115
    protected function executeQuery(): void
116
    {
117
        $sql = $this->getQuery();
118
119
        $this->logger->debug('Running SQL request: '.$sql);
120
121
        $this->statement = $this->tdbmService->getConnection()->executeQuery($sql, $this->parameters, DbalUtils::generateTypes($this->parameters));
122
123
        $this->fetchStarted = true;
124
    }
125
126
    /**
127
     * Counts found records (this is the number of records fetched, taking into account the LIMIT and OFFSET settings).
128
     *
129
     * @return int
130
     */
131
    public function count()
132
    {
133
        if ($this->count !== null) {
134
            return $this->count;
135
        }
136
137
        if ($this->fetchStarted && $this->tdbmService->getConnection()->getDatabasePlatform() instanceof MySqlPlatform) {
138
            // Optimisation: we don't need a separate "count" SQL request in MySQL.
139
            assert($this->statement instanceof Statement || $this->statement instanceof Result);
140
            $this->count = (int)$this->statement->rowCount();
141
            return $this->count;
142
        }
143
        return $this->getRowCountViaSqlQuery();
144
    }
145
146
    /**
147
     * Makes a separate SQL query to compute the row count.
148
     * (not needed in MySQL if fetch is already done)
149
     */
150
    private function getRowCountViaSqlQuery(): int
151
    {
152
        $countSql = 'SELECT COUNT(1) FROM ('.$this->getQuery().') c';
153
154
        $this->logger->debug('Running count SQL request: '.$countSql);
155
156
        $this->count = (int) $this->tdbmService->getConnection()->fetchColumn($countSql, $this->parameters, 0, DbalUtils::generateTypes($this->parameters));
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Connection::fetchColumn() has been deprecated: Use fetchOne() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

156
        $this->count = (int) /** @scrutinizer ignore-deprecated */ $this->tdbmService->getConnection()->fetchColumn($countSql, $this->parameters, 0, DbalUtils::generateTypes($this->parameters));

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
157
        return $this->count;
158
    }
159
160
    /**
161
     * Fetches record at current cursor.
162
     *
163
     * @return AbstractTDBMObject
164
     */
165
    public function current()
166
    {
167
        return $this->current;
168
    }
169
170
    /**
171
     * Returns the current result's key.
172
     *
173
     * @return int
174
     */
175
    public function key()
176
    {
177
        return $this->key;
178
    }
179
180
    /**
181
     * Advances the cursor to the next result.
182
     * Casts the database result into one (or several) beans.
183
     */
184
    public function next(): void
185
    {
186
        $row = $this->statement->fetch(\PDO::FETCH_ASSOC);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Statement::fetch() has been deprecated: Use fetchNumeric(), fetchAssociative() or fetchOne() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

186
        $row = /** @scrutinizer ignore-deprecated */ $this->statement->fetch(\PDO::FETCH_ASSOC);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
Deprecated Code introduced by
The function Doctrine\DBAL\Driver\ResultStatement::fetch() has been deprecated: Use fetchNumeric(), fetchAssociative() or fetchOne() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

186
        $row = /** @scrutinizer ignore-deprecated */ $this->statement->fetch(\PDO::FETCH_ASSOC);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
187
        if ($row) {
188
            /** @var array<string, array<string, array<string, mixed>>> $beansData array<tablegroup, array<table, array<column, value>>>*/
189
            $beansData = [];
190
            $allNull = true;
191
            foreach ($row as $key => $value) {
192
                if (!isset($this->columnDescriptors[$key])) {
193
                    continue;
194
                }
195
                if ($allNull !== false && $value !== null) {
196
                    $allNull = false;
197
                }
198
199
                $columnDescriptor = $this->columnDescriptors[$key];
200
201
                if ($columnDescriptor['tableGroup'] === null) {
202
                    // A column can have no tableGroup (if it comes from an ORDER BY expression)
203
                    continue;
204
                }
205
206
                // Let's cast the value according to its type
207
                $value = $columnDescriptor['type']->convertToPHPValue($value, $this->databasePlatform);
208
209
                $beansData[$columnDescriptor['tableGroup']][$columnDescriptor['table']][$columnDescriptor['column']] = $value;
210
            }
211
            if ($allNull === true) {
212
                $this->next();
213
                return;
214
            }
215
216
            $reflectionClassCache = [];
217
            $firstBean = true;
218
            /** @var array<string, array<string, mixed>> $beanData */
219
            foreach ($beansData as $beanData) {
220
                // Let's find the bean class name associated to the bean.
221
222
                list($actualClassName, $mainBeanTableName, $tablesUsed) = $this->tdbmService->_getClassNameFromBeanData($beanData);
223
224
                // @TODO (gua) this is a weird hack to be able to force a TDBMObject...
225
                // `$this->className` could be used to override `$actualClassName`
226
                if ($this->className !== null && is_a($this->className, TDBMObject::class, true)) {
227
                    $actualClassName = $this->className;
228
                }
229
230
                // Let's filter out the beanData that is not used (because it belongs to a part of the hierarchy that is not fetched:
231
                foreach ($beanData as $tableName => $descriptors) {
232
                    if (!in_array($tableName, $tablesUsed)) {
233
                        unset($beanData[$tableName]);
234
                    }
235
                }
236
237
                // Must we create the bean? Let's see in the cache if we have a mapping DbRow?
238
                // Let's get the first object mapping a row:
239
                // We do this loop only for the first table
240
241
                $primaryKeys = $this->tdbmService->_getPrimaryKeysFromObjectData($mainBeanTableName, $beanData[$mainBeanTableName]);
242
                $hash = $this->tdbmService->getObjectHash($primaryKeys);
243
244
                $dbRow = $this->objectStorage->get($mainBeanTableName, $hash);
245
                if ($dbRow !== null) {
246
                    $bean = $dbRow->getTDBMObject();
247
                    if ($bean->_getStatus() === TDBMObjectStateEnum::STATE_NOT_LOADED) {
248
                        $bean->_constructFromData($beanData, $this->tdbmService);
249
                    }
250
                } else {
251
                    // Let's construct the bean
252
                    if (!isset($reflectionClassCache[$actualClassName])) {
253
                        $reflectionClassCache[$actualClassName] = new \ReflectionClass($actualClassName);
254
                    }
255
                    // Let's bypass the constructor when creating the bean!
256
                    /** @var AbstractTDBMObject $bean */
257
                    $bean = $reflectionClassCache[$actualClassName]->newInstanceWithoutConstructor();
258
                    $bean->_constructFromData($beanData, $this->tdbmService);
259
                }
260
261
                // The first bean is the one containing the main table.
262
                if ($firstBean) {
263
                    $firstBean = false;
264
                    $this->current = $bean;
265
                }
266
            }
267
268
            ++$this->key;
269
        } else {
270
            $this->current = null;
271
        }
272
    }
273
274
    /**
275
     * Moves the cursor to the beginning of the result set.
276
     */
277
    public function rewind()
278
    {
279
        $this->executeQuery();
280
        $this->key = -1;
281
        $this->next();
282
    }
283
    /**
284
     * Checks if the cursor is reading a valid result.
285
     *
286
     * @return bool
287
     */
288
    public function valid()
289
    {
290
        return $this->current !== null;
291
    }
292
293
    /**
294
     * Whether a offset exists.
295
     *
296
     * @link http://php.net/manual/en/arrayaccess.offsetexists.php
297
     *
298
     * @param mixed $offset <p>
299
     *                      An offset to check for.
300
     *                      </p>
301
     *
302
     * @return bool true on success or false on failure.
303
     *              </p>
304
     *              <p>
305
     *              The return value will be casted to boolean if non-boolean was returned
306
     *
307
     * @since 5.0.0
308
     */
309
    public function offsetExists($offset)
310
    {
311
        throw new TDBMInvalidOperationException('You cannot access this result set via index because it was fetched in CURSOR mode. Use ARRAY_MODE instead.');
312
    }
313
314
    /**
315
     * Offset to retrieve.
316
     *
317
     * @link http://php.net/manual/en/arrayaccess.offsetget.php
318
     *
319
     * @param mixed $offset <p>
320
     *                      The offset to retrieve.
321
     *                      </p>
322
     *
323
     * @return mixed Can return all value types
324
     *
325
     * @since 5.0.0
326
     */
327
    public function offsetGet($offset)
328
    {
329
        throw new TDBMInvalidOperationException('You cannot access this result set via index because it was fetched in CURSOR mode. Use ARRAY_MODE instead.');
330
    }
331
332
    /**
333
     * Offset to set.
334
     *
335
     * @link http://php.net/manual/en/arrayaccess.offsetset.php
336
     *
337
     * @param mixed $offset <p>
338
     *                      The offset to assign the value to.
339
     *                      </p>
340
     * @param mixed $value  <p>
341
     *                      The value to set.
342
     *                      </p>
343
     *
344
     * @since 5.0.0
345
     */
346
    public function offsetSet($offset, $value)
347
    {
348
        throw new TDBMInvalidOperationException('You cannot set values in a TDBM result set.');
349
    }
350
351
    /**
352
     * Offset to unset.
353
     *
354
     * @link http://php.net/manual/en/arrayaccess.offsetunset.php
355
     *
356
     * @param mixed $offset <p>
357
     *                      The offset to unset.
358
     *                      </p>
359
     *
360
     * @since 5.0.0
361
     */
362
    public function offsetUnset($offset)
363
    {
364
        throw new TDBMInvalidOperationException('You cannot unset values in a TDBM result set.');
365
    }
366
}
367