[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Part of the Joomla Framework Database Package 4 * 5 * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. 6 * @license GNU General Public License version 2 or later; see LICENSE 7 */ 8 9 namespace Joomla\Database\Mysqli; 10 11 use Joomla\Database\Exception\ExecutionFailureException; 12 use Joomla\Database\Exception\PrepareStatementFailureException; 13 use Joomla\Database\FetchMode; 14 use Joomla\Database\FetchOrientation; 15 use Joomla\Database\ParameterType; 16 use Joomla\Database\StatementInterface; 17 18 /** 19 * MySQLi Database Statement. 20 * 21 * This class is modeled on \Doctrine\DBAL\Driver\Mysqli\MysqliStatement 22 * 23 * @since 2.0.0 24 */ 25 class MysqliStatement implements StatementInterface 26 { 27 /** 28 * Values which have been bound to the statement. 29 * 30 * @var array 31 * @since 2.0.0 32 */ 33 protected $bindedValues; 34 35 /** 36 * Mapping between named parameters and position in query. 37 * 38 * @var array 39 * @since 2.0.0 40 */ 41 protected $parameterKeyMapping; 42 43 /** 44 * Mapping array for parameter types. 45 * 46 * @var array 47 * @since 2.0.0 48 */ 49 protected $parameterTypeMapping = [ 50 ParameterType::BOOLEAN => 'i', 51 ParameterType::INTEGER => 'i', 52 ParameterType::LARGE_OBJECT => 's', 53 ParameterType::NULL => 's', 54 ParameterType::STRING => 's', 55 ]; 56 57 /** 58 * Column names from the executed statement. 59 * 60 * @var array|boolean|null 61 * @since 2.0.0 62 */ 63 protected $columnNames; 64 65 /** 66 * The database connection resource. 67 * 68 * @var \mysqli 69 * @since 2.0.0 70 */ 71 protected $connection; 72 73 /** 74 * The default fetch mode for the statement. 75 * 76 * @var integer 77 * @since 2.0.0 78 */ 79 protected $defaultFetchStyle = FetchMode::MIXED; 80 81 /** 82 * The query string being prepared. 83 * 84 * @var string 85 * @since 2.0.0 86 */ 87 protected $query; 88 89 /** 90 * Internal tracking flag to set whether there is a result set available for processing 91 * 92 * @var boolean 93 * @since 2.0.0 94 */ 95 private $result = false; 96 97 /** 98 * Values which have been bound to the rows of each result set. 99 * 100 * @var array 101 * @since 2.0.0 102 */ 103 protected $rowBindedValues; 104 105 /** 106 * The prepared statement. 107 * 108 * @var \mysqli_stmt 109 * @since 2.0.0 110 */ 111 protected $statement; 112 113 /** 114 * Bound parameter types. 115 * 116 * @var array 117 * @since 2.0.0 118 */ 119 protected $typesKeyMapping; 120 121 /** 122 * Constructor. 123 * 124 * @param \mysqli $connection The database connection resource 125 * @param string $query The query this statement will process 126 * 127 * @since 2.0.0 128 * @throws PrepareStatementFailureException 129 */ 130 public function __construct(\mysqli $connection, string $query) 131 { 132 $this->connection = $connection; 133 $this->query = $query; 134 135 $query = $this->prepareParameterKeyMapping($query); 136 137 $this->statement = $connection->prepare($query); 138 139 if (!$this->statement) 140 { 141 throw new PrepareStatementFailureException($this->connection->error, $this->connection->errno); 142 } 143 } 144 145 /** 146 * Replace named parameters with numbered parameters 147 * 148 * @param string $sql The SQL statement to prepare. 149 * 150 * @return string The processed SQL statement. 151 * 152 * @since 2.0.0 153 */ 154 public function prepareParameterKeyMapping($sql) 155 { 156 $escaped = false; 157 $startPos = 0; 158 $quoteChar = ''; 159 $literal = ''; 160 $mapping = []; 161 $replace = []; 162 $matches = []; 163 $pattern = '/([:][a-zA-Z0-9_]+)/'; 164 165 if (!preg_match($pattern, $sql, $matches)) 166 { 167 return $sql; 168 } 169 170 $sql = trim($sql); 171 $n = \strlen($sql); 172 173 while ($startPos < $n) 174 { 175 if (!preg_match($pattern, $sql, $matches, 0, $startPos)) 176 { 177 break; 178 } 179 180 $j = strpos($sql, "'", $startPos); 181 $k = strpos($sql, '"', $startPos); 182 183 if (($k !== false) && (($k < $j) || ($j === false))) 184 { 185 $quoteChar = '"'; 186 $j = $k; 187 } 188 else 189 { 190 $quoteChar = "'"; 191 } 192 193 if ($j === false) 194 { 195 $j = $n; 196 } 197 198 // Search for named prepared parameters and replace it with ? and save its position 199 $substring = substr($sql, $startPos, $j - $startPos); 200 201 if (preg_match_all($pattern, $substring, $matches, PREG_PATTERN_ORDER + PREG_OFFSET_CAPTURE)) 202 { 203 foreach ($matches[0] as $i => $match) 204 { 205 if ($i === 0) 206 { 207 $literal .= substr($substring, 0, $match[1]); 208 } 209 210 $mapping[$match[0]] = \count($mapping); 211 $endOfPlaceholder = $match[1] + strlen($match[0]); 212 $beginOfNextPlaceholder = $matches[0][$i + 1][1] ?? strlen($substring); 213 $beginOfNextPlaceholder -= $endOfPlaceholder; 214 $literal .= '?' . substr($substring, $endOfPlaceholder, $beginOfNextPlaceholder); 215 } 216 } 217 else 218 { 219 $literal .= $substring; 220 } 221 222 $startPos = $j; 223 $j++; 224 225 if ($j >= $n) 226 { 227 break; 228 } 229 230 // Quote comes first, find end of quote 231 while (true) 232 { 233 $k = strpos($sql, $quoteChar, $j); 234 $escaped = false; 235 236 if ($k === false) 237 { 238 break; 239 } 240 241 $l = $k - 1; 242 243 while ($l >= 0 && $sql[$l] === '\\') 244 { 245 $l--; 246 $escaped = !$escaped; 247 } 248 249 if ($escaped) 250 { 251 $j = $k + 1; 252 253 continue; 254 } 255 256 break; 257 } 258 259 if ($k === false) 260 { 261 // Error in the query - no end quote; ignore it 262 break; 263 } 264 265 $literal .= substr($sql, $startPos, $k - $startPos + 1); 266 $startPos = $k + 1; 267 } 268 269 if ($startPos < $n) 270 { 271 $literal .= substr($sql, $startPos, $n - $startPos); 272 } 273 274 $this->parameterKeyMapping = $mapping; 275 276 return $literal; 277 } 278 279 /** 280 * Binds a parameter to the specified variable name. 281 * 282 * @param string|integer $parameter Parameter identifier. For a prepared statement using named placeholders, this will be a parameter 283 * name of the form `:name`. For a prepared statement using question mark placeholders, this will be 284 * the 1-indexed position of the parameter. 285 * @param mixed $variable Name of the PHP variable to bind to the SQL statement parameter. 286 * @param integer $dataType Constant corresponding to a SQL datatype, this should be the processed type from the QueryInterface. 287 * @param integer $length The length of the variable. Usually required for OUTPUT parameters. 288 * @param array $driverOptions Optional driver options to be used. 289 * 290 * @return boolean 291 * 292 * @since 2.0.0 293 */ 294 public function bindParam($parameter, &$variable, string $dataType = ParameterType::STRING, ?int $length = null, ?array $driverOptions = null) 295 { 296 $this->bindedValues[$parameter] =& $variable; 297 298 // Validate parameter type 299 if (!isset($this->parameterTypeMapping[$dataType])) 300 { 301 throw new \InvalidArgumentException(sprintf('Unsupported parameter type `%s`', $dataType)); 302 } 303 304 $this->typesKeyMapping[$parameter] = $this->parameterTypeMapping[$dataType]; 305 306 return true; 307 } 308 309 /** 310 * Binds a array of values to bound parameters. 311 * 312 * @param array $values The values to bind to the statement 313 * 314 * @return boolean 315 * 316 * @since 2.0.0 317 */ 318 private function bindValues(array $values) 319 { 320 $params = []; 321 $types = str_repeat('s', \count($values)); 322 323 if (!empty($this->parameterKeyMapping)) 324 { 325 foreach ($values as $key => &$value) 326 { 327 $params[$this->parameterKeyMapping[$key]] =& $value; 328 } 329 330 ksort($params); 331 } 332 else 333 { 334 foreach ($values as $key => &$value) 335 { 336 $params[] =& $value; 337 } 338 } 339 340 array_unshift($params, $types); 341 342 return \call_user_func_array([$this->statement, 'bind_param'], $params); 343 } 344 345 /** 346 * Closes the cursor, enabling the statement to be executed again. 347 * 348 * @return void 349 * 350 * @since 2.0.0 351 */ 352 public function closeCursor(): void 353 { 354 $this->statement->free_result(); 355 $this->result = false; 356 } 357 358 /** 359 * Fetches the SQLSTATE associated with the last operation on the statement handle. 360 * 361 * @return string 362 * 363 * @since 2.0.0 364 */ 365 public function errorCode() 366 { 367 return $this->statement->errno; 368 } 369 370 /** 371 * Fetches extended error information associated with the last operation on the statement handle. 372 * 373 * @return array 374 * 375 * @since 2.0.0 376 */ 377 public function errorInfo() 378 { 379 return $this->statement->error; 380 } 381 382 /** 383 * Executes a prepared statement 384 * 385 * @param array|null $parameters An array of values with as many elements as there are bound parameters in the SQL statement being executed. 386 * 387 * @return boolean 388 * 389 * @since 2.0.0 390 */ 391 public function execute(?array $parameters = null) 392 { 393 if ($this->bindedValues !== null) 394 { 395 $params = []; 396 $types = []; 397 398 if (!empty($this->parameterKeyMapping)) 399 { 400 foreach ($this->bindedValues as $key => &$value) 401 { 402 $params[$this->parameterKeyMapping[$key]] =& $value; 403 $types[$this->parameterKeyMapping[$key]] = $this->typesKeyMapping[$key]; 404 } 405 } 406 else 407 { 408 foreach ($this->bindedValues as $key => &$value) 409 { 410 $params[] =& $value; 411 $types[$key] = $this->typesKeyMapping[$key]; 412 } 413 } 414 415 ksort($params); 416 ksort($types); 417 418 array_unshift($params, implode('', $types)); 419 420 if (!\call_user_func_array([$this->statement, 'bind_param'], $params)) 421 { 422 throw new PrepareStatementFailureException($this->statement->error, $this->statement->errno); 423 } 424 } 425 elseif ($parameters !== null) 426 { 427 if (!$this->bindValues($parameters)) 428 { 429 throw new PrepareStatementFailureException($this->statement->error, $this->statement->errno); 430 } 431 } 432 433 if (!$this->statement->execute()) 434 { 435 throw new ExecutionFailureException($this->query, $this->statement->error, $this->statement->errno); 436 } 437 438 if ($this->columnNames === null) 439 { 440 $meta = $this->statement->result_metadata(); 441 442 if ($meta !== false) 443 { 444 $columnNames = []; 445 446 foreach ($meta->fetch_fields() as $col) 447 { 448 $columnNames[] = $col->name; 449 } 450 451 $meta->free(); 452 453 $this->columnNames = $columnNames; 454 } 455 else 456 { 457 $this->columnNames = false; 458 } 459 } 460 461 if ($this->columnNames !== false) 462 { 463 $this->statement->store_result(); 464 465 $this->rowBindedValues = array_fill(0, \count($this->columnNames), null); 466 $refs = []; 467 468 foreach ($this->rowBindedValues as $key => &$value) 469 { 470 $refs[$key] =& $value; 471 } 472 473 if (!\call_user_func_array([$this->statement, 'bind_result'], $refs)) 474 { 475 throw new \RuntimeException($this->statement->error, $this->statement->errno); 476 } 477 } 478 479 $this->result = true; 480 481 return true; 482 } 483 484 /** 485 * Fetches the next row from a result set 486 * 487 * @param integer|null $fetchStyle Controls how the next row will be returned to the caller. This value must be one of the 488 * FetchMode constants, defaulting to value of FetchMode::MIXED. 489 * @param integer $cursorOrientation For a StatementInterface object representing a scrollable cursor, this value determines which row 490 * will be returned to the caller. This value must be one of the FetchOrientation constants, 491 * defaulting to FetchOrientation::NEXT. 492 * @param integer $cursorOffset For a StatementInterface object representing a scrollable cursor for which the cursorOrientation 493 * parameter is set to FetchOrientation::ABS, this value specifies the absolute number of the row in 494 * the result set that shall be fetched. For a StatementInterface object representing a scrollable 495 * cursor for which the cursorOrientation parameter is set to FetchOrientation::REL, this value 496 * specifies the row to fetch relative to the cursor position before `fetch()` was called. 497 * 498 * @return mixed The return value of this function on success depends on the fetch type. In all cases, boolean false is returned on failure. 499 * 500 * @since 2.0.0 501 */ 502 public function fetch(?int $fetchStyle = null, int $cursorOrientation = FetchOrientation::NEXT, int $cursorOffset = 0) 503 { 504 if (!$this->result) 505 { 506 return false; 507 } 508 509 $fetchStyle = $fetchStyle ?: $this->defaultFetchStyle; 510 511 if ($fetchStyle === FetchMode::COLUMN) 512 { 513 return $this->fetchColumn(); 514 } 515 516 $values = $this->fetchData(); 517 518 if ($values === null) 519 { 520 return false; 521 } 522 523 if ($values === false) 524 { 525 throw new \RuntimeException($this->statement->error, $this->statement->errno); 526 } 527 528 switch ($fetchStyle) 529 { 530 case FetchMode::NUMERIC: 531 return $values; 532 533 case FetchMode::ASSOCIATIVE: 534 return array_combine($this->columnNames, $values); 535 536 case FetchMode::MIXED: 537 $ret = array_combine($this->columnNames, $values); 538 $ret += $values; 539 540 return $ret; 541 542 case FetchMode::STANDARD_OBJECT: 543 return (object) array_combine($this->columnNames, $values); 544 545 default: 546 throw new \InvalidArgumentException("Unknown fetch type '{$fetchStyle}'"); 547 } 548 } 549 550 /** 551 * Returns a single column from the next row of a result set 552 * 553 * @param integer $columnIndex 0-indexed number of the column you wish to retrieve from the row. 554 * If no value is supplied, the first column is retrieved. 555 * 556 * @return mixed Returns a single column from the next row of a result set or boolean false if there are no more rows. 557 * 558 * @since 2.0.0 559 */ 560 public function fetchColumn($columnIndex = 0) 561 { 562 $row = $this->fetch(FetchMode::NUMERIC); 563 564 if ($row === false) 565 { 566 return false; 567 } 568 569 return $row[$columnIndex] ?? null; 570 } 571 572 /** 573 * Fetch the data from the statement. 574 * 575 * @return array|boolean 576 * 577 * @since 2.0.0 578 */ 579 private function fetchData() 580 { 581 $return = $this->statement->fetch(); 582 583 if ($return === true) 584 { 585 $values = []; 586 587 foreach ($this->rowBindedValues as $v) 588 { 589 $values[] = $v; 590 } 591 592 return $values; 593 } 594 595 return $return; 596 } 597 598 /** 599 * Returns the number of rows affected by the last SQL statement. 600 * 601 * @return integer 602 * 603 * @since 2.0.0 604 */ 605 public function rowCount(): int 606 { 607 if ($this->columnNames === false) 608 { 609 return $this->statement->affected_rows; 610 } 611 612 return $this->statement->num_rows; 613 } 614 615 /** 616 * Sets the fetch mode to use while iterating this statement. 617 * 618 * @param integer $fetchMode The fetch mode, must be one of the FetchMode constants. 619 * @param mixed ...$args Optional mode-specific arguments. 620 * 621 * @return void 622 * 623 * @since 2.0.0 624 */ 625 public function setFetchMode(int $fetchMode, ...$args): void 626 { 627 $this->defaultFetchStyle = $fetchMode; 628 } 629 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |