InnerResultIterator::createInnerResultIterator()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 13
c 0
b 0
f 0
dl 0
loc 15
rs 9.8333
cc 1
nc 1
nop 10

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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

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

183
        /** @scrutinizer ignore-call */ 
184
        $row = $this->result->fetchAssociative();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

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