diff options
Diffstat (limited to 'vendor/doctrine/dbal/src/Driver/Mysqli/Statement.php')
-rw-r--r-- | vendor/doctrine/dbal/src/Driver/Mysqli/Statement.php | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/vendor/doctrine/dbal/src/Driver/Mysqli/Statement.php b/vendor/doctrine/dbal/src/Driver/Mysqli/Statement.php new file mode 100644 index 0000000..8436fad --- /dev/null +++ b/vendor/doctrine/dbal/src/Driver/Mysqli/Statement.php | |||
@@ -0,0 +1,154 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Driver\Mysqli; | ||
6 | |||
7 | use Doctrine\DBAL\Driver\Exception; | ||
8 | use Doctrine\DBAL\Driver\Mysqli\Exception\FailedReadingStreamOffset; | ||
9 | use Doctrine\DBAL\Driver\Mysqli\Exception\NonStreamResourceUsedAsLargeObject; | ||
10 | use Doctrine\DBAL\Driver\Mysqli\Exception\StatementError; | ||
11 | use Doctrine\DBAL\Driver\Statement as StatementInterface; | ||
12 | use Doctrine\DBAL\ParameterType; | ||
13 | use mysqli_sql_exception; | ||
14 | use mysqli_stmt; | ||
15 | |||
16 | use function array_fill; | ||
17 | use function assert; | ||
18 | use function count; | ||
19 | use function feof; | ||
20 | use function fread; | ||
21 | use function get_resource_type; | ||
22 | use function is_int; | ||
23 | use function is_resource; | ||
24 | use function str_repeat; | ||
25 | |||
26 | final class Statement implements StatementInterface | ||
27 | { | ||
28 | private const PARAMETER_TYPE_STRING = 's'; | ||
29 | private const PARAMETER_TYPE_INTEGER = 'i'; | ||
30 | private const PARAMETER_TYPE_BINARY = 'b'; | ||
31 | |||
32 | /** @var mixed[] */ | ||
33 | private array $boundValues; | ||
34 | |||
35 | private string $types; | ||
36 | |||
37 | /** | ||
38 | * Contains ref values for bindValue(). | ||
39 | * | ||
40 | * @var mixed[] | ||
41 | */ | ||
42 | private array $values = []; | ||
43 | |||
44 | /** @internal The statement can be only instantiated by its driver connection. */ | ||
45 | public function __construct(private readonly mysqli_stmt $stmt) | ||
46 | { | ||
47 | $paramCount = $this->stmt->param_count; | ||
48 | $this->types = str_repeat(self::PARAMETER_TYPE_STRING, $paramCount); | ||
49 | $this->boundValues = array_fill(1, $paramCount, null); | ||
50 | } | ||
51 | |||
52 | public function bindValue(int|string $param, mixed $value, ParameterType $type): void | ||
53 | { | ||
54 | assert(is_int($param)); | ||
55 | |||
56 | $this->types[$param - 1] = $this->convertParameterType($type); | ||
57 | $this->values[$param] = $value; | ||
58 | $this->boundValues[$param] =& $this->values[$param]; | ||
59 | } | ||
60 | |||
61 | public function execute(): Result | ||
62 | { | ||
63 | if (count($this->boundValues) > 0) { | ||
64 | $this->bindParameters(); | ||
65 | } | ||
66 | |||
67 | try { | ||
68 | if (! $this->stmt->execute()) { | ||
69 | throw StatementError::new($this->stmt); | ||
70 | } | ||
71 | } catch (mysqli_sql_exception $e) { | ||
72 | throw StatementError::upcast($e); | ||
73 | } | ||
74 | |||
75 | return new Result($this->stmt); | ||
76 | } | ||
77 | |||
78 | /** | ||
79 | * Binds parameters with known types previously bound to the statement | ||
80 | * | ||
81 | * @throws Exception | ||
82 | */ | ||
83 | private function bindParameters(): void | ||
84 | { | ||
85 | $streams = $values = []; | ||
86 | $types = $this->types; | ||
87 | |||
88 | foreach ($this->boundValues as $parameter => $value) { | ||
89 | assert(is_int($parameter)); | ||
90 | if (! isset($types[$parameter - 1])) { | ||
91 | $types[$parameter - 1] = self::PARAMETER_TYPE_STRING; | ||
92 | } | ||
93 | |||
94 | if ($types[$parameter - 1] === self::PARAMETER_TYPE_BINARY) { | ||
95 | if (is_resource($value)) { | ||
96 | if (get_resource_type($value) !== 'stream') { | ||
97 | throw NonStreamResourceUsedAsLargeObject::new($parameter); | ||
98 | } | ||
99 | |||
100 | $streams[$parameter] = $value; | ||
101 | $values[$parameter] = null; | ||
102 | continue; | ||
103 | } | ||
104 | |||
105 | $types[$parameter - 1] = self::PARAMETER_TYPE_STRING; | ||
106 | } | ||
107 | |||
108 | $values[$parameter] = $value; | ||
109 | } | ||
110 | |||
111 | if (! $this->stmt->bind_param($types, ...$values)) { | ||
112 | throw StatementError::new($this->stmt); | ||
113 | } | ||
114 | |||
115 | $this->sendLongData($streams); | ||
116 | } | ||
117 | |||
118 | /** | ||
119 | * Handle $this->_longData after regular query parameters have been bound | ||
120 | * | ||
121 | * @param array<int, resource> $streams | ||
122 | * | ||
123 | * @throws Exception | ||
124 | */ | ||
125 | private function sendLongData(array $streams): void | ||
126 | { | ||
127 | foreach ($streams as $paramNr => $stream) { | ||
128 | while (! feof($stream)) { | ||
129 | $chunk = fread($stream, 8192); | ||
130 | |||
131 | if ($chunk === false) { | ||
132 | throw FailedReadingStreamOffset::new($paramNr); | ||
133 | } | ||
134 | |||
135 | if (! $this->stmt->send_long_data($paramNr - 1, $chunk)) { | ||
136 | throw StatementError::new($this->stmt); | ||
137 | } | ||
138 | } | ||
139 | } | ||
140 | } | ||
141 | |||
142 | private function convertParameterType(ParameterType $type): string | ||
143 | { | ||
144 | return match ($type) { | ||
145 | ParameterType::NULL, | ||
146 | ParameterType::STRING, | ||
147 | ParameterType::ASCII, | ||
148 | ParameterType::BINARY => self::PARAMETER_TYPE_STRING, | ||
149 | ParameterType::INTEGER, | ||
150 | ParameterType::BOOLEAN => self::PARAMETER_TYPE_INTEGER, | ||
151 | ParameterType::LARGE_OBJECT => self::PARAMETER_TYPE_BINARY, | ||
152 | }; | ||
153 | } | ||
154 | } | ||