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

InnerResultIterator::offsetUnset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 1
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