306 lines
7.2 KiB
PHP
306 lines
7.2 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
|
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
|
*
|
|
* Licensed under The MIT License
|
|
* For full copyright and license information, please see the LICENSE.txt
|
|
* Redistributions of files must retain the above copyright notice.
|
|
*
|
|
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
|
* @link https://cakephp.org CakePHP(tm) Project
|
|
* @since 5.0.0
|
|
* @license https://opensource.org/licenses/mit-license.php MIT License
|
|
*/
|
|
namespace Cake\Database\Statement;
|
|
|
|
use Cake\Database\Driver;
|
|
use Cake\Database\StatementInterface;
|
|
use Cake\Database\TypeFactory;
|
|
use Cake\Database\TypeInterface;
|
|
use Generator;
|
|
use InvalidArgumentException;
|
|
use PDO;
|
|
use PDOStatement;
|
|
|
|
class Statement implements StatementInterface
|
|
{
|
|
/**
|
|
* @var array<string, int>
|
|
*/
|
|
protected const MODE_NAME_MAP = [
|
|
self::FETCH_TYPE_ASSOC => PDO::FETCH_ASSOC,
|
|
self::FETCH_TYPE_NUM => PDO::FETCH_NUM,
|
|
self::FETCH_TYPE_OBJ => PDO::FETCH_OBJ,
|
|
];
|
|
|
|
/**
|
|
* @var \Cake\Database\Driver
|
|
*/
|
|
protected Driver $_driver;
|
|
|
|
/**
|
|
* Cached bound parameters used for logging
|
|
*
|
|
* @var array<mixed>
|
|
*/
|
|
protected array $params = [];
|
|
|
|
/**
|
|
* @param \PDOStatement $statement PDO statement
|
|
* @param \Cake\Database\Driver $driver Database driver
|
|
* @param array<\Closure> $resultDecorators Results decorators
|
|
*/
|
|
public function __construct(
|
|
protected PDOStatement $statement,
|
|
Driver $driver,
|
|
protected array $resultDecorators = [],
|
|
) {
|
|
$this->_driver = $driver;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function bind(array $params, array $types): void
|
|
{
|
|
if (!$params) {
|
|
return;
|
|
}
|
|
|
|
$anonymousParams = is_int(key($params));
|
|
$offset = 1;
|
|
foreach ($params as $index => $value) {
|
|
$type = $types[$index] ?? null;
|
|
if ($anonymousParams) {
|
|
$index += $offset;
|
|
}
|
|
$this->bindValue($index, $value, $type);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function bindValue(string|int $column, mixed $value, string|int|null $type = 'string'): void
|
|
{
|
|
$type ??= 'string';
|
|
if (!is_int($type)) {
|
|
[$value, $type] = $this->cast($value, $type);
|
|
}
|
|
|
|
$this->params[$column] = $value;
|
|
$this->performBind($column, $value, $type);
|
|
}
|
|
|
|
/**
|
|
* Converts a give value to a suitable database value based on type and
|
|
* return relevant internal statement type.
|
|
*
|
|
* @param mixed $value The value to cast.
|
|
* @param \Cake\Database\TypeInterface|string|int $type The type name or type instance to use.
|
|
* @return array List containing converted value and internal type.
|
|
* @phpstan-return array{0:mixed, 1:int}
|
|
*/
|
|
protected function cast(mixed $value, TypeInterface|string|int $type = 'string'): array
|
|
{
|
|
if (is_string($type)) {
|
|
$type = TypeFactory::build($type);
|
|
}
|
|
if ($type instanceof TypeInterface) {
|
|
$value = $type->toDatabase($value, $this->_driver);
|
|
$type = $type->toStatement($value, $this->_driver);
|
|
}
|
|
|
|
return [$value, $type];
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function getBoundParams(): array
|
|
{
|
|
return $this->params;
|
|
}
|
|
|
|
/**
|
|
* @param string|int $column
|
|
* @param mixed $value
|
|
* @param int $type
|
|
* @return void
|
|
*/
|
|
protected function performBind(string|int $column, mixed $value, int $type): void
|
|
{
|
|
$this->statement->bindValue($column, $value, $type);
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function execute(?array $params = null): bool
|
|
{
|
|
return $this->statement->execute($params);
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function fetch(string|int $mode = PDO::FETCH_NUM): mixed
|
|
{
|
|
$mode = $this->convertMode($mode);
|
|
$row = $this->statement->fetch($mode);
|
|
if ($row === false) {
|
|
return false;
|
|
}
|
|
|
|
foreach ($this->resultDecorators as $decorator) {
|
|
$row = $decorator($row);
|
|
}
|
|
|
|
return $row;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function fetchAssoc(): array
|
|
{
|
|
return $this->fetch(PDO::FETCH_ASSOC) ?: [];
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function fetchColumn(int $position): mixed
|
|
{
|
|
$row = $this->fetch(PDO::FETCH_NUM);
|
|
if ($row && isset($row[$position])) {
|
|
return $row[$position];
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function fetchAll(string|int $mode = PDO::FETCH_NUM): array
|
|
{
|
|
$mode = $this->convertMode($mode);
|
|
$rows = $this->statement->fetchAll($mode);
|
|
|
|
foreach ($this->resultDecorators as $decorator) {
|
|
$rows = array_map($decorator, $rows);
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
|
|
/**
|
|
* Converts mode name to PDO constant.
|
|
*
|
|
* @param string|int $mode Mode name or PDO constant
|
|
* @return int
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
protected function convertMode(string|int $mode): int
|
|
{
|
|
if (is_int($mode)) {
|
|
// We don't try to validate the PDO constants
|
|
return $mode;
|
|
}
|
|
|
|
return static::MODE_NAME_MAP[$mode]
|
|
??
|
|
throw new InvalidArgumentException("Invalid fetch mode requested. Expected 'assoc', 'num' or 'obj'.");
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function closeCursor(): void
|
|
{
|
|
$this->statement->closeCursor();
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function rowCount(): int
|
|
{
|
|
return $this->statement->rowCount();
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function columnCount(): int
|
|
{
|
|
return $this->statement->columnCount();
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function errorCode(): string
|
|
{
|
|
return $this->statement->errorCode() ?: '';
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function errorInfo(): array
|
|
{
|
|
return $this->statement->errorInfo();
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function lastInsertId(?string $table = null, ?string $column = null): string|int
|
|
{
|
|
if ($column && $this->columnCount()) {
|
|
$row = $this->fetch(static::FETCH_TYPE_ASSOC);
|
|
|
|
if ($row && isset($row[$column])) {
|
|
return $row[$column];
|
|
}
|
|
}
|
|
|
|
return $this->_driver->lastInsertId($table);
|
|
}
|
|
|
|
/**
|
|
* Returns prepared query string stored in PDOStatement.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function queryString(): string
|
|
{
|
|
return $this->statement->queryString;
|
|
}
|
|
|
|
/**
|
|
* Get the inner iterator
|
|
*
|
|
* @return \Generator
|
|
*/
|
|
public function getIterator(): Generator
|
|
{
|
|
$this->statement->setFetchMode(PDO::FETCH_ASSOC);
|
|
|
|
foreach ($this->statement as $row) {
|
|
foreach ($this->resultDecorators as $decorator) {
|
|
$row = $decorator($row);
|
|
}
|
|
|
|
yield $row;
|
|
}
|
|
|
|
$this->closeCursor();
|
|
}
|
|
}
|