Bicycle/Services/DataBase/Classes/Statement.php
Egor Isaev 8fdcd0b901 dev
2026-08-07 13:44:45 +03:00

113 lines
4.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* @package Bicycle
* @author Egor Isaev
* @description Statement.php
* @copyright (c) 07/08/2026
*/
namespace Services\DataBase\Classes;
use PDO;
use PDOStatement;
/**
* Расширение PDOStatement, подключается через PDO::ATTR_STATEMENT_CLASS
* (см. PdoDriver::connect()). Добавляет showQuery()/sq() — PDOStatement::debugDumpParams()
* печатает в stdout, а не возвращает строку, поэтому значения bind-параметров
* копятся вручную (bindValue()/execute() переопределены) и подставляются в SQL сами.
* execute() также профилирует время в Profiler — здесь, а не в PdoDriver::query(),
* потому что через ATTR_STATEMENT_CLASS проходит вообще любое выполнение запроса,
* в том числе ручной $pdo->prepare()->execute() в обход query() (как в Repository).
*/
class Statement extends PDOStatement
{
/** @var array<int|string,mixed> Значения, забинженные через bindValue()/execute() */
protected array $_bound_params = [];
/**
* @var bool Не логировать этот execute() в Profiler — для служебных запросов вроде EXPLAIN
* внутри самой панели (ProfilerToolbar), которые иначе засоряли бы её же счётчик SQL.
*/
public bool $skip_profiling = false;
/**
* Конструктор вызывается только изнутри PDO (PDO::ATTR_STATEMENT_CLASS) — не создавать напрямую.
*
* @param PdoDriver $driver Владелец, нужен для quote() в showQuery()
*/
protected function __construct(protected PdoDriver $driver)
{
}
/**
* @inheritDoc
*/
public function bindValue(int|string $param, mixed $value, int $type = PDO::PARAM_STR): bool
{
$this->_bound_params[$param] = $value;
return parent::bindValue($param, $value, $type);
}
/**
* @inheritDoc
*/
public function execute(?array $params = null): bool
{
foreach ($params ?? [] as $key => $value) {
$this->_bound_params[$key] = $value;
}
$start = microtime(true);
$result = parent::execute($params);
if (!$this->skip_profiling) {
Profiler::log($this->queryString, $this->_bound_params, (microtime(true) - $start) * 1000);
}
return $result;
}
/**
* Возвращает SQL с подставленными вместо плейсхолдеров значениями — только для дебага
* (не для реального выполнения: значения квотируются через PDO::quote(), не защищают от всего,
* на что рассчитана честная подготовленная подстановка).
*
* @return string
* @throws \System\Classes\MyException
*/
public function showQuery(): string
{
$sql = $this->queryString;
foreach ($this->_bound_params as $key => $value) {
$quoted = is_int($value) || is_float($value) ? (string) $value : $this->driver->pdo()->quote((string) $value);
if (is_int($key)) {
$pos = strpos($sql, '?');
if ($pos !== false) {
$sql = substr_replace($sql, $quoted, $pos, 1);
}
} else {
$placeholder = str_starts_with((string) $key, ':') ? $key : ':' . $key;
$sql = str_replace($placeholder, $quoted, $sql);
}
}
return $sql;
}
/**
* Алиас showQuery() для быстрого дебага.
*
* @return string
* @throws \System\Classes\MyException
*/
public function sq(): string
{
return $this->showQuery();
}
}