*/ 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 */ 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(); } }