Passed
Pull Request — master (#179)
by David
03:19 queued 40s
created

InnerResultIterator::createInnerResultIterator()   A

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