1<?php
2
3namespace Doctrine\DBAL\ForwardCompatibility;
4
5use Doctrine\DBAL\Driver;
6use Doctrine\DBAL\Exception;
7use Doctrine\DBAL\Exception\NoKeyValue;
8use Doctrine\DBAL\ParameterType;
9use Doctrine\Deprecations\Deprecation;
10use IteratorAggregate;
11use PDO;
12use ReturnTypeWillChange;
13use Traversable;
14
15use function array_shift;
16use function func_get_args;
17use function method_exists;
18
19/**
20 * A wrapper around a Doctrine\DBAL\Driver\ResultStatement that adds 3.0 features
21 * defined in Result interface
22 */
23class Result implements IteratorAggregate, DriverStatement, DriverResultStatement
24{
25    /** @var Driver\ResultStatement */
26    private $stmt;
27
28    public static function ensure(Driver\ResultStatement $stmt): Result
29    {
30        if ($stmt instanceof Result) {
31            return $stmt;
32        }
33
34        return new Result($stmt);
35    }
36
37    public function __construct(Driver\ResultStatement $stmt)
38    {
39        $this->stmt = $stmt;
40    }
41
42    /**
43     * @return Driver\ResultStatement
44     */
45    #[ReturnTypeWillChange]
46    public function getIterator()
47    {
48        return $this->stmt;
49    }
50
51    /**
52     * {@inheritDoc}
53     *
54     * @deprecated Use Result::free() instead.
55     */
56    public function closeCursor()
57    {
58        return $this->stmt->closeCursor();
59    }
60
61    /**
62     * {@inheritDoc}
63     */
64    public function columnCount()
65    {
66        return $this->stmt->columnCount();
67    }
68
69    /**
70     * {@inheritDoc}
71     *
72     * @deprecated Use one of the fetch- or iterate-related methods.
73     */
74    public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
75    {
76        return $this->stmt->setFetchMode($fetchMode, $arg2, $arg3);
77    }
78
79    /**
80     * {@inheritDoc}
81     *
82     * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead.
83     */
84    public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
85    {
86        Deprecation::triggerIfCalledFromOutside(
87            'doctrine/dbal',
88            'https://github.com/doctrine/dbal/pull/4019',
89            'Result::fetch() is deprecated, use Result::fetchNumeric(), fetchAssociative() or fetchOne() instead.'
90        );
91
92        return $this->stmt->fetch(...func_get_args());
93    }
94
95    /**
96     * {@inheritDoc}
97     *
98     * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead.
99     */
100    public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
101    {
102        Deprecation::triggerIfCalledFromOutside(
103            'doctrine/dbal',
104            'https://github.com/doctrine/dbal/pull/4019',
105            'Result::fetchAll() is deprecated, use Result::fetchAllNumeric(), fetchAllAssociative() or ' .
106            'fetchFirstColumn() instead.'
107        );
108
109        return $this->stmt->fetchAll($fetchMode, $fetchArgument, $ctorArgs);
110    }
111
112    /**
113     * {@inheritDoc}
114     *
115     * @deprecated Use fetchOne() instead.
116     */
117    public function fetchColumn($columnIndex = 0)
118    {
119        Deprecation::triggerIfCalledFromOutside(
120            'doctrine/dbal',
121            'https://github.com/doctrine/dbal/pull/4019',
122            'Result::fetchColumn() is deprecated, use Result::fetchOne() instead.'
123        );
124
125        return $this->stmt->fetchColumn($columnIndex);
126    }
127
128    /**
129     * {@inheritDoc}
130     */
131    public function fetchNumeric()
132    {
133        return $this->stmt->fetch(PDO::FETCH_NUM);
134    }
135
136    /**
137     * {@inheritDoc}
138     */
139    public function fetchAssociative()
140    {
141        return $this->stmt->fetch(PDO::FETCH_ASSOC);
142    }
143
144    /**
145     * {@inheritDoc}
146     */
147    public function fetchOne()
148    {
149        $row = $this->fetchNumeric();
150
151        if ($row === false) {
152            return false;
153        }
154
155        return $row[0];
156    }
157
158    /**
159     * {@inheritDoc}
160     */
161    public function fetchAllNumeric(): array
162    {
163        $rows = [];
164
165        while (($row = $this->fetchNumeric()) !== false) {
166            $rows[] = $row;
167        }
168
169        return $rows;
170    }
171
172    /**
173     * {@inheritDoc}
174     */
175    public function fetchAllAssociative(): array
176    {
177        $rows = [];
178
179        while (($row = $this->fetchAssociative()) !== false) {
180            $rows[] = $row;
181        }
182
183        return $rows;
184    }
185
186    /**
187     * {@inheritDoc}
188     */
189    public function fetchAllKeyValue(): array
190    {
191        $this->ensureHasKeyValue();
192        $data = [];
193
194        foreach ($this->fetchAllNumeric() as [$key, $value]) {
195            $data[$key] = $value;
196        }
197
198        return $data;
199    }
200
201    /**
202     * {@inheritDoc}
203     */
204    public function fetchAllAssociativeIndexed(): array
205    {
206        $data = [];
207
208        foreach ($this->fetchAllAssociative() as $row) {
209            $data[array_shift($row)] = $row;
210        }
211
212        return $data;
213    }
214
215    /**
216     * {@inheritDoc}
217     */
218    public function fetchFirstColumn(): array
219    {
220        $rows = [];
221
222        while (($row = $this->fetchOne()) !== false) {
223            $rows[] = $row;
224        }
225
226        return $rows;
227    }
228
229    /**
230     * {@inheritdoc}
231     *
232     * @return Traversable<int,array<int,mixed>>
233     */
234    public function iterateNumeric(): Traversable
235    {
236        while (($row = $this->fetchNumeric()) !== false) {
237            yield $row;
238        }
239    }
240
241    /**
242     * {@inheritDoc}
243     *
244     * @return Traversable<int,array<string,mixed>>
245     */
246    public function iterateAssociative(): Traversable
247    {
248        while (($row = $this->fetchAssociative()) !== false) {
249            yield $row;
250        }
251    }
252
253    /**
254     * {@inheritDoc}
255     *
256     * @return Traversable<mixed,mixed>
257     */
258    public function iterateKeyValue(): Traversable
259    {
260        $this->ensureHasKeyValue();
261
262        foreach ($this->iterateNumeric() as [$key, $value]) {
263            yield $key => $value;
264        }
265    }
266
267    /**
268     * {@inheritDoc}
269     *
270     * @return Traversable<mixed,array<string,mixed>>
271     */
272    public function iterateAssociativeIndexed(): Traversable
273    {
274        foreach ($this->iterateAssociative() as $row) {
275            yield array_shift($row) => $row;
276        }
277    }
278
279    /**
280     * {@inheritDoc}
281     *
282     * @return Traversable<int,mixed>
283     */
284    public function iterateColumn(): Traversable
285    {
286        while (($value = $this->fetchOne()) !== false) {
287            yield $value;
288        }
289    }
290
291    /**
292     * {@inheritDoc}
293     */
294    public function rowCount()
295    {
296        if (method_exists($this->stmt, 'rowCount')) {
297            return $this->stmt->rowCount();
298        }
299
300        throw Exception::notSupported('rowCount');
301    }
302
303    public function free(): void
304    {
305        $this->closeCursor();
306    }
307
308    private function ensureHasKeyValue(): void
309    {
310        $columnCount = $this->columnCount();
311
312        if ($columnCount < 2) {
313            throw NoKeyValue::fromColumnCount($columnCount);
314        }
315    }
316
317    /**
318     * {@inheritDoc}
319     *
320     * @deprecated This feature will no longer be available on Result object in 3.0.x version.
321     */
322    public function bindValue($param, $value, $type = ParameterType::STRING)
323    {
324        Deprecation::triggerIfCalledFromOutside(
325            'doctrine/dbal',
326            'https://github.com/doctrine/dbal/pull/4019',
327            'Result::bindValue() is deprecated, no replacement.'
328        );
329
330        if ($this->stmt instanceof Driver\Statement) {
331            return $this->stmt->bindValue($param, $value, $type);
332        }
333
334        throw Exception::notSupported('bindValue');
335    }
336
337    /**
338     * {@inheritDoc}
339     *
340     * @deprecated This feature will no longer be available on Result object in 3.0.x version.
341     */
342    public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null)
343    {
344        Deprecation::triggerIfCalledFromOutside(
345            'doctrine/dbal',
346            'https://github.com/doctrine/dbal/pull/4019',
347            'Result::bindParam() is deprecated, no replacement.'
348        );
349
350        if ($this->stmt instanceof Driver\Statement) {
351            return $this->stmt->bindParam($param, $variable, $type, $length);
352        }
353
354        throw Exception::notSupported('bindParam');
355    }
356
357    /**
358     * {@inheritDoc}
359     *
360     * @deprecated The error information is available via exceptions.
361     */
362    public function errorCode()
363    {
364        Deprecation::triggerIfCalledFromOutside(
365            'doctrine/dbal',
366            'https://github.com/doctrine/dbal/pull/4019',
367            'Result::errorCode() is deprecated, the error information is available via exceptions.'
368        );
369
370        if ($this->stmt instanceof Driver\Statement) {
371            return $this->stmt->errorCode();
372        }
373
374        throw Exception::notSupported('errorCode');
375    }
376
377    /**
378     * {@inheritDoc}
379     *
380     * @deprecated The error information is available via exceptions.
381     */
382    public function errorInfo()
383    {
384        Deprecation::triggerIfCalledFromOutside(
385            'doctrine/dbal',
386            'https://github.com/doctrine/dbal/pull/4019',
387            'Result::errorInfo() is deprecated, the error information is available via exceptions.'
388        );
389
390        if ($this->stmt instanceof Driver\Statement) {
391            return $this->stmt->errorInfo();
392        }
393
394        throw Exception::notSupported('errorInfo');
395    }
396
397    /**
398     * {@inheritDoc}
399     *
400     * @deprecated This feature will no longer be available on Result object in 3.0.x version.
401     */
402    public function execute($params = null)
403    {
404        Deprecation::triggerIfCalledFromOutside(
405            'doctrine/dbal',
406            'https://github.com/doctrine/dbal/pull/4019',
407            'Result::execute() is deprecated, no replacement.'
408        );
409
410        if ($this->stmt instanceof Driver\Statement) {
411            return $this->stmt->execute($params);
412        }
413
414        throw Exception::notSupported('execute');
415    }
416}
417