1<?php 2 3namespace Doctrine\DBAL\Driver\IBMDB2; 4 5use Doctrine\DBAL\Driver\FetchUtils; 6use Doctrine\DBAL\Driver\IBMDB2\Exception\CannotCopyStreamToStream; 7use Doctrine\DBAL\Driver\IBMDB2\Exception\CannotCreateTemporaryFile; 8use Doctrine\DBAL\Driver\IBMDB2\Exception\CannotWriteToTemporaryFile; 9use Doctrine\DBAL\Driver\IBMDB2\Exception\StatementError; 10use Doctrine\DBAL\Driver\Result; 11use Doctrine\DBAL\Driver\Statement as StatementInterface; 12use Doctrine\DBAL\Driver\StatementIterator; 13use Doctrine\DBAL\FetchMode; 14use Doctrine\DBAL\ParameterType; 15use IteratorAggregate; 16use PDO; 17use ReflectionClass; 18use ReflectionObject; 19use ReflectionProperty; 20use ReturnTypeWillChange; 21use stdClass; 22 23use function array_change_key_case; 24use function assert; 25use function db2_bind_param; 26use function db2_execute; 27use function db2_fetch_array; 28use function db2_fetch_assoc; 29use function db2_fetch_both; 30use function db2_fetch_object; 31use function db2_free_result; 32use function db2_num_fields; 33use function db2_num_rows; 34use function db2_stmt_error; 35use function db2_stmt_errormsg; 36use function error_get_last; 37use function fclose; 38use function func_get_args; 39use function func_num_args; 40use function fwrite; 41use function gettype; 42use function is_int; 43use function is_object; 44use function is_resource; 45use function is_string; 46use function ksort; 47use function sprintf; 48use function stream_copy_to_stream; 49use function stream_get_meta_data; 50use function strtolower; 51use function tmpfile; 52 53use const CASE_LOWER; 54use const DB2_BINARY; 55use const DB2_CHAR; 56use const DB2_LONG; 57use const DB2_PARAM_FILE; 58use const DB2_PARAM_IN; 59 60/** 61 * @deprecated Use {@link Statement} instead 62 */ 63class DB2Statement implements IteratorAggregate, StatementInterface, Result 64{ 65 /** @var resource */ 66 private $stmt; 67 68 /** @var mixed[] */ 69 private $bindParam = []; 70 71 /** 72 * Map of LOB parameter positions to the tuples containing reference to the variable bound to the driver statement 73 * and the temporary file handle bound to the underlying statement 74 * 75 * @var mixed[][] 76 */ 77 private $lobs = []; 78 79 /** @var string Name of the default class to instantiate when fetching class instances. */ 80 private $defaultFetchClass = '\stdClass'; 81 82 /** @var mixed[] Constructor arguments for the default class to instantiate when fetching class instances. */ 83 private $defaultFetchClassCtorArgs = []; 84 85 /** @var int */ 86 private $defaultFetchMode = FetchMode::MIXED; 87 88 /** 89 * Indicates whether the statement is in the state when fetching results is possible 90 * 91 * @var bool 92 */ 93 private $result = false; 94 95 /** 96 * @internal The statement can be only instantiated by its driver connection. 97 * 98 * @param resource $stmt 99 */ 100 public function __construct($stmt) 101 { 102 $this->stmt = $stmt; 103 } 104 105 /** 106 * {@inheritdoc} 107 */ 108 public function bindValue($param, $value, $type = ParameterType::STRING) 109 { 110 assert(is_int($param)); 111 112 return $this->bindParam($param, $value, $type); 113 } 114 115 /** 116 * {@inheritdoc} 117 */ 118 public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) 119 { 120 assert(is_int($param)); 121 122 switch ($type) { 123 case ParameterType::INTEGER: 124 $this->bind($param, $variable, DB2_PARAM_IN, DB2_LONG); 125 break; 126 127 case ParameterType::LARGE_OBJECT: 128 if (isset($this->lobs[$param])) { 129 [, $handle] = $this->lobs[$param]; 130 fclose($handle); 131 } 132 133 $handle = $this->createTemporaryFile(); 134 $path = stream_get_meta_data($handle)['uri']; 135 136 $this->bind($param, $path, DB2_PARAM_FILE, DB2_BINARY); 137 138 $this->lobs[$param] = [&$variable, $handle]; 139 break; 140 141 default: 142 $this->bind($param, $variable, DB2_PARAM_IN, DB2_CHAR); 143 break; 144 } 145 146 return true; 147 } 148 149 /** 150 * @param int $position Parameter position 151 * @param mixed $variable 152 * 153 * @throws DB2Exception 154 */ 155 private function bind($position, &$variable, int $parameterType, int $dataType): void 156 { 157 $this->bindParam[$position] =& $variable; 158 159 if (! db2_bind_param($this->stmt, $position, 'variable', $parameterType, $dataType)) { 160 throw StatementError::new($this->stmt); 161 } 162 } 163 164 /** 165 * {@inheritdoc} 166 * 167 * @deprecated Use free() instead. 168 */ 169 public function closeCursor() 170 { 171 $this->bindParam = []; 172 173 if (! db2_free_result($this->stmt)) { 174 return false; 175 } 176 177 $this->result = false; 178 179 return true; 180 } 181 182 /** 183 * {@inheritdoc} 184 */ 185 public function columnCount() 186 { 187 return db2_num_fields($this->stmt) ?: 0; 188 } 189 190 /** 191 * {@inheritdoc} 192 * 193 * @deprecated The error information is available via exceptions. 194 */ 195 public function errorCode() 196 { 197 return db2_stmt_error(); 198 } 199 200 /** 201 * {@inheritdoc} 202 * 203 * @deprecated The error information is available via exceptions. 204 */ 205 public function errorInfo() 206 { 207 return [ 208 db2_stmt_errormsg(), 209 db2_stmt_error(), 210 ]; 211 } 212 213 /** 214 * {@inheritdoc} 215 */ 216 public function execute($params = null) 217 { 218 if ($params === null) { 219 ksort($this->bindParam); 220 221 $params = []; 222 223 foreach ($this->bindParam as $column => $value) { 224 $params[] = $value; 225 } 226 } 227 228 foreach ($this->lobs as [$source, $target]) { 229 if (is_resource($source)) { 230 $this->copyStreamToStream($source, $target); 231 232 continue; 233 } 234 235 $this->writeStringToStream($source, $target); 236 } 237 238 $retval = db2_execute($this->stmt, $params); 239 240 foreach ($this->lobs as [, $handle]) { 241 fclose($handle); 242 } 243 244 $this->lobs = []; 245 246 if ($retval === false) { 247 throw StatementError::new($this->stmt); 248 } 249 250 $this->result = true; 251 252 return $retval; 253 } 254 255 /** 256 * {@inheritdoc} 257 * 258 * @deprecated Use one of the fetch- or iterate-related methods. 259 */ 260 public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) 261 { 262 $this->defaultFetchMode = $fetchMode; 263 $this->defaultFetchClass = $arg2 ?: $this->defaultFetchClass; 264 $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs; 265 266 return true; 267 } 268 269 /** 270 * {@inheritdoc} 271 * 272 * @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead. 273 */ 274 #[ReturnTypeWillChange] 275 public function getIterator() 276 { 277 return new StatementIterator($this); 278 } 279 280 /** 281 * {@inheritdoc} 282 * 283 * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. 284 */ 285 public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) 286 { 287 // do not try fetching from the statement if it's not expected to contain result 288 // in order to prevent exceptional situation 289 if (! $this->result) { 290 return false; 291 } 292 293 $fetchMode = $fetchMode ?: $this->defaultFetchMode; 294 switch ($fetchMode) { 295 case FetchMode::COLUMN: 296 return $this->fetchColumn(); 297 298 case FetchMode::MIXED: 299 return db2_fetch_both($this->stmt); 300 301 case FetchMode::ASSOCIATIVE: 302 return db2_fetch_assoc($this->stmt); 303 304 case FetchMode::CUSTOM_OBJECT: 305 $className = $this->defaultFetchClass; 306 $ctorArgs = $this->defaultFetchClassCtorArgs; 307 308 if (func_num_args() >= 2) { 309 $args = func_get_args(); 310 $className = $args[1]; 311 $ctorArgs = $args[2] ?? []; 312 } 313 314 $result = db2_fetch_object($this->stmt); 315 316 if ($result instanceof stdClass) { 317 $result = $this->castObject($result, $className, $ctorArgs); 318 } 319 320 return $result; 321 322 case FetchMode::NUMERIC: 323 return db2_fetch_array($this->stmt); 324 325 case FetchMode::STANDARD_OBJECT: 326 return db2_fetch_object($this->stmt); 327 328 default: 329 throw new DB2Exception('Given Fetch-Style ' . $fetchMode . ' is not supported.'); 330 } 331 } 332 333 /** 334 * {@inheritdoc} 335 * 336 * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. 337 */ 338 public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) 339 { 340 $rows = []; 341 342 switch ($fetchMode) { 343 case FetchMode::CUSTOM_OBJECT: 344 while (($row = $this->fetch(...func_get_args())) !== false) { 345 $rows[] = $row; 346 } 347 348 break; 349 350 case FetchMode::COLUMN: 351 while (($row = $this->fetchColumn()) !== false) { 352 $rows[] = $row; 353 } 354 355 break; 356 357 default: 358 while (($row = $this->fetch($fetchMode)) !== false) { 359 $rows[] = $row; 360 } 361 } 362 363 return $rows; 364 } 365 366 /** 367 * {@inheritdoc} 368 * 369 * @deprecated Use fetchOne() instead. 370 */ 371 public function fetchColumn($columnIndex = 0) 372 { 373 $row = $this->fetch(FetchMode::NUMERIC); 374 375 if ($row === false) { 376 return false; 377 } 378 379 return $row[$columnIndex] ?? null; 380 } 381 382 /** 383 * {@inheritDoc} 384 */ 385 public function fetchNumeric() 386 { 387 if (! $this->result) { 388 return false; 389 } 390 391 return db2_fetch_array($this->stmt); 392 } 393 394 /** 395 * {@inheritdoc} 396 */ 397 public function fetchAssociative() 398 { 399 // do not try fetching from the statement if it's not expected to contain the result 400 // in order to prevent exceptional situation 401 if (! $this->result) { 402 return false; 403 } 404 405 return db2_fetch_assoc($this->stmt); 406 } 407 408 /** 409 * {@inheritdoc} 410 */ 411 public function fetchOne() 412 { 413 return FetchUtils::fetchOne($this); 414 } 415 416 /** 417 * {@inheritdoc} 418 */ 419 public function fetchAllNumeric(): array 420 { 421 return FetchUtils::fetchAllNumeric($this); 422 } 423 424 /** 425 * {@inheritdoc} 426 */ 427 public function fetchAllAssociative(): array 428 { 429 return FetchUtils::fetchAllAssociative($this); 430 } 431 432 /** 433 * {@inheritdoc} 434 */ 435 public function fetchFirstColumn(): array 436 { 437 return FetchUtils::fetchFirstColumn($this); 438 } 439 440 /** 441 * {@inheritdoc} 442 */ 443 public function rowCount() 444 { 445 return @db2_num_rows($this->stmt) ? : 0; 446 } 447 448 public function free(): void 449 { 450 $this->bindParam = []; 451 452 db2_free_result($this->stmt); 453 454 $this->result = false; 455 } 456 457 /** 458 * Casts a stdClass object to the given class name mapping its' properties. 459 * 460 * @param stdClass $sourceObject Object to cast from. 461 * @param class-string|object $destinationClass Name of the class or class instance to cast to. 462 * @param mixed[] $ctorArgs Arguments to use for constructing the destination class instance. 463 * 464 * @return object 465 * 466 * @throws DB2Exception 467 */ 468 private function castObject(stdClass $sourceObject, $destinationClass, array $ctorArgs = []) 469 { 470 if (! is_string($destinationClass)) { 471 if (! is_object($destinationClass)) { 472 throw new DB2Exception(sprintf( 473 'Destination class has to be of type string or object, %s given.', 474 gettype($destinationClass) 475 )); 476 } 477 } else { 478 $destinationClass = new ReflectionClass($destinationClass); 479 $destinationClass = $destinationClass->newInstanceArgs($ctorArgs); 480 } 481 482 $sourceReflection = new ReflectionObject($sourceObject); 483 $destinationClassReflection = new ReflectionObject($destinationClass); 484 /** @var ReflectionProperty[] $destinationProperties */ 485 $destinationProperties = array_change_key_case($destinationClassReflection->getProperties(), CASE_LOWER); 486 487 foreach ($sourceReflection->getProperties() as $sourceProperty) { 488 $sourceProperty->setAccessible(true); 489 490 $name = $sourceProperty->getName(); 491 $value = $sourceProperty->getValue($sourceObject); 492 493 // Try to find a case-matching property. 494 if ($destinationClassReflection->hasProperty($name)) { 495 $destinationProperty = $destinationClassReflection->getProperty($name); 496 497 $destinationProperty->setAccessible(true); 498 $destinationProperty->setValue($destinationClass, $value); 499 500 continue; 501 } 502 503 $name = strtolower($name); 504 505 // Try to find a property without matching case. 506 // Fallback for the driver returning either all uppercase or all lowercase column names. 507 if (isset($destinationProperties[$name])) { 508 $destinationProperty = $destinationProperties[$name]; 509 510 $destinationProperty->setAccessible(true); 511 $destinationProperty->setValue($destinationClass, $value); 512 513 continue; 514 } 515 516 $destinationClass->$name = $value; 517 } 518 519 return $destinationClass; 520 } 521 522 /** 523 * @return resource 524 * 525 * @throws DB2Exception 526 */ 527 private function createTemporaryFile() 528 { 529 $handle = @tmpfile(); 530 531 if ($handle === false) { 532 throw CannotCreateTemporaryFile::new(error_get_last()); 533 } 534 535 return $handle; 536 } 537 538 /** 539 * @param resource $source 540 * @param resource $target 541 * 542 * @throws DB2Exception 543 */ 544 private function copyStreamToStream($source, $target): void 545 { 546 if (@stream_copy_to_stream($source, $target) === false) { 547 throw CannotCopyStreamToStream::new(error_get_last()); 548 } 549 } 550 551 /** 552 * @param resource $target 553 * 554 * @throws DB2Exception 555 */ 556 private function writeStringToStream(string $string, $target): void 557 { 558 if (@fwrite($target, $string) === false) { 559 throw CannotWriteToTemporaryFile::new(error_get_last()); 560 } 561 } 562} 563