Completed
Push — master ( 65e5fe...b5ff43 )
by David
17s queued 12s
created

InnerResultIterator::next()   C

Complexity

Conditions 12
Paths 149

Size

Total Lines 74
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 36
dl 0
loc 74
rs 6.5583
c 0
b 0
f 0
cc 12
nc 149
nop 0

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