Completed
Push — master ( 42e2f4...06fa71 )
by David
15s queued 12s
created

ResultIterator::createResultIterator()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 16
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 12
c 0
b 0
f 0
dl 0
loc 16
rs 9.8666
cc 3
nc 2
nop 8

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 Psr\Log\NullLogger;
7
use function array_map;
8
use Doctrine\DBAL\Connection;
9
use Doctrine\DBAL\Statement;
10
use function is_array;
11
use function is_int;
12
use Mouf\Database\MagicQuery;
13
use TheCodingMachine\TDBM\QueryFactory\QueryFactory;
14
use Porpaginas\Result;
15
use Psr\Log\LoggerInterface;
16
use TheCodingMachine\TDBM\Utils\DbalUtils;
17
use Traversable;
18
19
/*
20
 Copyright (C) 2006-2017 David Négrier - THE CODING MACHINE
21
22
 This program is free software; you can redistribute it and/or modify
23
 it under the terms of the GNU General Public License as published by
24
 the Free Software Foundation; either version 2 of the License, or
25
 (at your option) any later version.
26
27
 This program is distributed in the hope that it will be useful,
28
 but WITHOUT ANY WARRANTY; without even the implied warranty of
29
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
30
 GNU General Public License for more details.
31
32
 You should have received a copy of the GNU General Public License
33
 along with this program; if not, write to the Free Software
34
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
35
 */
36
37
/**
38
 * Iterator used to retrieve results.
39
 */
