Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
100.00% |
109 / 109 |
|
98.81% |
83 / 84 |
|
43.75% |
28 / 64 |
|
94.74% |
18 / 19 |
CRAP | |
0.00% |
0 / 1 |
| Query | |
100.00% |
109 / 109 |
|
98.81% |
83 / 84 |
|
43.75% |
28 / 64 |
|
100.00% |
19 / 19 |
372.08 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
5 / 5 |
|
100.00% |
5 / 5 |
|
75.00% |
3 / 4 |
|
100.00% |
1 / 1 |
3.14 | |||
| __toString | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| one | |
100.00% |
9 / 9 |
|
100.00% |
7 / 7 |
|
25.00% |
1 / 4 |
|
100.00% |
1 / 1 |
6.80 | |||
| first | |
100.00% |
5 / 5 |
|
100.00% |
6 / 6 |
|
0.00% |
0 / 4 |
|
100.00% |
1 / 1 |
2 | |||
| fetch | |
100.00% |
4 / 4 |
|
100.00% |
4 / 4 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| all | |
100.00% |
12 / 12 |
|
100.00% |
9 / 9 |
|
0.00% |
0 / 8 |
|
100.00% |
1 / 1 |
3 | |||
| lazy | |
100.00% |
9 / 9 |
|
88.89% |
8 / 9 |
|
0.00% |
0 / 6 |
|
100.00% |
1 / 1 |
3 | |||
| terminalOptions | |
100.00% |
4 / 4 |
|
100.00% |
8 / 8 |
|
25.00% |
2 / 8 |
|
100.00% |
1 / 1 |
10.75 | |||
| executeFresh | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| executeForFetch | |
100.00% |
5 / 5 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| hydrateRecord | |
100.00% |
4 / 4 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| hydrator | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| run | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| len | |
100.00% |
3 / 3 |
|
100.00% |
3 / 3 |
|
0.00% |
0 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| interpolate | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| bindArgs | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
| bindValue | |
100.00% |
25 / 25 |
|
100.00% |
17 / 17 |
|
53.85% |
7 / 13 |
|
100.00% |
1 / 1 |
14.29 | |||
| fetchArrayRecord | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| nullIfNot | |
100.00% |
3 / 3 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Celemas\Quma; |
| 6 | |
| 7 | use Celemas\Quma\Exception\UnexpectedResultCount; |
| 8 | use Celemas\Quma\Hydration\Hydrator; |
| 9 | use Closure; |
| 10 | use Generator; |
| 11 | use InvalidArgumentException; |
| 12 | use JsonException; |
| 13 | use PDO; |
| 14 | use PDOStatement; |
| 15 | |
| 16 | /** @api */ |
| 17 | class Query |
| 18 | { |
| 19 | // Matches multi line single and double quotes and handles \' \" escapes |
| 20 | public const string PATTERN_STRING = '/([\'"])(?:\\\1|[\s\S])*?\1/'; |
| 21 | |
| 22 | // PostgreSQL blocks delimited with $$ |
| 23 | public const string PATTERN_BLOCK = '/(\$\$)[\s\S]*?\1/'; |
| 24 | |
| 25 | // Multi line comments /* */ |
| 26 | public const string PATTERN_COMMENT_MULTI = '/\/\*([\s\S]*?)\*\//'; |
| 27 | |
| 28 | // Single line comments -- |
| 29 | public const string PATTERN_COMMENT_SINGLE = '/--.*$/m'; |
| 30 | |
| 31 | protected PDOStatement $stmt; |
| 32 | protected bool $executed = false; |
| 33 | protected ?Hydrator $hydrator = null; |
| 34 | |
| 35 | public function __construct( |
| 36 | protected Database $db, |
| 37 | protected string $query, |
| 38 | protected Args $args, |
| 39 | protected ?string $sourcePath = null, |
| 40 | ) { |
| 41 | $this->stmt = $this->db->getConn()->prepare($query); |
| 42 | |
| 43 | if ($args->count() > 0) { |
| 44 | $this->bindArgs($args->get(), $args->type()); |
| 45 | } |
| 46 | |
| 47 | if ($this->db->debug) { |
| 48 | Debug::query($this->db, $this->query, $this->args, $this->sourcePath); |
| 49 | } |
| 50 | } |
| 51 | |
| 52 | public function __toString(): string |
| 53 | { |
| 54 | return $this->interpolate(); |
| 55 | } |
| 56 | |
| 57 | /** |
| 58 | * @template T of object |
| 59 | * |
| 60 | * @param class-string<T>|Closure(array<string, mixed>):class-string<T>|null $map |
| 61 | * @return ($map is null ? array<array-key, mixed> : T) |
| 62 | */ |
| 63 | public function one(string|Closure|null $map = null, ?int $fetchMode = null): array|object |
| 64 | { |
| 65 | [$map, $fetchMode] = $this->terminalOptions($map, $fetchMode); |
| 66 | $this->executeFresh(); |
| 67 | |
| 68 | try { |
| 69 | $record = $this->fetchArrayRecord($fetchMode); |
| 70 | |
| 71 | if ($record === null) { |
| 72 | throw UnexpectedResultCount::none(); |
| 73 | } |
| 74 | |
| 75 | if ($this->fetchArrayRecord($fetchMode) !== null) { |
| 76 | throw UnexpectedResultCount::multiple(); |
| 77 | } |
| 78 | |
| 79 | return $this->hydrateRecord($record, $map); |
| 80 | } finally { |
| 81 | $this->stmt->closeCursor(); |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | /** |
| 86 | * @template T of object |
| 87 | * |
| 88 | * @param class-string<T>|Closure(array<string, mixed>):class-string<T>|null $map |
| 89 | * @return ($map is null ? array<array-key, mixed>|null : T|null) |
| 90 | */ |
| 91 | public function first(string|Closure|null $map = null, ?int $fetchMode = null): array|object|null |
| 92 | { |
| 93 | [$map, $fetchMode] = $this->terminalOptions($map, $fetchMode); |
| 94 | $this->executeFresh(); |
| 95 | |
| 96 | try { |
| 97 | $record = $this->fetchArrayRecord($fetchMode); |
| 98 | |
| 99 | return $record === null ? null : $this->hydrateRecord($record, $map); |
| 100 | } finally { |
| 101 | $this->stmt->closeCursor(); |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | /** |
| 106 | * @template T of object |
| 107 | * |
| 108 | * @param class-string<T>|Closure(array<string, mixed>):class-string<T>|null $map |
| 109 | * @return ($map is null ? array<array-key, mixed>|null : T|null) |
| 110 | */ |
| 111 | public function fetch(string|Closure|null $map = null, ?int $fetchMode = null): array|object|null |
| 112 | { |
| 113 | [$map, $fetchMode] = $this->terminalOptions($map, $fetchMode); |
| 114 | $this->executeForFetch(); |
| 115 | |
| 116 | $record = $this->fetchArrayRecord($fetchMode); |
| 117 | |
| 118 | return $record === null ? null : $this->hydrateRecord($record, $map); |
| 119 | } |
| 120 | |
| 121 | /** |
| 122 | * @template T of object |
| 123 | * |
| 124 | * @param class-string<T>|Closure(array<string, mixed>):class-string<T>|null $map |
| 125 | * @return ($map is null ? list<array<array-key, mixed>> : list<T>) |
| 126 | */ |
| 127 | public function all(string|Closure|null $map = null, ?int $fetchMode = null): array |
| 128 | { |
| 129 | [$map, $fetchMode] = $this->terminalOptions($map, $fetchMode); |
| 130 | $this->executeFresh(); |
| 131 | |
| 132 | try { |
| 133 | if ($map === null) { |
| 134 | /** |
| 135 | * @mago-expect lint:inline-variable-return Psalm makes this necessary |
| 136 | * @var list<array<array-key, mixed>> $records |
| 137 | */ |
| 138 | $records = $this->stmt->fetchAll($fetchMode); |
| 139 | |
| 140 | return $records; |
| 141 | } |
| 142 | |
| 143 | /** @var list<T> $result */ |
| 144 | $result = []; |
| 145 | /** @var list<array<array-key, mixed>> $records */ |
| 146 | $records = $this->stmt->fetchAll($fetchMode); |
| 147 | |
| 148 | foreach ($records as $record) { |
| 149 | /** @var T $object */ |
| 150 | $object = $this->hydrator()->hydrate($record, $map, $this->sourcePath); |
| 151 | $result[] = $object; |
| 152 | } |
| 153 | |
| 154 | return $result; |
| 155 | } finally { |
| 156 | $this->stmt->closeCursor(); |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | /** |
| 161 | * @template T of object |
| 162 | * |
| 163 | * @param class-string<T>|Closure(array<string, mixed>):class-string<T>|null $map |
| 164 | * @return ($map is null |
| 165 | * ? Generator<int, array<array-key, mixed>, mixed, void> |
| 166 | * : Generator<int, T, mixed, void>) |
| 167 | */ |
| 168 | public function lazy(string|Closure|null $map = null, ?int $fetchMode = null): Generator |
| 169 | { |
| 170 | [$map, $fetchMode] = $this->terminalOptions($map, $fetchMode); |
| 171 | $this->executeFresh(); |
| 172 | |
| 173 | try { |
| 174 | while (($record = $this->fetchArrayRecord($fetchMode)) !== null) { |
| 175 | if ($map === null) { |
| 176 | yield $record; |
| 177 | |
| 178 | continue; |
| 179 | } |
| 180 | |
| 181 | /** @var T $object */ |
| 182 | $object = $this->hydrator()->hydrate($record, $map, $this->sourcePath); |
| 183 | |
| 184 | yield $object; |
| 185 | } |
| 186 | } finally { |
| 187 | $this->stmt->closeCursor(); |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | /** |
| 192 | * @return array{0: string|Closure|null, 1: int} |
| 193 | */ |
| 194 | private function terminalOptions(string|Closure|null $map, ?int $fetchMode): array |
| 195 | { |
| 196 | $mode = $fetchMode ?? ($map === null ? $this->db->getFetchMode() : PDO::FETCH_ASSOC); |
| 197 | |
| 198 | if ($map !== null && $mode !== PDO::FETCH_ASSOC) { |
| 199 | throw new InvalidArgumentException('Hydration requires PDO::FETCH_ASSOC.'); |
| 200 | } |
| 201 | |
| 202 | return [$map, $mode]; |
| 203 | } |
| 204 | |
| 205 | private function executeFresh(): void |
| 206 | { |
| 207 | $this->db->connect(); |
| 208 | $this->stmt->closeCursor(); |
| 209 | $this->stmt->execute(); |
| 210 | $this->executed = false; |
| 211 | } |
| 212 | |
| 213 | private function executeForFetch(): void |
| 214 | { |
| 215 | $this->db->connect(); |
| 216 | |
| 217 | if (!$this->executed) { |
| 218 | $this->stmt->closeCursor(); |
| 219 | $this->stmt->execute(); |
| 220 | $this->executed = true; |
| 221 | } |
| 222 | } |
| 223 | |
| 224 | /** |
| 225 | * @template T of object |
| 226 | * |
| 227 | * @param array<array-key, mixed> $record |
| 228 | * @param string|Closure(array<string, mixed>):class-string<T>|null $map |
| 229 | * @return ($map is null ? array<array-key, mixed> : T) |
| 230 | */ |
| 231 | private function hydrateRecord(array $record, string|Closure|null $map): array|object |
| 232 | { |
| 233 | if ($map === null) { |
| 234 | return $record; |
| 235 | } |
| 236 | |
| 237 | /** |
| 238 | * @mago-expect lint:inline-variable-return Psalm makes this necessary |
| 239 | * @var T $object |
| 240 | */ |
| 241 | $object = $this->hydrator()->hydrate($record, $map, $this->sourcePath); |
| 242 | |
| 243 | return $object; |
| 244 | } |
| 245 | |
| 246 | private function hydrator(): Hydrator |
| 247 | { |
| 248 | return $this->hydrator ??= Hydrator::default(); |
| 249 | } |
| 250 | |
| 251 | public function run(): bool |
| 252 | { |
| 253 | $this->db->connect(); |
| 254 | $this->stmt->closeCursor(); |
| 255 | $this->executed = false; |
| 256 | |
| 257 | return $this->stmt->execute(); |
| 258 | } |
| 259 | |
| 260 | public function len(): int |
| 261 | { |
| 262 | $this->executeFresh(); |
| 263 | |
| 264 | try { |
| 265 | return $this->stmt->rowCount(); |
| 266 | } finally { |
| 267 | $this->stmt->closeCursor(); |
| 268 | } |
| 269 | } |
| 270 | |
| 271 | /** |
| 272 | * For debugging purposes only. |
| 273 | * |
| 274 | * Replaces any parameter placeholders in a query with the |
| 275 | * value of that parameter and returns the query as string. |
| 276 | * |
| 277 | * Covers most of the cases but is not perfect. |
| 278 | */ |
| 279 | public function interpolate(): string |
| 280 | { |
| 281 | return Debug::interpolate($this->query, $this->args); |
| 282 | } |
| 283 | |
| 284 | protected function bindArgs(array $args, ArgType $argType): void |
| 285 | { |
| 286 | array_walk( |
| 287 | $args, |
| 288 | function (mixed $value, int|string $index) use ($argType): void { |
| 289 | if ($argType === ArgType::Named) { |
| 290 | $arg = ':' . $index; |
| 291 | } else { |
| 292 | $arg = (int) $index + 1; // question mark placeholders are 1-indexed |
| 293 | } |
| 294 | |
| 295 | $this->bindValue($arg, $value); |
| 296 | }, |
| 297 | ); |
| 298 | } |
| 299 | |
| 300 | protected function bindValue(string|int $arg, mixed $value): void |
| 301 | { |
| 302 | switch (gettype($value)) { |
| 303 | case 'boolean': |
| 304 | $this->stmt->bindValue($arg, $value, PDO::PARAM_BOOL); |
| 305 | |
| 306 | break; |
| 307 | |
| 308 | case 'integer': |
| 309 | $this->stmt->bindValue($arg, $value, PDO::PARAM_INT); |
| 310 | |
| 311 | break; |
| 312 | |
| 313 | case 'string': |
| 314 | $this->stmt->bindValue($arg, $value, PDO::PARAM_STR); |
| 315 | |
| 316 | break; |
| 317 | |
| 318 | case 'NULL': |
| 319 | $this->stmt->bindValue($arg, $value, PDO::PARAM_NULL); |
| 320 | |
| 321 | break; |
| 322 | |
| 323 | case 'array': |
| 324 | try { |
| 325 | $json = json_encode($value, JSON_THROW_ON_ERROR); |
| 326 | } catch (JsonException $e) { |
| 327 | throw new InvalidArgumentException( |
| 328 | 'Array parameters must be JSON-encodable.', |
| 329 | previous: $e, |
| 330 | ); |
| 331 | } |
| 332 | |
| 333 | $this->stmt->bindValue($arg, $json, PDO::PARAM_STR); |
| 334 | |
| 335 | break; |
| 336 | |
| 337 | default: |
| 338 | throw new InvalidArgumentException( |
| 339 | 'Only the types bool, int, string, null and array are supported', |
| 340 | ); |
| 341 | } |
| 342 | } |
| 343 | |
| 344 | protected function fetchArrayRecord(int $fetchMode): ?array |
| 345 | { |
| 346 | return $this->nullIfNot($this->stmt->fetch($fetchMode)); |
| 347 | } |
| 348 | |
| 349 | protected function nullIfNot(mixed $value): ?array |
| 350 | { |
| 351 | if (is_array($value)) { |
| 352 | return $value; |
| 353 | } |
| 354 | |
| 355 | return null; |
| 356 | } |
| 357 | } |
Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not
necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once.
Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement
always has an else as part of its logical flow even if you didn't write one.
| 36 | protected Database $db, |
| 37 | protected string $query, |
| 38 | protected Args $args, |
| 39 | protected ?string $sourcePath = null, |
| 40 | ) { |
| 41 | $this->stmt = $this->db->getConn()->prepare($query); |
| 42 | |
| 43 | if ($args->count() > 0) { |
| 44 | $this->bindArgs($args->get(), $args->type()); |
| 45 | } |
| 46 | |
| 47 | if ($this->db->debug) { |
| 47 | if ($this->db->debug) { |
| 48 | Debug::query($this->db, $this->query, $this->args, $this->sourcePath); |
| 49 | } |
| 50 | } |
| 50 | } |
| 54 | return $this->interpolate(); |
| 55 | } |
| 127 | public function all(string|Closure|null $map = null, ?int $fetchMode = null): array |
| 128 | { |
| 129 | [$map, $fetchMode] = $this->terminalOptions($map, $fetchMode); |
| 130 | $this->executeFresh(); |
| 131 | |
| 132 | try { |
| 133 | if ($map === null) { |
| 138 | $records = $this->stmt->fetchAll($fetchMode); |
| 139 | |
| 140 | return $records; |
| 140 | return $records; |
| 144 | $result = []; |
| 145 | /** @var list<array<array-key, mixed>> $records */ |
| 146 | $records = $this->stmt->fetchAll($fetchMode); |
| 147 | |
| 148 | foreach ($records as $record) { |
| 148 | foreach ($records as $record) { |
| 148 | foreach ($records as $record) { |
| 149 | /** @var T $object */ |
| 150 | $object = $this->hydrator()->hydrate($record, $map, $this->sourcePath); |
| 148 | foreach ($records as $record) { |
| 149 | /** @var T $object */ |
| 150 | $object = $this->hydrator()->hydrate($record, $map, $this->sourcePath); |
| 151 | $result[] = $object; |
| 152 | } |
| 153 | |
| 154 | return $result; |
| 154 | return $result; |
| 156 | $this->stmt->closeCursor(); |
| 157 | } |
| 158 | } |
| 284 | protected function bindArgs(array $args, ArgType $argType): void |
| 285 | { |
| 286 | array_walk( |
| 287 | $args, |
| 288 | function (mixed $value, int|string $index) use ($argType): void { |
| 289 | if ($argType === ArgType::Named) { |
| 290 | $arg = ':' . $index; |
| 291 | } else { |
| 292 | $arg = (int) $index + 1; // question mark placeholders are 1-indexed |
| 293 | } |
| 294 | |
| 295 | $this->bindValue($arg, $value); |
| 296 | }, |
| 297 | ); |
| 298 | } |
| 300 | protected function bindValue(string|int $arg, mixed $value): void |
| 301 | { |
| 302 | switch (gettype($value)) { |
| 303 | case 'boolean': |
| 308 | case 'integer': |
| 313 | case 'string': |
| 318 | case 'NULL': |
| 323 | case 'array': |
| 323 | case 'array': |
| 304 | $this->stmt->bindValue($arg, $value, PDO::PARAM_BOOL); |
| 305 | |
| 306 | break; |
| 309 | $this->stmt->bindValue($arg, $value, PDO::PARAM_INT); |
| 310 | |
| 311 | break; |
| 314 | $this->stmt->bindValue($arg, $value, PDO::PARAM_STR); |
| 315 | |
| 316 | break; |
| 319 | $this->stmt->bindValue($arg, $value, PDO::PARAM_NULL); |
| 320 | |
| 321 | break; |
| 324 | try { |
| 325 | $json = json_encode($value, JSON_THROW_ON_ERROR); |
| 326 | } catch (JsonException $e) { |
| 327 | throw new InvalidArgumentException( |
| 328 | 'Array parameters must be JSON-encodable.', |
| 333 | $this->stmt->bindValue($arg, $json, PDO::PARAM_STR); |
| 334 | |
| 335 | break; |
| 338 | throw new InvalidArgumentException( |
| 339 | 'Only the types bool, int, string, null and array are supported', |
| 339 | 'Only the types bool, int, string, null and array are supported', |
| 340 | ); |
| 341 | } |
| 342 | } |
| 215 | $this->db->connect(); |
| 216 | |
| 217 | if (!$this->executed) { |
| 218 | $this->stmt->closeCursor(); |
| 219 | $this->stmt->execute(); |
| 220 | $this->executed = true; |
| 221 | } |
| 222 | } |
| 222 | } |
| 207 | $this->db->connect(); |
| 208 | $this->stmt->closeCursor(); |
| 209 | $this->stmt->execute(); |
| 210 | $this->executed = false; |
| 211 | } |
| 111 | public function fetch(string|Closure|null $map = null, ?int $fetchMode = null): array|object|null |
| 112 | { |
| 113 | [$map, $fetchMode] = $this->terminalOptions($map, $fetchMode); |
| 114 | $this->executeForFetch(); |
| 115 | |
| 116 | $record = $this->fetchArrayRecord($fetchMode); |
| 117 | |
| 118 | return $record === null ? null : $this->hydrateRecord($record, $map); |
| 118 | return $record === null ? null : $this->hydrateRecord($record, $map); |
| 118 | return $record === null ? null : $this->hydrateRecord($record, $map); |
| 118 | return $record === null ? null : $this->hydrateRecord($record, $map); |
| 119 | } |
| 344 | protected function fetchArrayRecord(int $fetchMode): ?array |
| 345 | { |
| 346 | return $this->nullIfNot($this->stmt->fetch($fetchMode)); |
| 347 | } |
| 91 | public function first(string|Closure|null $map = null, ?int $fetchMode = null): array|object|null |
| 92 | { |
| 93 | [$map, $fetchMode] = $this->terminalOptions($map, $fetchMode); |
| 94 | $this->executeFresh(); |
| 95 | |
| 96 | try { |
| 97 | $record = $this->fetchArrayRecord($fetchMode); |
| 98 | |
| 99 | return $record === null ? null : $this->hydrateRecord($record, $map); |
| 99 | return $record === null ? null : $this->hydrateRecord($record, $map); |
| 99 | return $record === null ? null : $this->hydrateRecord($record, $map); |
| 99 | return $record === null ? null : $this->hydrateRecord($record, $map); |
| 99 | return $record === null ? null : $this->hydrateRecord($record, $map); |
| 101 | $this->stmt->closeCursor(); |
| 102 | } |
| 103 | } |
| 231 | private function hydrateRecord(array $record, string|Closure|null $map): array|object |
| 232 | { |
| 233 | if ($map === null) { |
| 234 | return $record; |
| 241 | $object = $this->hydrator()->hydrate($record, $map, $this->sourcePath); |
| 242 | |
| 243 | return $object; |
| 244 | } |
| 248 | return $this->hydrator ??= Hydrator::default(); |
| 249 | } |
| 281 | return Debug::interpolate($this->query, $this->args); |
| 282 | } |
| 168 | public function lazy(string|Closure|null $map = null, ?int $fetchMode = null): Generator |
| 169 | { |
| 170 | [$map, $fetchMode] = $this->terminalOptions($map, $fetchMode); |
| 171 | $this->executeFresh(); |
| 172 | |
| 173 | try { |
| 174 | while (($record = $this->fetchArrayRecord($fetchMode)) !== null) { |
| 175 | if ($map === null) { |
| 176 | yield $record; |
| 177 | |
| 178 | continue; |
| 174 | while (($record = $this->fetchArrayRecord($fetchMode)) !== null) { |
| 175 | if ($map === null) { |
| 176 | yield $record; |
| 177 | |
| 178 | continue; |
| 179 | } |
| 180 | |
| 181 | /** @var T $object */ |
| 182 | $object = $this->hydrator()->hydrate($record, $map, $this->sourcePath); |
| 174 | while (($record = $this->fetchArrayRecord($fetchMode)) !== null) { |
| 186 | } finally { |
| 186 | } finally { |
| 187 | $this->stmt->closeCursor(); |
| 189 | } |
| 262 | $this->executeFresh(); |
| 263 | |
| 264 | try { |
| 265 | return $this->stmt->rowCount(); |
| 265 | return $this->stmt->rowCount(); |
| 267 | $this->stmt->closeCursor(); |
| 268 | } |
| 269 | } |
| 349 | protected function nullIfNot(mixed $value): ?array |
| 350 | { |
| 351 | if (is_array($value)) { |
| 352 | return $value; |
| 355 | return null; |
| 356 | } |
| 63 | public function one(string|Closure|null $map = null, ?int $fetchMode = null): array|object |
| 64 | { |
| 65 | [$map, $fetchMode] = $this->terminalOptions($map, $fetchMode); |
| 66 | $this->executeFresh(); |
| 67 | |
| 68 | try { |
| 69 | $record = $this->fetchArrayRecord($fetchMode); |
| 70 | |
| 71 | if ($record === null) { |
| 72 | throw UnexpectedResultCount::none(); |
| 75 | if ($this->fetchArrayRecord($fetchMode) !== null) { |
| 76 | throw UnexpectedResultCount::multiple(); |
| 79 | return $this->hydrateRecord($record, $map); |
| 79 | return $this->hydrateRecord($record, $map); |
| 81 | $this->stmt->closeCursor(); |
| 82 | } |
| 83 | } |
| 253 | $this->db->connect(); |
| 254 | $this->stmt->closeCursor(); |
| 255 | $this->executed = false; |
| 256 | |
| 257 | return $this->stmt->execute(); |
| 258 | } |
| 194 | private function terminalOptions(string|Closure|null $map, ?int $fetchMode): array |
| 195 | { |
| 196 | $mode = $fetchMode ?? ($map === null ? $this->db->getFetchMode() : PDO::FETCH_ASSOC); |
| 196 | $mode = $fetchMode ?? ($map === null ? $this->db->getFetchMode() : PDO::FETCH_ASSOC); |
| 196 | $mode = $fetchMode ?? ($map === null ? $this->db->getFetchMode() : PDO::FETCH_ASSOC); |
| 196 | $mode = $fetchMode ?? ($map === null ? $this->db->getFetchMode() : PDO::FETCH_ASSOC); |
| 197 | |
| 198 | if ($map !== null && $mode !== PDO::FETCH_ASSOC) { |
| 198 | if ($map !== null && $mode !== PDO::FETCH_ASSOC) { |
| 198 | if ($map !== null && $mode !== PDO::FETCH_ASSOC) { |
| 199 | throw new InvalidArgumentException('Hydration requires PDO::FETCH_ASSOC.'); |
| 202 | return [$map, $mode]; |
| 203 | } |
| 288 | function (mixed $value, int|string $index) use ($argType): void { |
| 289 | if ($argType === ArgType::Named) { |
| 289 | if ($argType === ArgType::Named) { |
| 290 | $arg = ':' . $index; |
| 292 | $arg = (int) $index + 1; // question mark placeholders are 1-indexed |
| 293 | } |
| 294 | |
| 295 | $this->bindValue($arg, $value); |
| 295 | $this->bindValue($arg, $value); |
| 296 | }, |