MysqliStatement.php 5.85 KB
Newer Older
1 2 3 4
<?php

namespace Doctrine\DBAL\Driver\Mysqli;

5 6 7 8
use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionError;
use Doctrine\DBAL\Driver\Mysqli\Exception\FailedReadingStreamOffset;
use Doctrine\DBAL\Driver\Mysqli\Exception\StatementError;
use Doctrine\DBAL\Driver\Mysqli\Exception\UnknownType;
9
use Doctrine\DBAL\Driver\Result as ResultInterface;
10
use Doctrine\DBAL\Driver\Statement as StatementInterface;
11
use Doctrine\DBAL\Exception\InvalidArgumentException;
12
use Doctrine\DBAL\ParameterType;
13 14
use mysqli;
use mysqli_stmt;
15

16
use function array_fill;
17
use function assert;
18
use function count;
19 20 21
use function feof;
use function fread;
use function get_resource_type;
Sergei Morozov's avatar
Sergei Morozov committed
22
use function is_int;
23
use function is_resource;
24
use function str_repeat;
25

26 27 28 29
/**
 * @deprecated Use {@link Statement} instead
 */
class MysqliStatement implements StatementInterface
30
{
31
    /** @var string[] */
32
    protected static $_paramTypeMap = [
Sergei Morozov's avatar
Sergei Morozov committed
33
        ParameterType::STRING       => 's',
34
        ParameterType::BINARY       => 's',
Sergei Morozov's avatar
Sergei Morozov committed
35 36 37
        ParameterType::BOOLEAN      => 'i',
        ParameterType::NULL         => 's',
        ParameterType::INTEGER      => 'i',
38
        ParameterType::LARGE_OBJECT => 'b',
39
    ];
40

41
    /** @var mysqli */
42
    protected $_conn;
Benjamin Morel's avatar
Benjamin Morel committed
43

44
    /** @var mysqli_stmt */
45
    protected $_stmt;
46

47
    /** @var mixed[] */
48
    protected $_bindedValues;
49

50
    /** @var string */
51 52
    protected $types;

53
    /**
Benjamin Morel's avatar
Benjamin Morel committed
54
     * Contains ref values for bindValue().
55
     *
56
     * @var mixed[]
57
     */
58
    protected $_values = [];
59

Benjamin Morel's avatar
Benjamin Morel committed
60
    /**
61
     * @param string $prepareString
Benjamin Morel's avatar
Benjamin Morel committed
62
     *
63
     * @throws MysqliException
Benjamin Morel's avatar
Benjamin Morel committed
64
     */
65
    public function __construct(mysqli $conn, $prepareString)
66 67
    {
        $this->_conn = $conn;
Sergei Morozov's avatar
Sergei Morozov committed
68 69 70 71

        $stmt = $conn->prepare($prepareString);

        if ($stmt === false) {
72
            throw ConnectionError::new($this->_conn);
73 74
        }

Sergei Morozov's avatar
Sergei Morozov committed
75 76
        $this->_stmt = $stmt;

77
        $paramCount = $this->_stmt->param_count;
78 79
        if (0 >= $paramCount) {
            return;
80
        }
81 82 83

        $this->types         = str_repeat('s', $paramCount);
        $this->_bindedValues = array_fill(1, $paramCount, null);
84 85 86 87 88
    }

    /**
     * {@inheritdoc}
     */
89
    public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
90
    {
Sergei Morozov's avatar
Sergei Morozov committed
91 92
        assert(is_int($column));

Sergei Morozov's avatar
Sergei Morozov committed
93
        if (! isset(self::$_paramTypeMap[$type])) {
94
            throw UnknownType::new($type);
95 96 97
        }

        $this->_bindedValues[$column] =& $variable;
Sergei Morozov's avatar
Sergei Morozov committed
98
        $this->types[$column - 1]     = self::$_paramTypeMap[$type];
Benjamin Morel's avatar
Benjamin Morel committed
99

100 101 102 103 104 105
        return true;
    }

    /**
     * {@inheritdoc}
     */
106
    public function bindValue($param, $value, $type = ParameterType::STRING)
107
    {
Sergei Morozov's avatar
Sergei Morozov committed
108 109
        assert(is_int($param));

Sergei Morozov's avatar
Sergei Morozov committed
110
        if (! isset(self::$_paramTypeMap[$type])) {
111
            throw UnknownType::new($type);
112 113
        }

114
        $this->_values[$param]       = $value;
115
        $this->_bindedValues[$param] =& $this->_values[$param];
Sergei Morozov's avatar
Sergei Morozov committed
116
        $this->types[$param - 1]     = self::$_paramTypeMap[$type];
Benjamin Morel's avatar
Benjamin Morel committed
117

118 119 120 121 122 123
        return true;
    }

    /**
     * {@inheritdoc}
     */
124
    public function execute($params = null): ResultInterface
125
    {
126 127
        if ($this->_bindedValues !== null) {
            if ($params !== null) {
Sergei Morozov's avatar
Sergei Morozov committed
128
                if (! $this->bindUntypedValues($params)) {
129
                    throw StatementError::new($this->_stmt);
130 131
                }
            } else {
Sergei Morozov's avatar
Sergei Morozov committed
132
                $this->bindTypedParameters();
133 134 135
            }
        }

136
        if (! $this->_stmt->execute()) {
137
            throw StatementError::new($this->_stmt);
138 139
        }

140
        return new Result($this->_stmt);
141 142
    }

143
    /**
Sergei Morozov's avatar
Sergei Morozov committed
144
     * Binds parameters with known types previously bound to the statement
145
     */
146
    private function bindTypedParameters(): void
147 148 149 150 151
    {
        $streams = $values = [];
        $types   = $this->types;

        foreach ($this->_bindedValues as $parameter => $value) {
152 153
            assert(is_int($parameter));

154 155 156 157 158 159 160 161 162
            if (! isset($types[$parameter - 1])) {
                $types[$parameter - 1] = static::$_paramTypeMap[ParameterType::STRING];
            }

            if ($types[$parameter - 1] === static::$_paramTypeMap[ParameterType::LARGE_OBJECT]) {
                if (is_resource($value)) {
                    if (get_resource_type($value) !== 'stream') {
                        throw new InvalidArgumentException('Resources passed with the LARGE_OBJECT parameter type must be stream resources.');
                    }
Grégoire Paris's avatar
Grégoire Paris committed
163

164 165 166 167
                    $streams[$parameter] = $value;
                    $values[$parameter]  = null;
                    continue;
                }
168 169

                $types[$parameter - 1] = static::$_paramTypeMap[ParameterType::STRING];
170 171 172 173 174
            }

            $values[$parameter] = $value;
        }

Sergei Morozov's avatar
Sergei Morozov committed
175
        if (! $this->_stmt->bind_param($types, ...$values)) {
176
            throw StatementError::new($this->_stmt);
Sergei Morozov's avatar
Sergei Morozov committed
177 178 179
        }

        $this->sendLongData($streams);
180 181 182 183 184
    }

    /**
     * Handle $this->_longData after regular query parameters have been bound
     *
185 186
     * @param array<int, resource> $streams
     *
187 188
     * @throws MysqliException
     */
189
    private function sendLongData(array $streams): void
190 191 192 193 194 195
    {
        foreach ($streams as $paramNr => $stream) {
            while (! feof($stream)) {
                $chunk = fread($stream, 8192);

                if ($chunk === false) {
196
                    throw FailedReadingStreamOffset::new($paramNr);
197 198 199
                }

                if (! $this->_stmt->send_long_data($paramNr - 1, $chunk)) {
200
                    throw StatementError::new($this->_stmt);
201 202 203 204 205
                }
            }
        }
    }

206
    /**
Benjamin Morel's avatar
Benjamin Morel committed
207
     * Binds a array of values to bound parameters.
208
     *
209
     * @param mixed[] $values
Benjamin Morel's avatar
Benjamin Morel committed
210
     *
211
     * @return bool
212
     */
Sergei Morozov's avatar
Sergei Morozov committed
213
    private function bindUntypedValues(array $values)
214
    {
215
        $params = [];
216
        $types  = str_repeat('s', count($values));
217 218 219 220

        foreach ($values as &$v) {
            $params[] =& $v;
        }
Benjamin Morel's avatar
Benjamin Morel committed
221

222
        return $this->_stmt->bind_param($types, ...$params);
223 224
    }
}