40
class ResultIterator implements Result, \ArrayAccess, \JsonSerializable
41
{
42
    /**
43
     * @var Statement
44
     */
45
    protected $statement;
46
47
    private $objectStorage;
48
    private $className;
49
50
    private $tdbmService;
51
    private $parameters;
52
    private $magicQuery;
53
54
    /**
55
     * @var QueryFactory
56
     */
57
    private $queryFactory;
58
59
    /**
60
     * @var InnerResultIterator|null
61
     */
62
    private $innerResultIterator;
63
64
    private $totalCount;
65
66
    private $mode;
67
68
    private $logger;
69
70
    private function __construct()
71
    {
72
    }
73
74
    /**
75
     * @param mixed[] $parameters
76
     */
77
    public static function createResultIterator(QueryFactory $queryFactory, array $parameters, ObjectStorageInterface $objectStorage, ?string $className, TDBMService $tdbmService, MagicQuery $magicQuery, int $mode, LoggerInterface $logger): self
78
    {
79
        $iterator =  new self();
80
        if ($mode !== TDBMService::MODE_CURSOR && $mode !== TDBMService::MODE_ARRAY) {
81
            throw new TDBMException("Unknown fetch mode: '".$mode."'");
82
        }
83
84
        $iterator->queryFactory = $queryFactory;
85
        $iterator->objectStorage = $objectStorage;
86
        $iterator->className = $className;
87
        $iterator->tdbmService = $tdbmService;
88
        $iterator->parameters = $parameters;
89
        $iterator->magicQuery = $magicQuery;
90
        $iterator->mode = $mode;
91
        $iterator->logger = $logger;
92
        return $iterator;
93
    }
94
95
    public static function createEmpyIterator(): self
96
    {
97
        $iterator = new self();
98
        $iterator->totalCount = 0;
99
        $iterator->logger = new NullLogger();
100
        return $iterator;
101
    }
102
103
    protected function executeCountQuery(): void
104
    {
105
        $sql = $this->magicQuery->buildPreparedStatement($this->queryFactory->getMagicSqlCount(), $this->parameters);
106
        $this->logger->debug('Running count query: '.$sql);
107
        $this->totalCount = (int) $this->tdbmService->getConnection()->fetchColumn($sql, $this->parameters, 0, DbalUtils::generateArrayTypes($this->parameters));
108
    }
109
110
    /**
111
     * Counts found records (this is the number of records fetched, taking into account the LIMIT and OFFSET settings).
112
     *
113
     * @return int
114
     */
115
    public function count(): int
116
    {
117
        if ($this->totalCount === null) {
118
            $this->executeCountQuery();
119
        }
120
121
        return $this->totalCount;
122
    }
123
124
    /**
125
     * Casts the result set to a PHP array.
126
     *
127
     * @return AbstractTDBMObject[]
128
     */
129
    public function toArray(): array
130
    {
131
        if ($this->totalCount === 0) {
132
            return [];
133
        }
134
        return iterator_to_array($this->getIterator());
135
    }
136
137
    /**
138
     * Returns a new iterator mapping any call using the $callable function.
139
     *
140
     * @param callable $callable
141
     *
142
     * @return MapIterator
143
     */
144
    public function map(callable $callable): MapIterator
145
    {
146
        if ($this->totalCount === 0) {
147
            return new MapIterator([], $callable);
148
        }
149
        return new MapIterator($this->getIterator(), $callable);
150
    }
151
152
    /**
153
     * Retrieve an external iterator.
154
     *
155
     * @link http://php.net/manual/en/iteratoraggregate.getiterator.php
156
     *
157
     * @return InnerResultIterator An instance of an object implementing <b>Iterator</b> or
158
     *                             <b>Traversable</b>
159
     *
160
     * @since 5.0.0
161
     */
162
    public function getIterator()
163
    {
164
        if ($this->innerResultIterator === null) {
165
            if ($this->totalCount === 0) {
166
                $this->innerResultIterator = InnerResultArray::createEmpyIterator();
167
            } elseif ($this->mode === TDBMService::MODE_CURSOR) {
168
                $this->innerResultIterator = InnerResultIterator::createInnerResultIterator($this->queryFactory->getMagicSql(), $this->parameters, null, null, $this->queryFactory->getColumnDescriptors(), $this->objectStorage, $this->className, $this->tdbmService, $this->magicQuery, $this->logger);
169
            } else {
170
                $this->innerResultIterator = InnerResultArray::createInnerResultIterator($this->queryFactory->getMagicSql(), $this->parameters, null, null, $this->queryFactory->getColumnDescriptors(), $this->objectStorage, $this->className, $this->tdbmService, $this->magicQuery, $this->logger);
171
            }
172
        }
173
174
        return $this->innerResultIterator;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->innerResultIterator returns the type TheCodingMachine\TDBM\In...DBM\InnerResultIterator which is incompatible with the return type mandated by Porpaginas\Result::getIterator() of Porpaginas\Iterator.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
175
    }
176
177
    /**
178
     * @param int $offset
179
     * @param int $limit
180
     *
181
     * @return PageIterator
182
     */
183
    public function take($offset, $limit)
184
    {
185
        if ($this->totalCount === 0) {
186
            return PageIterator::createEmpyIterator($this);
187
        }
188
        return PageIterator::createResultIterator($this, $this->queryFactory->getMagicSql(), $this->parameters, $limit, $offset, $this->queryFactory->getColumnDescriptors(), $this->objectStorage, $this->className, $this->tdbmService, $this->magicQuery, $this->mode, $this->logger);
189
    }
190
191
    /**
192
     * Whether a offset exists.
193
     *
194
     * @link http://php.net/manual/en/arrayaccess.offsetexists.php
195
     *
196
     * @param mixed $offset <p>
197
     *                      An offset to check for.
198
     *                      </p>
199
     *
200
     * @return bool true on success or false on failure.
201
     *              </p>
202
     *              <p>
203
     *              The return value will be casted to boolean if non-boolean was returned
204
     *
205
     * @since 5.0.0
206
     */
207
    public function offsetExists($offset)
208
    {
209
        return $this->getIterator()->offsetExists($offset);
210
    }
211
212
    /**
213
     * Offset to retrieve.
214
     *
215
     * @link http://php.net/manual/en/arrayaccess.offsetget.php
216
     *
217
     * @param mixed $offset <p>
218
     *                      The offset to retrieve.
219
     *                      </p>
220
     *
221
     * @return mixed Can return all value types
222
     *
223
     * @since 5.0.0
224
     */
225
    public function offsetGet($offset)
226
    {
227
        return $this->getIterator()->offsetGet($offset);
228
    }
229
230
    /**
231
     * Offset to set.
232
     *
233
     * @link http://php.net/manual/en/arrayaccess.offsetset.php
234
     *
235
     * @param mixed $offset <p>
236
     *                      The offset to assign the value to.
237
     *                      </p>
238
     * @param mixed $value  <p>
239
     *                      The value to set.
240
     *                      </p>
241
     *
242
     * @since 5.0.0
243
     */
244
    public function offsetSet($offset, $value)
245
    {
246
        return $this->getIterator()->offsetSet($offset, $value);
247
    }
248
249
    /**
250
     * Offset to unset.
251
     *
252
     * @link http://php.net/manual/en/arrayaccess.offsetunset.php
253
     *
254
     * @param mixed $offset <p>
255
     *                      The offset to unset.
256
     *                      </p>
257
     *
258
     * @since 5.0.0
259
     */
260
    public function offsetUnset($offset)
261
    {
262
        return $this->getIterator()->offsetUnset($offset);
263
    }
264
265
    /**
266
     * Specify data which should be serialized to JSON.
267
     *
268
     * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
269
     *
270
     * @param bool $stopRecursion Parameter used internally by TDBM to
271
     *                            stop embedded objects from embedding
272
     *                            other objects
273
     *
274
     * @return mixed data which can be serialized by <b>json_encode</b>,
275
     *               which is a value of any type other than a resource
276
     *
277
     * @since 5.4.0
278
     */
279
    public function jsonSerialize($stopRecursion = false)
280
    {
281
        return array_map(function (AbstractTDBMObject $item) use ($stopRecursion) {
282
            return $item->jsonSerialize($stopRecursion);
0 ignored issues
show
Unused Code introduced by
The call to JsonSerializable::jsonSerialize() has too many arguments starting with $stopRecursion. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

282
            return $item->/** @scrutinizer ignore-call */ jsonSerialize($stopRecursion);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
283
        }, $this->toArray());
284
    }
285
286
    /**
287
     * Returns only one value (the first) of the result set.
288
     * Returns null if no value exists.
289
     *
290
     * @return mixed|null
291
     */
292
    public function first()
293
    {
294
        if ($this->totalCount === 0) {
295
            return null;
296
        }
297
        $page = $this->take(0, 1);
298
        foreach ($page as $bean) {
299
            return $bean;
300
        }
301
302
        return null;
303
    }
304
305
    /**
306
     * Sets the ORDER BY directive executed in SQL and returns a NEW ResultIterator.
307
     *
308
     * For instance:
309
     *
310
     *  $resultSet = $resultSet->withOrder('label ASC, status DESC');
311
     *
312
     * **Important:** TDBM does its best to protect you from SQL injection. In particular, it will only allow column names in the "ORDER BY" clause. This means you are safe to pass input from the user directly in the ORDER BY parameter.
313
     * If you want to pass an expression to the ORDER BY clause, you will need to tell TDBM to stop checking for SQL injections. You do this by passing a `UncheckedOrderBy` object as a parameter:
314
     *
315
     *  $resultSet->withOrder(new UncheckedOrderBy('RAND()'))
316
     *
317
     * @param string|UncheckedOrderBy|null $orderBy
318
     *
319
     * @return ResultIterator
320
     */
321
    public function withOrder($orderBy) : ResultIterator
322
    {
323
        $clone = clone $this;
324
        if ($this->totalCount === 0) {
325
            return $clone;
326
        }
327
        $clone->queryFactory = clone $this->queryFactory;
328
        $clone->queryFactory->sort($orderBy);
329
        $clone->innerResultIterator = null;
330
331
        return $clone;
332
    }
333
334
    /**
335
     * Sets new parameters for the SQL query and returns a NEW ResultIterator.
336
     *
337
     * For instance:
338
     *
339
     *  $resultSet = $resultSet->withParameters([ 'status' => 'on' ]);
340
     *
341
     * @param mixed[] $parameters
342
     *
343
     * @return ResultIterator
344
     */
345
    public function withParameters(array $parameters) : ResultIterator
346
    {
347
        $clone = clone $this;
348
        if ($this->totalCount === 0) {
349
            return $clone;
350
        }
351
        $clone->parameters = $parameters;
352
        $clone->innerResultIterator = null;
353
        $clone->totalCount = null;
354
355
        return $clone;
356
    }
357
}
358