link_db`). * * `PHPShopOrm` собирает SQL конкатенацией — для записей колбэка, где в запрос попадают * данные извне, модуль ходит в БД сам. Ошибка запроса — {@see RuntimeException} с кодом * ошибки MySQL (1062 — дубликат ключа) при любом `mysqli_report()`: в PHP 7.4 по умолчанию * mysqli молчит, с 8.1 бросает исключения. */ final class Database { /** Дубликат уникального ключа. */ public const ER_DUP_ENTRY = 1062; /** @var mysqli */ private $link; public function __construct(mysqli $link) { $this->link = $link; } public static function fromHost(): self { // Колбэк и витрина держат соединение в $PHPShopBase, ядро дублирует его в глобальный $link_db. $base = $GLOBALS['PHPShopBase'] ?? null; $link = is_object($base) && isset($base->link_db) ? $base->link_db : ($GLOBALS['link_db'] ?? null); if (!$link instanceof mysqli) { throw new RuntimeException('Нет соединения ядра PHPShop с БД.'); } return new self($link); } /** * @param array $params * * @return array> */ public function fetchAll(string $sql, array $params = []): array { $statement = $this->run($sql, $params); try { $result = $statement->get_result(); if ($result === false) { throw new RuntimeException('Запрос не вернул строк: ' . $statement->error, $statement->errno); } $rows = []; while (is_array($row = $result->fetch_assoc())) { $rows[] = $row; } return $rows; } finally { $statement->close(); } } /** * @param array $params * * @return mixed первая колонка первой строки или null */ public function fetchValue(string $sql, array $params = []) { $rows = $this->fetchAll($sql, $params); if ($rows === []) { return null; } return reset($rows[0]); } /** * @param array $params * * @return int число затронутых строк */ public function execute(string $sql, array $params = []): int { $statement = $this->run($sql, $params); $affected = $statement->affected_rows; $statement->close(); return (int) $affected; } /** * @param array $params */ private function run(string $sql, array $params): mysqli_stmt { try { $statement = $this->link->prepare($sql); if ($statement === false) { throw new RuntimeException('Запрос не подготовлен: ' . $this->link->error, $this->link->errno); } if ($params !== []) { $types = ''; foreach ($params as $param) { $types .= is_int($param) ? 'i' : 's'; } $statement->bind_param($types, ...$params); } if (!$statement->execute()) { $error = new RuntimeException('Запрос не выполнен: ' . $statement->error, $statement->errno); $statement->close(); throw $error; } return $statement; } catch (mysqli_sql_exception $exception) { throw new RuntimeException( 'Ошибка MySQL: ' . $exception->getMessage(), (int) $exception->getCode(), $exception, ); } } }