Passed
Pull Request — master (#12)
by Dorian
04:12
created

InnerResultIterator::executeQuery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 0
1
<?php
2
3
namespace TheCodingMachine\TDBM;
4
5
use Doctrine\DBAL\Platforms\MySqlPlatform;
6
use Doctrine\DBAL\Statement;
7
use Mouf\Database\MagicQuery;
8
use Psr\Log\LoggerInterface;
9
10
/*
11
 Copyright (C) 2006-2017 David Négrier - THE CODING MACHINE
12
13
 This program is free software; you can redistribute it and/or modify
14
 it under the terms of the GNU General Public License as published by
15
 the Free Software Foundation; either version 2 of the License, or
16
 (at your option) any later version.
17
18
 This program is distributed in the hope that it will be useful,
19
 but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21
 GNU General Public License for more details.
22
23
 You should have received a copy of the GNU General Public License
24
 along with this program; if not, write to the Free Software
25
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
26
 */
27
28
/**
29
 * Iterator used to retrieve results.
30
 */
31
class InnerResultIterator implements \Iterator, \Countable, \ArrayAccess
32
{
33
    /**
34
     * @var Statement
35
     */
36
    protected $statement;
37
38
    protected $fetchStarted = false;
39
    private $objectStorage;
40
    private $className;
41
42
    private $tdbmService;
43
    private $magicSql;
44
    private $parameters;
45
    private $limit;
46
    private $offset;
47
    private $columnDescriptors;
48
    private $magicQuery;
49
50
    /**
51
     * The key of the current retrieved object.
52
     *
53
     * @var int
54
     */
55
    protected $key = -1;
56
57
    protected $current = null;
58
59
    private $databasePlatform;
60
61
    /**
62
     * @var LoggerInterface
63
     */
64
    private $logger;
65
66
    public function __construct($magicSql, array $parameters, $limit, $offset, array $columnDescriptors, $objectStorage, $className, TDBMService $tdbmService, MagicQuery $magicQuery, LoggerInterface $logger)
67
    {
68
        $this->magicSql = $magicSql;
69
        $this->objectStorage = $objectStorage;
70
        $this->className = $className;
71
        $this->tdbmService = $tdbmService;
72
        $this->parameters = $parameters;
73
        $this->limit = $limit;
74
        $this->offset = $offset;
75
        $this->columnDescriptors = $columnDescriptors;
76
        $this->magicQuery = $magicQuery;
77
        $this->databasePlatform = $this->tdbmService->getConnection()->getDatabasePlatform();
78
        $this->logger = $logger;
79
    }
80
81
    private function getQuery(): string
82
    {
83
        $sql = $this->magicQuery->build($this->magicSql, $this->parameters);
84
        $sql = $this->tdbmService->getConnection()->getDatabasePlatform()->modifyLimitQuery($sql, $this->limit, $this->offset);
85
        return $sql;
86
    }
87
88
    protected function executeQuery()
89
    {
90
        $sql = $this->getQuery();
91
92
        $this->logger->debug('Running SQL request: '.$sql);
93
94
        $this->statement = $this->tdbmService->getConnection()->executeQuery($sql, $this->parameters);
95
96
        $this->fetchStarted = true;
97
    }
98
99
    /**
100
     * Counts found records (this is the number of records fetched, taking into account the LIMIT and OFFSET settings).
101
     *
102
     * @return int
103
     */
104
    public function count()
105
    {
106
        if ($this->count !== null) {
107
            return $this->count;
108
        }
109
110
        if ($this->tdbmService->getConnection()->getDatabasePlatform() instanceof MySqlPlatform) {
111
            // Optimisation: we don't need a separate "count" SQL request in MySQL.
112
            return $this->getRowCountViaRowCountFunction();
113
        } else {
114
            return $this->getRowCountViaSqlQuery();
115
        }
116
    }
117
118
    private $count = null;
119
120
    /**
121
     * Get the row count from the rowCount function (only works with MySQL)
122
     */
123
    private function getRowCountViaRowCountFunction(): int
124
    {
125
        if (!$this->fetchStarted) {
126
            $this->executeQuery();
127
        }
128
129
        $this->count = $this->statement->rowCount();
130
        return $this->count;
131
    }
132
133
134
    /**
135
     * Makes a separate SQL query to compute the row count.
136
     * (not needed in MySQL)
137
     */
138
    private function getRowCountViaSqlQuery(): int
139
    {
140
        $countSql = 'SELECT COUNT(1) FROM ('.$this->getQuery().') c';
141
142
        $this->logger->debug('Running count SQL request: '.$countSql);
143
144
        $this->count = $this->tdbmService->getConnection()->fetchColumn($countSql, $this->parameters);
145
        return $this->count;
146
    }
147
148
    /**
149
     * Fetches record at current cursor.
150
     *
151
     * @return AbstractTDBMObject|null
152
     */
153
    public function current()
154
    {
155
        return $this->current;
156
    }
157
158
    /**
159
     * Returns the current result's key.
160
     *
161
     * @return int
162
     */
163
    public function key()
164
    {
165
        return $this->key;
166
    }
167
168
    /**
169
     * Advances the cursor to the next result.
170
     * Casts the database result into one (or several) beans.
171
     */
172
    public function next()
173
    {
174
        $row = $this->statement->fetch(\PDO::FETCH_LAZY);
175
        if ($row) {
176
177
            // array<tablegroup, array<table, array<column, value>>>
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

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