1<?php
2
3namespace Doctrine\DBAL\Driver;
4
5use Doctrine\DBAL\Driver\PDO\Exception;
6use Doctrine\DBAL\Driver\Statement as StatementInterface;
7use Doctrine\DBAL\FetchMode;
8use Doctrine\DBAL\ParameterType;
9use Doctrine\Deprecations\Deprecation;
10use PDO;
11use PDOException;
12use ReturnTypeWillChange;
13
14use function array_slice;
15use function assert;
16use function func_get_args;
17use function is_array;
18
19/**
20 * The PDO implementation of the Statement interface.
21 * Used by all PDO-based drivers.
22 *
23 * @deprecated Use {@link Statement} instead
24 */
25class PDOStatement extends \PDOStatement implements StatementInterface, Result
26{
27    use PDOStatementImplementations;
28
29    private const PARAM_TYPE_MAP = [
30        ParameterType::NULL         => PDO::PARAM_NULL,
31        ParameterType::INTEGER      => PDO::PARAM_INT,
32        ParameterType::STRING       => PDO::PARAM_STR,
33        ParameterType::ASCII        => PDO::PARAM_STR,
34        ParameterType::BINARY       => PDO::PARAM_LOB,
35        ParameterType::LARGE_OBJECT => PDO::PARAM_LOB,
36        ParameterType::BOOLEAN      => PDO::PARAM_BOOL,
37    ];
38
39    private const FETCH_MODE_MAP = [
40        FetchMode::ASSOCIATIVE     => PDO::FETCH_ASSOC,
41        FetchMode::NUMERIC         => PDO::FETCH_NUM,
42        FetchMode::MIXED           => PDO::FETCH_BOTH,
43        FetchMode::STANDARD_OBJECT => PDO::FETCH_OBJ,
44        FetchMode::COLUMN          => PDO::FETCH_COLUMN,
45        FetchMode::CUSTOM_OBJECT   => PDO::FETCH_CLASS,
46    ];
47
48    /**
49     * Protected constructor.
50     *
51     * @internal The statement can be only instantiated by its driver connection.
52     */
53    protected function __construct()
54    {
55    }
56
57    /**
58     * {@inheritdoc}
59     */
60    #[ReturnTypeWillChange]
61    public function bindValue($param, $value, $type = ParameterType::STRING)
62    {
63        $type = $this->convertParamType($type);
64
65        try {
66            return parent::bindValue($param, $value, $type);
67        } catch (PDOException $exception) {
68            throw Exception::new($exception);
69        }
70    }
71
72    /**
73     * @param mixed    $param
74     * @param mixed    $variable
75     * @param int      $type
76     * @param int|null $length
77     * @param mixed    $driverOptions
78     *
79     * @return bool
80     */
81    #[ReturnTypeWillChange]
82    public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null, $driverOptions = null)
83    {
84        $type = $this->convertParamType($type);
85
86        try {
87            return parent::bindParam($param, $variable, $type, ...array_slice(func_get_args(), 3));
88        } catch (PDOException $exception) {
89            throw Exception::new($exception);
90        }
91    }
92
93    /**
94     * {@inheritdoc}
95     *
96     * @deprecated Use free() instead.
97     */
98    #[ReturnTypeWillChange]
99    public function closeCursor()
100    {
101        try {
102            return parent::closeCursor();
103        } catch (PDOException $exception) {
104            // Exceptions not allowed by the interface.
105            // In case driver implementations do not adhere to the interface, silence exceptions here.
106            return true;
107        }
108    }
109
110    /**
111     * {@inheritdoc}
112     */
113    #[ReturnTypeWillChange]
114    public function execute($params = null)
115    {
116        try {
117            return parent::execute($params);
118        } catch (PDOException $exception) {
119            throw Exception::new($exception);
120        }
121    }
122
123    /**
124     * {@inheritdoc}
125     *
126     * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead.
127     */
128    #[ReturnTypeWillChange]
129    public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
130    {
131        $args = func_get_args();
132
133        if (isset($args[0])) {
134            $args[0] = $this->convertFetchMode($args[0]);
135        }
136
137        try {
138            return parent::fetch(...$args);
139        } catch (PDOException $exception) {
140            throw Exception::new($exception);
141        }
142    }
143
144    /**
145     * {@inheritdoc}
146     *
147     * @deprecated Use fetchOne() instead.
148     */
149    #[ReturnTypeWillChange]
150    public function fetchColumn($columnIndex = 0)
151    {
152        try {
153            return parent::fetchColumn($columnIndex);
154        } catch (PDOException $exception) {
155            throw Exception::new($exception);
156        }
157    }
158
159    /**
160     * {@inheritdoc}
161     */
162    public function fetchNumeric()
163    {
164        return $this->fetch(PDO::FETCH_NUM);
165    }
166
167    /**
168     * {@inheritdoc}
169     */
170    public function fetchAssociative()
171    {
172        return $this->fetch(PDO::FETCH_ASSOC);
173    }
174
175    /**
176     * {@inheritdoc}
177     */
178    public function fetchOne()
179    {
180        return $this->fetch(PDO::FETCH_COLUMN);
181    }
182
183    /**
184     * {@inheritdoc}
185     */
186    public function fetchAllNumeric(): array
187    {
188        return $this->fetchAll(PDO::FETCH_NUM);
189    }
190
191    /**
192     * {@inheritdoc}
193     */
194    public function fetchAllAssociative(): array
195    {
196        return $this->fetchAll(PDO::FETCH_ASSOC);
197    }
198
199    /**
200     * {@inheritdoc}
201     */
202    public function fetchFirstColumn(): array
203    {
204        return $this->fetchAll(PDO::FETCH_COLUMN);
205    }
206
207    public function free(): void
208    {
209        parent::closeCursor();
210    }
211
212    /**
213     * @param mixed ...$args
214     */
215    private function doSetFetchMode(int $fetchMode, ...$args): bool
216    {
217        $fetchMode = $this->convertFetchMode($fetchMode);
218
219        // This thin wrapper is necessary to shield against the weird signature
220        // of PDOStatement::setFetchMode(): even if the second and third
221        // parameters are optional, PHP will not let us remove it from this
222        // declaration.
223        $slice = [];
224
225        foreach ($args as $arg) {
226            if ($arg === null) {
227                break;
228            }
229
230            $slice[] = $arg;
231        }
232
233        try {
234            return parent::setFetchMode($fetchMode, ...$slice);
235        } catch (PDOException $exception) {
236            throw Exception::new($exception);
237        }
238    }
239
240    /**
241     * @param mixed ...$args
242     *
243     * @return mixed[]
244     */
245    private function doFetchAll(...$args): array
246    {
247        if (isset($args[0])) {
248            $args[0] = $this->convertFetchMode($args[0]);
249        }
250
251        $slice = [];
252
253        foreach ($args as $arg) {
254            if ($arg === null) {
255                break;
256            }
257
258            $slice[] = $arg;
259        }
260
261        try {
262            $data = parent::fetchAll(...$slice);
263        } catch (PDOException $exception) {
264            throw Exception::new($exception);
265        }
266
267        assert(is_array($data));
268
269        return $data;
270    }
271
272    /**
273     * Converts DBAL parameter type to PDO parameter type
274     *
275     * @param int $type Parameter type
276     */
277    private function convertParamType(int $type): int
278    {
279        if (! isset(self::PARAM_TYPE_MAP[$type])) {
280            // TODO: next major: throw an exception
281            Deprecation::trigger(
282                'doctrine/dbal',
283                'https://github.com/doctrine/dbal/pull/3088',
284                'Using a PDO parameter type (%d given) is deprecated, ' .
285                'use \Doctrine\DBAL\Types\Types constants instead.',
286                $type
287            );
288
289            return $type;
290        }
291
292        return self::PARAM_TYPE_MAP[$type];
293    }
294
295    /**
296     * Converts DBAL fetch mode to PDO fetch mode
297     *
298     * @param int $fetchMode Fetch mode
299     */
300    private function convertFetchMode(int $fetchMode): int
301    {
302        if (! isset(self::FETCH_MODE_MAP[$fetchMode])) {
303            Deprecation::trigger(
304                'doctrine/dbal',
305                'https://github.com/doctrine/dbal/pull/3088',
306                'Using an unsupported PDO fetch mode or a bitmask of fetch modes (%d given)' .
307                ' is deprecated and will cause an error in Doctrine DBAL 3.0',
308                $fetchMode
309            );
310
311            return $fetchMode;
312        }
313
314        return self::FETCH_MODE_MAP[$fetchMode];
315    }
316}
317