Passed
Pull Request — 5.1 (#197)
by ARP
06:50
created

InnerResultIterator::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 13
rs 9.9
c 0
b 0
f 0
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 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
            }
194
195
            $reflectionClassCache = [];
196
            $firstBean = true;
197
            foreach ($beansData as $beanData) {
198
199
                // Let's find the bean class name associated to the bean.
200
201
                list($actualClassName, $mainBeanTableName, $tablesUsed) = $this->tdbmService->_getClassNameFromBeanData($beanData);
202
203
                if ($this->className !== null) {
204
                    $actualClassName = $this->className;
205
                }
206
207
                // Let's filter out the beanData that is not used (because it belongs to a part of the hierarchy that is not fetched:
208
                foreach ($beanData as $tableName => $descriptors) {
209
                    if (!in_array($tableName, $tablesUsed)) {
210
                        unset($beanData[$tableName]);
211
                    }
212
                }
213
214
                // Must we create the bean? Let's see in the cache if we have a mapping DbRow?
215
                // Let's get the first object mapping a row:
216
                // We do this loop only for the first table
217
218
                $primaryKeys = $this->tdbmService->_getPrimaryKeysFromObjectData($mainBeanTableName, $beanData[$mainBeanTableName]);
219
                $hash = $this->tdbmService->getObjectHash($primaryKeys);
220
221
                $dbRow = $this->objectStorage->get($mainBeanTableName, $hash);
222
                if ($dbRow !== null) {
223
                    $bean = $dbRow->getTDBMObject();
224
                } else {
225
                    // Let's construct the bean
226
                    if (!isset($reflectionClassCache[$actualClassName])) {
227
                        $reflectionClassCache[$actualClassName] = new \ReflectionClass($actualClassName);
228
                    }
229
                    // Let's bypass the constructor when creating the bean!
230
                    $bean = $reflectionClassCache[$actualClassName]->newInstanceWithoutConstructor();
231
                    $bean->_constructFromData($beanData, $this->tdbmService);
232
                }
233
234
                // The first bean is the one containing the main table.
235
                if ($firstBean) {
236
                    $firstBean = false;
237
                    $this->current = $bean;
238
                }
239
            }
240
241
            ++$this->key;
242
        } else {
243
            $this->current = null;
244
        }
245
    }
246
247
    /**
248
     * Moves the cursor to the beginning of the result set.
249
     */
250
    public function rewind()
251
    {
252
        $this->executeQuery();
253
        $this->key = -1;
254
        $this->next();
255
    }
256
    /**
257
     * Checks if the cursor is reading a valid result.
258
     *
259
     * @return bool
260
     */
261
    public function valid()
262
    {
263
        return $this->current !== null;
264
    }
265
266
    /**
267
     * Whether a offset exists.
268
     *
269
     * @link http://php.net/manual/en/arrayaccess.offsetexists.php
270
     *
271
     * @param mixed $offset <p>
272
     *                      An offset to check for.
273
     *                      </p>
274
     *
275
     * @return bool true on success or false on failure.
276
     *              </p>
277
     *              <p>
278
     *              The return value will be casted to boolean if non-boolean was returned
279
     *
280
     * @since 5.0.0
281
     */
282
    public function offsetExists($offset)
283
    {
284
        throw new TDBMInvalidOperationException('You cannot access this result set via index because it was fetched in CURSOR mode. Use ARRAY_MODE instead.');
285
    }
286
287
    /**
288
     * Offset to retrieve.
289
     *
290
     * @link http://php.net/manual/en/arrayaccess.offsetget.php
291
     *
292
     * @param mixed $offset <p>
293
     *                      The offset to retrieve.
294
     *                      </p>
295
     *
296
     * @return mixed Can return all value types
297
     *
298
     * @since 5.0.0
299
     */
300
    public function offsetGet($offset)
301
    {
302
        throw new TDBMInvalidOperationException('You cannot access this result set via index because it was fetched in CURSOR mode. Use ARRAY_MODE instead.');
303
    }
304
305
    /**
306
     * Offset to set.
307
     *
308
     * @link http://php.net/manual/en/arrayaccess.offsetset.php
309
     *
310
     * @param mixed $offset <p>
311
     *                      The offset to assign the value to.
312
     *                      </p>
313
     * @param mixed $value  <p>
314
     *                      The value to set.
315
     *                      </p>
316
     *
317
     * @since 5.0.0
318
     */
319
    public function offsetSet($offset, $value)
320
    {
321
        throw new TDBMInvalidOperationException('You can set values in a TDBM result set.');
322
    }
323
324
    /**
325
     * Offset to unset.
326
     *
327
     * @link http://php.net/manual/en/arrayaccess.offsetunset.php
328
     *
329
     * @param mixed $offset <p>
330
     *                      The offset to unset.
331
     *                      </p>
332
     *
333
     * @since 5.0.0
334
     */
335
    public function offsetUnset($offset)
336
    {
337
        throw new TDBMInvalidOperationException('You can unset values in a TDBM result set.');
338
    }
339
}
340