[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/vendor/joomla/database/src/Mysqli/ -> MysqliStatement.php (source)

   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  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer