Completed
Push — master ( 42e2f4...06fa71 )
by David
15s queued 12s
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, \Countable, \ArrayAccess
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
    public static function createEmpyIterator(): self
97
    {
98
        $iterator = new static();
99
        $iterator->count = 0;
100
        $iterator->logger = new NullLogger();
101
        return $iterator;
102
    }
103
104
    private function getQuery(): string
105
    {
106
        $sql = $this->magicQuery->buildPreparedStatement($this->magicSql, $this->parameters);
107
        $sql = $this->tdbmService->getConnection()->getDatabasePlatform()->modifyLimitQuery($sql, $this->limit, $this->offset);
108
        return $sql;
109
    }
110
111
    protected function executeQuery(): void
112
    {
113
        $sql = $this->getQuery();
114
115
        $this->logger->debug('Running SQL request: '.$sql);
116
117
        $this->statement = $this->tdbmService->getConnection()->executeQuery($sql, $this->parameters, DbalUtils::generateArrayTypes($this->parameters));
118
119
        $this->fetchStarted = true;
120
    }
121
122
    /**
123
     * Counts found records (this is the number of records fetched, taking into account the LIMIT and OFFSET settings).
124
     *
125
     * @return int
126
     */
127
    public function count()
128
    {
129
        if ($this->count !== null) {
130
            return $this->count;
131
        }
132
133
        if ($this->fetchStarted && $this->tdbmService->getConnection()->getDatabasePlatform() instanceof MySqlPlatform) {
134
            // Optimisation: we don't need a separate "count" SQL request in MySQL.
135
            $this->count = $this->statement->rowCount();
136
            return $this->count;
137
        }
138
        return $this->getRowCountViaSqlQuery();
139
    }
140
141
    /**
142
     * Makes a separate SQL query to compute the row count.
143
     * (not needed in MySQL if fetch is already done)
144
     */
145
    private function getRowCountViaSqlQuery(): int
146
    {
147
        $countSql = 'SELECT COUNT(1) FROM ('.$this->getQuery().') c';
148
149
        $this->logger->debug('Running count SQL request: '.$countSql);
150
151
        $this->count = (int) $this->tdbmService->getConnection()->fetchColumn($countSql, $this->parameters, 0, DbalUtils::generateArrayTypes($this->parameters));
152
        return $this->count;
153
    }
154
155
    /**
156
     * Fetches record at current cursor.
157
     *
158
     * @return AbstractTDBMObject
159
     */
160
    public function current()
161
    {
162
        return $this->current;
163
    }
164
165
    /**
166
     * Returns the current result's key.
167
     *
168
     * @return int
169
     */
170
    public function key()
171
    {
172
        return $this->key;
173
    }
174
175
    /**
176
     * Advances the cursor to the next result.
177
     * Casts the database result into one (or several) beans.
178
     */
179
    public function next()
180
    {
181
        $row = $this->statement->fetch(\PDO::FETCH_ASSOC);
182
        if ($row) {
183
184
            // array<tablegroup, array<table, array<column, value>>>
185
            $beansData = [];
186
            foreach ($row as $key => $value) {
187
                if (!isset($this->columnDescriptors[$key])) {
188
                    continue;
189
                }
190
191
                $columnDescriptor = $this->columnDescriptors[$key];
192
193
                if ($columnDescriptor['tableGroup'] === null) {
194
                    // A column can have no tableGroup (if it comes from an ORDER BY expression)
195
                    continue;
196
                }
197
198
                // Let's cast the value according to its type
199
                $value = $columnDescriptor['type']->convertToPHPValue($value, $this->databasePlatform);
200
201
                $beansData[$columnDescriptor['tableGroup']][$columnDescriptor['table']][$columnDescriptor['column']] = $value;
202
            }
203
204
            $reflectionClassCache = [];
205
            $firstBean = true;
206
            foreach ($beansData as $beanData) {
207
208
                // Let's find the bean class name associated to the bean.
209
210
                list($actualClassName, $mainBeanTableName, $tablesUsed) = $this->tdbmService->_getClassNameFromBeanData($beanData);
211
212
                if ($this->className !== null) {
213
                    $actualClassName = $this->className;
214
                }
215
216
                // Let's filter out the beanData that is not used (because it belongs to a part of the hierarchy that is not fetched:
217
                foreach ($beanData as $tableName => $descriptors) {
218
                    if (!in_array($tableName, $tablesUsed)) {
219
                        unset($beanData[$tableName]);
220
                    }
221
                }
222
223
                // Must we create the bean? Let's see in the cache if we have a mapping DbRow?
224
                // Let's get the first object mapping a row:
225
                // We do this loop only for the first table
226
227
                $primaryKeys = $this->tdbmService->_getPrimaryKeysFromObjectData($mainBeanTableName, $beanData[$mainBeanTableName]);
228
                $hash = $this->tdbmService->getObjectHash($primaryKeys);
229
230
                $dbRow = $this->objectStorage->get($mainBeanTableName, $hash);
231
                if ($dbRow !== null) {
232
                    $bean = $dbRow->getTDBMObject();
233
                } else {
234
                    // Let's construct the bean
235
                    if (!isset($reflectionClassCache[$actualClassName])) {
236
                        $reflectionClassCache[$actualClassName] = new \ReflectionClass($actualClassName);
237
                    }
238
                    // Let's bypass the constructor when creating the bean!
239
                    $bean = $reflectionClassCache[$actualClassName]->newInstanceWithoutConstructor();
240
                    $bean->_constructFromData($beanData, $this->tdbmService);
241
                }
242
243
                // The first bean is the one containing the main table.
244
                if ($firstBean) {
245
                    $firstBean = false;
246
                    $this->current = $bean;
247
                }
248
            }
249
250
            ++$this->key;
251
        } else {
252
            $this->current = null;
253
        }
254
    }
255
256
    /**
257
     * Moves the cursor to the beginning of the result set.
258
     */
259
    public function rewind()
260
    {
261
        if ($this->count === 0) {
262
            return;
263
        }
264
        $this->executeQuery();
265
        $this->key = -1;
266
        $this->next();
267
    }
268
    /**
269
     * Checks if the cursor is reading a valid result.
270
     *
271
     * @return bool
272
     */
273
    public function valid()
274
    {
275
        if ($this->count === 0) {
276
            return false;
277
        }
278
        return $this->current !== null;
279
    }
280
281
    /**
282
     * Whether a offset exists.
283
     *
284
     * @link http://php.net/manual/en/arrayaccess.offsetexists.php
285
     *
286
     * @param mixed $offset <p>
287
     *                      An offset to check for.
288
     *                      </p>
289
     *
290
     * @return bool true on success or false on failure.
291
     *              </p>
292
     *              <p>
293
     *              The return value will be casted to boolean if non-boolean was returned
294
     *
295
     * @since 5.0.0
296
     */
297
    public function offsetExists($offset)
298
    {
299
        throw new TDBMInvalidOperationException('You cannot access this result set via index because it was fetched in CURSOR mode. Use ARRAY_MODE instead.');
300
    }
301
302
    /**
303
     * Offset to retrieve.
304
     *
305
     * @link http://php.net/manual/en/arrayaccess.offsetget.php
306
     *
307
     * @param mixed $offset <p>
308
     *                      The offset to retrieve.
309
     *                      </p>
310
     *
311
     * @return mixed Can return all value types
312
     *
313
     * @since 5.0.0
314
     */
315
    public function offsetGet($offset)
316
    {
317
        throw new TDBMInvalidOperationException('You cannot access this result set via index because it was fetched in CURSOR mode. Use ARRAY_MODE instead.');
318
    }
319
320
    /**
321
     * Offset to set.
322
     *
323
     * @link http://php.net/manual/en/arrayaccess.offsetset.php
324
     *
325
     * @param mixed $offset <p>
326
     *                      The offset to assign the value to.
327
     *                      </p>
328
     * @param mixed $value  <p>
329
     *                      The value to set.
330
     *                      </p>
331
     *
332
     * @since 5.0.0
333
     */
334
    public function offsetSet($offset, $value)
335
    {
336
        throw new TDBMInvalidOperationException('You cannot set values in a TDBM result set.');
337
    }
338
339
    /**
340
     * Offset to unset.
341
     *
342
     * @link http://php.net/manual/en/arrayaccess.offsetunset.php
343
     *
344
     * @param mixed $offset <p>
345
     *                      The offset to unset.
346
     *                      </p>
347
     *
348
     * @since 5.0.0
349
     */
350
    public function offsetUnset($offset)
351
    {
352
        throw new TDBMInvalidOperationException('You cannot unset values in a TDBM result set.');
353
    }
354
}
355