From 0485a5b823081b0789d43ef21f180efecd62ed9a Mon Sep 17 00:00:00 2001 From: Dimitri Sitchet Tomkeu Date: Tue, 24 Feb 2026 20:43:17 +0100 Subject: [PATCH 1/3] =?UTF-8?q?refactor:=20remplacement=20de=20ConnectionR?= =?UTF-8?q?esolver=20par=20DatabaseManager=20et=20mise=20=C3=A0=20jour=20d?= =?UTF-8?q?es=20services=20de=20connexion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Config/Services.php | 101 +++++++---- src/Config/database.php | 4 +- src/ConnectionResolver.php | 177 ------------------- src/Database.php | 223 ----------------------- src/DatabaseManager.php | 272 +++++++++++++++++++++++++++++ src/Providers/DatabaseProvider.php | 5 +- 6 files changed, 341 insertions(+), 441 deletions(-) delete mode 100644 src/ConnectionResolver.php delete mode 100644 src/Database.php create mode 100644 src/DatabaseManager.php diff --git a/src/Config/Services.php b/src/Config/Services.php index d41e648..155a10f 100644 --- a/src/Config/Services.php +++ b/src/Config/Services.php @@ -12,87 +12,114 @@ namespace BlitzPHP\Database\Config; use BlitzPHP\Container\Services as BaseServices; -use BlitzPHP\Contracts\Database\ConnectionResolverInterface; use BlitzPHP\Database\Builder\BaseBuilder; use BlitzPHP\Database\Connection\BaseConnection; -use BlitzPHP\Database\Database; -use Dimtrovich\DbDumper\Exceptions\Exception as DumperException; +use BlitzPHP\Database\DatabaseManager; use Dimtrovich\DbDumper\Exporter; use Dimtrovich\DbDumper\Importer; +use InvalidArgumentException; +/** + * Services de base de données + */ class Services extends BaseServices { /** - * Query Builder + * Instance du gestionnaire de base de données */ - public static function builder(?string $group = null, bool $shared = true): BaseBuilder + protected static ?DatabaseManager $manager = null; + + /** + * Récupère le gestionnaire de base de données + */ + protected static function manager(): DatabaseManager { - if (true === $shared && isset(static::$instances[BaseBuilder::class])) { - return static::$instances[BaseBuilder::class]; + if (static::$manager === null) { + static::$manager = new DatabaseManager(static::logger(), static::event()); } - return static::$instances[BaseBuilder::class] = Database::builder(static::database($group)); + return static::$manager; } /** - * Connexion a la base de données + * Récupère une connexion à la base de données */ public static function database(?string $group = null, bool $shared = true): BaseConnection { - /** @var ConnectionResolverInterface */ - $connectionResolver = static::container()->get(ConnectionResolverInterface::class); - [$group] = $connectionResolver->connectionInfo($group); + $connection = static::manager()->connect($group, $shared); - if (true === $shared && isset(static::$instances[Database::class]) && static::$instances[Database::class]->group === $group) { - return static::$instances[Database::class]; + if (!$connection instanceof BaseConnection) { + throw new InvalidArgumentException('La connexion retournée n\'est pas une instance de BaseConnection'); } - return static::$instances[Database::class] = $connectionResolver->connect($group); + return $connection; } /** - * Systeme d'exportation de la base de donnees + * Récupère un query builder */ - public static function dbExporter(?BaseConnection $db = null, array $config = [], bool $shared = true): Exporter + public static function builder(?string $group = null, bool $shared = true): BaseBuilder { - if (true === $shared && isset(static::$instances[Exporter::class])) { - return static::$instances[Exporter::class]; + $key = 'builder_' . ($group ?? 'default'); + + if ($shared && isset(static::$instances[$key])) { + return static::$instances[$key]; + } + + $builder = static::manager()->builder(static::database($group, $shared)); + + if ($shared) { + static::$instances[$key] = $builder; } - $db ??= self::database(); - $config ??= config('dump', []); + return $builder; + } - if (! $db->conn) { - $db->initialize(); + /** + * Récupère un exportateur de base de données + */ + public static function dbExporter(?BaseConnection $db = null, array $config = [], bool $shared = true): Exporter + { + if ($shared) { + return static::sharedInstance('dbExporter', $db, $config); } - if (! $db->isPdo()) { - throw new DumperException('Impossible de sauvegarder la base de données. Vous devez utiliser un pilote PDO', DumperException::PDO_EXCEPTION); + $db ??= static::database(); + $config = $config ?: (array) config('dump', []); + + // Initialiser la connexion si nécessaire + $db->initialize(); + + $exporter = new Exporter($db->getDatabase(), $db->getConnection(), $config); + + if ($shared) { + static::$instances[Exporter::class] = $exporter; } - return static::$instances[Exporter::class] = new Exporter($db->database, $db->conn, $config); + return $exporter; } /** - * Systeme d'importation de la base de donnees + * Récupère un importateur de base de données */ public static function dbImporter(?BaseConnection $db = null, array $config = [], bool $shared = true): Importer { - if (true === $shared && isset(static::$instances[Importer::class])) { - return static::$instances[Importer::class]; + if ($shared) { + return static::sharedInstance('dbImporter', $db, $config); } - $db ??= self::database(); - $config ??= config('dump', []); + $db ??= static::database(); + $config = $config ?: (array) config('dump', []); - if (! $db->conn) { - $db->initialize(); - } + // Initialiser la connexion si nécessaire + $db->initialize(); + + $importer = new Importer($db->getDatabase(), $db->getConnection(), $config); - if (! $db->isPdo()) { - throw new DumperException('Impossible de restaurer la base de données. Vous devez utiliser un pilote PDO', DumperException::PDO_EXCEPTION); + if ($shared) { + static::$instances[Importer::class] = $importer; } - return static::$instances[Importer::class] = new Importer($db->database, $db->conn, $config); + return $importer; } } diff --git a/src/Config/database.php b/src/Config/database.php index 61c438c..890f541 100644 --- a/src/Config/database.php +++ b/src/Config/database.php @@ -29,11 +29,11 @@ /** * @var string Pilote de base de données à utiliser */ - 'driver' => env('db.default.driver', 'pdomysql'), + 'driver' => env('db.default.driver', 'mysql'), /** @var int */ 'port' => env('db.default.port', 3306), /** @var string */ - 'host' => env('db.default.hostname', 'localhost'), + 'hostname' => env('db.default.hostname', 'localhost'), /** @var string */ 'username' => env('db.default.username', 'root'), /** @var string */ diff --git a/src/ConnectionResolver.php b/src/ConnectionResolver.php deleted file mode 100644 index b6ab850..0000000 --- a/src/ConnectionResolver.php +++ /dev/null @@ -1,177 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace BlitzPHP\Database; - -use BlitzPHP\Contracts\Database\ConnectionInterface; -use BlitzPHP\Contracts\Database\ConnectionResolverInterface; -use BlitzPHP\Database\Config\Services; -use BlitzPHP\Database\Creator\BaseCreator; -use InvalidArgumentException; - -class ConnectionResolver implements ConnectionResolverInterface -{ - protected string $defaultConnection = 'default'; - - /** - * Cache pour les instances de toutes les connections - * qui ont été requetées en tant que instance partagées - * - * @var array - */ - protected static $instances = []; - - /** - * L'instance principale utilisée pour gérer toutes les ouvertures à la base de données. - * - * @var Database|null - */ - protected static $factory; - - /** - * {@inheritDoc} - */ - public function connection(?string $name = null): ConnectionInterface - { - return $this->connect($name ?: $this->defaultConnection); - } - - /** - * {@inheritDoc} - */ - public function connect($group = null, bool $shared = true): ConnectionInterface - { - // Si on a deja passer une connection, pas la peine de continuer - if ($group instanceof ConnectionInterface) { - return $group; - } - - [$group, $config] = $this->connectionInfo($group); - - if ($shared && isset(static::$instances[$group])) { - return static::$instances[$group]; - } - - static::ensureFactory(); - - $connection = static::$factory->load( - $config, - $group, - Services::logger(), - Services::event() - ); - - static::$instances[$group] = &$connection; - - return $connection; - } - - /** - * {@inheritDoc} - */ - public function connectionInfo(array|string|null $group = null): array - { - if (is_array($group)) { - $config = $group; - $group = 'custom-' . md5(json_encode($config)); - } - - $config ??= config('database'); - - if (empty($group)) { - $group = $config['connection'] ?? 'auto'; - } - if ($group === 'auto') { - $group = on_test() ? 'test' : (on_prod() ? 'production' : 'development'); - } - - if (! isset($config[$group]) && ! str_contains($group, 'custom-')) { - $group = 'default'; - } - - if (is_string($group) && ! isset($config[$group]) && ! str_starts_with($group, 'custom-')) { - throw new InvalidArgumentException($group . ' is not a valid database connection group.'); - } - - if (str_contains($group, 'custom-')) { - $config = [$group => $config]; - } - - $config = $config[$group]; - - if (str_contains($config['driver'], 'sqlite') && $config['database'] !== ':memory:' && ! str_contains($config['database'], DIRECTORY_SEPARATOR)) { - $config['database'] = APP_STORAGE_PATH . $config['database']; - } - - return [$group, $config]; - } - - /** - * {@inheritDoc} - */ - public function getDefaultConnection(): string - { - return $this->defaultConnection; - } - - /** - * {@inheritDoc} - */ - public function setDefaultConnection(string $name): void - { - $this->defaultConnection = $name; - } - - /** - * Renvoie un tableau contenant toute les connxions deja etablies. - */ - public static function getConnections(): array - { - return static::$instances; - } - - /** - * Charge et retourne une instance du Creator specifique au groupe de la base de donnees - * et charge le groupe s'il n'est pas encore chargé. - * - * @param array|ConnectionInterface|string|null $group - */ - public function creator($group = null, bool $shared = true): BaseCreator - { - $db = $this->connect($group, $shared); - - return static::$factory->loadCreator($db); - } - - /** - * Retourne une nouvelle de la classe Database Utilities. - * - * @param array|ConnectionInterface|string|null $group - */ - public function utils($group = null): BaseUtils - { - $db = $this->connect($group); - - return static::$factory->loadUtils($db); - } - - /** - * S'assure que le gestionnaire de la base de données est chargé et prêt à être utiliser. - */ - protected static function ensureFactory() - { - if (static::$factory instanceof Database) { - return; - } - - static::$factory = new Database(); - } -} diff --git a/src/Database.php b/src/Database.php deleted file mode 100644 index c0cc7ab..0000000 --- a/src/Database.php +++ /dev/null @@ -1,223 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace BlitzPHP\Database; - -use BlitzPHP\Contracts\Database\ConnectionInterface; -use BlitzPHP\Database\Builder\BaseBuilder; -use BlitzPHP\Database\Connection\BaseConnection; -use BlitzPHP\Database\Creator\BaseCreator; -use InvalidArgumentException; - -/** - * Usine de connexion de base de données - * - * Crée et renvoie une instance de la DatabaseConnection appropriée - */ -class Database -{ - /** - * La seule instance d'utilisation de la classe - * - * @var object - */ - protected static $_instance; - - /** - * Maintient un tableau des instances de toutes les connexions qui ont été créé. - * - * Aide à garder une trace de toutes les connexions ouvertes pour les performances, surveillance, journalisation, etc. - * - * @var array - */ - protected $connections = []; - - /** - * Vérifie, instancie et renvoie la seule instance de la classe appelée. - * - * @return static - */ - public static function instance() - { - if (! (static::$_instance instanceof static)) { - $params = func_get_args(); - static::$_instance = new static(...$params); - } - - return static::$_instance; - } - - /** - * Renvoie une instance du pilote prêt à l'emploi. - * - * @throws InvalidArgumentException - * - * @uses self::load - */ - public static function connection(array $params = [], string $alias = '', ...$arguments): ConnectionInterface - { - return self::instance()->load($params, $alias, ...$arguments); - } - - /** - * Analyse les liaisons de connexion et renvoie une instance du pilote prêt à l'emploi. - * - * @throws InvalidArgumentException - */ - public function load(array $params = [], string $alias = '', ...$arguments): ConnectionInterface - { - if ($alias === '') { - throw new InvalidArgumentException('You must supply the parameter: alias.'); - } - - if (! empty($params['dsn']) && str_contains($params['dsn'], '://')) { - $params = $this->parseDSN($params); - } - - if (empty($params['driver'])) { - throw new InvalidArgumentException('You have not selected a database type to connect to.'); - } - - $this->connections[$alias] = $this->initDriver($params['driver'], 'Connection', $params, ...$arguments); - - return $this->connections[$alias]; - } - - /** - * Renvoie une instance Builder adaptee au pilote et prêt à l'emploi. - * - * @throws InvalidArgumentException - * - * @uses self::loadBuilder - */ - public static function builder(ConnectionInterface $db): BaseBuilder - { - return self::instance()->loadBuilder($db); - } - - /** - * Renvoie une instance Creator adaptee au pilote et prêt à l'emploi. - * - * @throws InvalidArgumentException - * - * @uses self::loadCreator - */ - public static function creator(ConnectionInterface $db): BaseCreator - { - return self::instance()->loadCreator($db); - } - - /** - * Crée une instance Creator pour le type de base de données actuel. - */ - public function loadCreator(ConnectionInterface $db): BaseCreator - { - if (! $db->conn) { - $db->initialize(); - } - - return $this->initDriver($db->driver, 'Creator', $db); - } - - /** - * Crée une instance Builder pour le type de base de données actuel. - */ - public function loadBuilder(ConnectionInterface $db): BaseBuilder - { - if (! $db->conn) { - $db->initialize(); - } - - return $this->initDriver($db->driver, 'Builder', $db); - } - - /** - * Crée une instance Utils pour le type de base de données actuel. - */ - public function loadUtils(ConnectionInterface $db): object - { - if (! $db->conn) { - $db->initialize(); - } - - return $this->initDriver($db->driver, 'Utils', $db); - } - - /** - * Analyser la chaîne DSN universelle - * - * @throws InvalidArgumentException - */ - protected function parseDSN(array $params): array - { - $dsn = parse_url($params['dsn']); - - if (! $dsn) { - throw new InvalidArgumentException('Your DSN connection string is invalid.'); - } - - $dsnParams = [ - 'dsn' => '', - 'driver' => $dsn['scheme'], - 'hostname' => isset($dsn['host']) ? rawurldecode($dsn['host']) : '', - 'port' => isset($dsn['port']) ? rawurldecode((string) $dsn['port']) : '', - 'username' => isset($dsn['user']) ? rawurldecode($dsn['user']) : '', - 'password' => isset($dsn['pass']) ? rawurldecode($dsn['pass']) : '', - 'database' => isset($dsn['path']) ? rawurldecode(substr($dsn['path'], 1)) : '', - ]; - - if (! empty($dsn['query'])) { - parse_str($dsn['query'], $extra); - - foreach ($extra as $key => $val) { - if (is_string($val) && in_array(strtolower($val), ['true', 'false', 'null'], true)) { - $val = $val === 'null' ? null : filter_var($val, FILTER_VALIDATE_BOOLEAN); - } - - $dsnParams[$key] = $val; - } - } - - return array_merge($params, $dsnParams); - } - - /** - * Initialiser le pilote de base de données. - * - * @param array|object $argument - * @param mixed $params - * - * @return BaseBuilder|BaseConnection|BaseCreator|BaseUtils - */ - protected function initDriver(string $driver, string $class, $params, ...$arguments): object - { - $driver = str_ireplace('pdo', '', $driver); - $driver = str_ireplace( - ['mysql', 'pgsql', 'sqlite'], - ['MySQL', 'Postgre', 'SQLite'], - $driver - ); - - $class = $class . '\\' . $driver; - - if (! str_contains($driver, '\\')) { - $class = "\\BlitzPHP\\Database\\{$class}"; - } - - if (is_array($params)) { - if (isset($params['host']) && ! isset($params['hostname'])) { - $params['hostname'] = $params['host']; - } - } - - return new $class($params, ...$arguments); - } -} diff --git a/src/DatabaseManager.php b/src/DatabaseManager.php new file mode 100644 index 0000000..75606ce --- /dev/null +++ b/src/DatabaseManager.php @@ -0,0 +1,272 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace BlitzPHP\Database; + +use BlitzPHP\Contracts\Database\ConnectionInterface; +use BlitzPHP\Contracts\Database\ConnectionResolverInterface; +use BlitzPHP\Contracts\Event\EventManagerInterface; +use BlitzPHP\Database\Builder\BaseBuilder; +use BlitzPHP\Database\Creator\BaseCreator; +use InvalidArgumentException; +use Psr\Log\LoggerInterface; + +/** + * Gestionnaire de bases de données + * + * Responsabilités: + * - Résolution des connexions + * - Gestion des instances partagées + * - Création des builders/creators + * - Parsing DSN + */ +class DatabaseManager implements ConnectionResolverInterface +{ + /** + * Connexion par défaut + */ + protected string $defaultConnection = 'default'; + + /** + * Instances de connexions partagées + * + * @var array + */ + protected array $connections = []; + + /** + * Constructeur + * + * @param ?LoggerInterface $logger Logger + * @param ?EventManagerInterface $event Event Manager + */ + public function __construct(protected ?LoggerInterface $logger = null, protected ?EventManagerInterface $event = null) + { + } + + /** + * {@inheritDoc} + */ + public function connection(?string $name = null): ConnectionInterface + { + return $this->connect($name ?: $this->defaultConnection); + } + + /** + * {@inheritDoc} + * + * @param array|ConnectionInterface|string|null $group + */ + public function connect($group = null, bool $shared = true): ConnectionInterface + { + // Si on a déjà une connexion, on la retourne directement + if ($group instanceof ConnectionInterface) { + return $group; + } + + [$groupName, $config] = $this->connectionInfo($group); + + if ($shared && isset($this->connections[$groupName])) { + return $this->connections[$groupName]; + } + + $connection = $this->createConnection($config); + + if ($shared) { + $this->connections[$groupName] = $connection; + } + + return $connection; + } + + /** + * {@inheritDoc} + * + * @return array{0: string, 1: array} [nom_du_groupe, configuration] + */ + public function connectionInfo(array|string|null $group = null): array + { + // Si c'est un tableau, c'est une configuration ad-hoc + if (is_array($group)) { + $config = $group; + $groupName = 'custom-' . md5(json_encode($config)); + + return [$groupName, $config]; + } + + $config = config('database'); + + if (empty($group)) { + $group = $config['connection'] ?? 'auto'; + } + + if ($group === 'auto') { + $group = environment(); + } + + // Fallback vers default si le groupe n'existe pas + if (!isset($config[$group]) && $group !== 'default' && !str_starts_with($group, 'custom-')) { + $group = 'default'; + } + + if (!isset($config[$group])) { + throw new InvalidArgumentException("Le groupe de connexion '{$group}' n'est pas configuré."); + } + + $connectionConfig = $config[$group]; + + // Traitement spécial pour SQLite + if ($connectionConfig['driver'] === 'sqlite') { + $connectionConfig = $this->resolveSqlitePath($connectionConfig); + } + + return [$group, $connectionConfig]; + } + + /** + * Crée un builder pour une connexion + */ + public function builder(ConnectionInterface $db): BaseBuilder + { + return new BaseBuilder($db); + } + + /** + * Crée un creator pour une connexion + */ + public function creator(ConnectionInterface $db): BaseCreator + { + $driver = $this->normalizeDriver($db->getDriver()); + $className = "BlitzPHP\\Database\\Creator\\{$driver}"; + + return new $className($db); + } + + /** + * {@inheritDoc} + */ + public function getDefaultConnection(): string + { + return $this->defaultConnection; + } + + /** + * {@inheritDoc} + */ + public function setDefaultConnection(string $name): void + { + $this->defaultConnection = $name; + } + + /** + * Retourne toutes les connexions établies + */ + public function getConnections(): array + { + return $this->connections; + } + + /** + * Ferme toutes les connexions + */ + public function closeAll(): void + { + foreach ($this->connections as $connection) { + $connection->close(); + } + $this->connections = []; + } + + /** + * Résout le chemin pour SQLite + */ + protected function resolveSqlitePath(array $config): array + { + if ($config['database'] === ':memory:') { + return $config; + } + + if (! str_contains($config['database'], DIRECTORY_SEPARATOR)) { + $config['database'] = defined('APP_STORAGE_PATH') + ? APP_STORAGE_PATH . $config['database'] + : $config['database']; + } + + return $config; + } + + /** + * Crée une instance de connexion + */ + protected function createConnection(array $config): ConnectionInterface + { + // Parser le DSN si nécessaire + if (!empty($config['dsn']) && str_contains($config['dsn'], '://')) { + $config = $this->parseDSN($config); + } + + $driver = $this->normalizeDriver($config['driver'] ?? ''); + + $className = "BlitzPHP\\Database\\Connection\\{$driver}"; + + return new $className($config, $this->logger, $this->event); + } + + /** + * Normalise le nom du driver + */ + protected function normalizeDriver(string $driver): string + { + // Enlever 'pdo' du nom si présent + $driver = str_ireplace('pdo', '', $driver); + + return match (strtolower($driver)) { + 'mysql' => 'MySQL', + 'pgsql', 'postgre', 'postgresql' => 'Postgre', + 'sqlite' => 'SQLite', + default => throw new InvalidArgumentException("Driver non supporté : {$driver}") + }; + } + + /** + * Parse une chaîne DSN universelle + */ + protected function parseDSN(array $params): array + { + $dsn = parse_url($params['dsn']); + + if (!$dsn) { + throw new InvalidArgumentException('La chaîne DSN est invalide.'); + } + + $dsnParams = [ + 'dsn' => '', + 'driver' => $dsn['scheme'], + 'hostname' => rawurldecode($dsn['host'] ?? ''), + 'port' => rawurldecode((string) ($dsn['port'] ?? '')), + 'username' => rawurldecode($dsn['user'] ?? ''), + 'password' => rawurldecode($dsn['pass'] ?? ''), + 'database' => isset($dsn['path']) ? rawurldecode(substr($dsn['path'], 1)) : '', + ]; + + if (!empty($dsn['query'])) { + parse_str($dsn['query'], $extra); + foreach ($extra as $key => $val) { + if (is_string($val) && in_array(strtolower($val), ['true', 'false', 'null'], true)) { + $val = $val === 'null' ? null : filter_var($val, FILTER_VALIDATE_BOOLEAN); + } + $dsnParams[$key] = $val; + } + } + + return array_merge($params, $dsnParams); + } +} diff --git a/src/Providers/DatabaseProvider.php b/src/Providers/DatabaseProvider.php index e2e1fc2..58ed00e 100644 --- a/src/Providers/DatabaseProvider.php +++ b/src/Providers/DatabaseProvider.php @@ -14,7 +14,8 @@ use BlitzPHP\Container\AbstractProvider; use BlitzPHP\Contracts\Database\ConnectionInterface; use BlitzPHP\Contracts\Database\ConnectionResolverInterface; -use BlitzPHP\Database\ConnectionResolver; +use BlitzPHP\Database\Config\Services; +use BlitzPHP\Database\DatabaseManager; class DatabaseProvider extends AbstractProvider { @@ -24,7 +25,7 @@ class DatabaseProvider extends AbstractProvider public static function definitions(): array { return [ - ConnectionResolverInterface::class => static fn () => new ConnectionResolver(), + ConnectionResolverInterface::class => static fn () => new DatabaseManager(Services::logger(), Services::event()), ConnectionInterface::class => static fn (ConnectionResolverInterface $resolver) => $resolver->connect(), ]; } From d47340029f1cf866eee9afaea994a6e74c731cdc Mon Sep 17 00:00:00 2001 From: Dimitri Sitchet Tomkeu Date: Tue, 24 Feb 2026 20:44:21 +0100 Subject: [PATCH 2/3] fix bugs --- src/Builder/BaseBuilder.php | 2 +- src/Connection/BaseConnection.php | 22 +++++++++++++++++++++- src/Query/Result.php | 6 +++++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/Builder/BaseBuilder.php b/src/Builder/BaseBuilder.php index 5ffbcdc..3735da1 100644 --- a/src/Builder/BaseBuilder.php +++ b/src/Builder/BaseBuilder.php @@ -794,7 +794,7 @@ public function query(string $sql, array $params = []) */ public function result(int|string $type = PDO::FETCH_OBJ): array { - return $this->execute()->result($type); + return $this->execute()->get($type); } /** diff --git a/src/Connection/BaseConnection.php b/src/Connection/BaseConnection.php index 22a4059..0a653de 100644 --- a/src/Connection/BaseConnection.php +++ b/src/Connection/BaseConnection.php @@ -595,7 +595,11 @@ public function escapeIdentifiers(mixed $item): mixed */ protected function escapeIdentifier(string $item): string { - return $this->escapeChar . str_replace($this->escapeChar, $this->escapeChar . $this->escapeChar, $item) . $this->escapeChar; + if ($this->isEscapedIdentifier($item)) { + return $item; + } + + return $this->escapeChar . $item . $this->escapeChar; } /** @@ -606,6 +610,22 @@ protected function isReserved(string $item): bool return in_array($item, ['*'], true); } + /** + * Determine si une chaine est échappée comme un identifiant SQL + */ + public function isEscapedIdentifier(string $value): bool + { + if ($value === '') { + return false; + } + + $value = trim($value); + + return str_starts_with($value, $this->escapeChar) + // && str_contains($value, '.') + && str_ends_with($value, $this->escapeChar); + } + /** * Crée le nom de la table avec son alias et le prefix des table de la base de données */ diff --git a/src/Query/Result.php b/src/Query/Result.php index e5bf69b..a065541 100644 --- a/src/Query/Result.php +++ b/src/Query/Result.php @@ -206,7 +206,11 @@ public function get(int|string $type = PDO::FETCH_OBJ): array public function result(int $mode = PDO::FETCH_OBJ, ?string $className = null): array { - $this->statement->setFetchMode($mode, $className); + if ($mode === PDO::FETCH_CLASS) { + $this->statement->setFetchMode($mode, $className); + } else { + $this->statement->setFetchMode($mode); + } $data = $this->statement->fetchAll(); From 2c4c182fa13e533aa93713c70fde033aad55f600 Mon Sep 17 00:00:00 2001 From: Dimitri Sitchet Tomkeu Date: Wed, 25 Feb 2026 14:28:51 +0100 Subject: [PATCH 3/3] =?UTF-8?q?chore:=20suppression=20de=20fichier=20non?= =?UTF-8?q?=20utilis=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Model.php | 2 +- src/Query.php | 429 -------------------------------------------------- 2 files changed, 1 insertion(+), 430 deletions(-) delete mode 100644 src/Query.php diff --git a/src/Model.php b/src/Model.php index ca988d9..676b0db 100644 --- a/src/Model.php +++ b/src/Model.php @@ -17,7 +17,7 @@ use BlitzPHP\Database\Builder\BaseBuilder; use BlitzPHP\Database\Connection\BaseConnection; use BlitzPHP\Database\Exceptions\DataException; -use BlitzPHP\Utilities\Date; +use BlitzPHP\Utilities\DateTime\Date; use Closure; use InvalidArgumentException; use ReflectionClass; diff --git a/src/Query.php b/src/Query.php deleted file mode 100644 index 33782be..0000000 --- a/src/Query.php +++ /dev/null @@ -1,429 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace BlitzPHP\Database; - -use BlitzPHP\Contracts\Database\ConnectionInterface; - -/** - * Query builder - */ -class Query -{ - /** - * The query string, as provided by the user. - * - * @var string - */ - protected $originalQueryString; - - /** - * The query string if table prefix has been swapped. - * - * @var string|null - */ - protected $swappedQueryString; - - /** - * The final query string after binding, etc. - * - * @var string|null - */ - protected $finalQueryString; - - /** - * The binds and their values used for binding. - * - * @var array - */ - protected $binds = []; - - /** - * Bind marker - * - * Character used to identify values in a prepared statement. - * - * @var string - */ - protected $bindMarker = '?'; - - /** - * The start time in seconds with microseconds - * for when this query was executed. - * - * @var float|string - */ - protected $startTime; - - /** - * The end time in seconds with microseconds - * for when this query was executed. - * - * @var float - */ - protected $endTime; - - /** - * The error code, if any. - * - * @var int - */ - protected $errorCode; - - /** - * The error message, if any. - * - * @var string - */ - protected $errorString; - - /** - * Pointer to database connection. - * Mainly for escaping features. - * - * @var ConnectionInterface - */ - public $db; - - public function __construct(ConnectionInterface $db) - { - $this->db = $db; - } - - /** - * Sets the raw query string to use for this statement. - * - * @param mixed $binds - * - * @return $this - */ - public function setQuery(string $sql, $binds = null, bool $setEscape = true) - { - $this->originalQueryString = $sql; - unset($this->swappedQueryString); - - if ($binds !== null) { - if (! is_array($binds)) { - $binds = [$binds]; - } - - if ($setEscape) { - array_walk($binds, static function (&$item) { - $item = [ - $item, - true, - ]; - }); - } - $this->binds = $binds; - } - - unset($this->finalQueryString); - - return $this; - } - - /** - * Will store the variables to bind into the query later. - * - * @return $this - */ - public function setBinds(array $binds, bool $setEscape = true) - { - if ($setEscape) { - array_walk($binds, static function (&$item) { - $item = [$item, true]; - }); - } - - $this->binds = $binds; - - unset($this->finalQueryString); - - return $this; - } - - /** - * Returns the final, processed query string after binding, etal - * has been performed. - */ - public function getQuery(): string - { - if (empty($this->finalQueryString)) { - $this->compileBinds(); - } - - return $this->finalQueryString; - } - - /** - * Records the execution time of the statement using microtime(true) - * for it's start and end values. If no end value is present, will - * use the current time to determine total duration. - * - * @return $this - */ - public function setDuration(float $start, ?float $end = null) - { - $this->startTime = $start; - - if ($end === null) { - $end = microtime(true); - } - - $this->endTime = $end; - - return $this; - } - - /** - * Returns the start time in seconds with microseconds. - * - * @return float|string - */ - public function getStartTime(bool $returnRaw = false, int $decimals = 6) - { - if ($returnRaw) { - return $this->startTime; - } - - return number_format($this->startTime, $decimals); - } - - /** - * Returns the duration of this query during execution, or null if - * the query has not been executed yet. - * - * @param int $decimals The accuracy of the returned time. - */ - public function getDuration(int $decimals = 6): string - { - return number_format(($this->endTime - $this->startTime), $decimals); - } - - /** - * Stores the error description that happened for this query. - * - * @return $this - */ - public function setError(int $code, string $error) - { - $this->errorCode = $code; - $this->errorString = $error; - - return $this; - } - - /** - * Reports whether this statement created an error not. - */ - public function hasError(): bool - { - return ! empty($this->errorString); - } - - /** - * Returns the error code created while executing this statement. - */ - public function getErrorCode(): int - { - return $this->errorCode; - } - - /** - * Returns the error message created while executing this statement. - */ - public function getErrorMessage(): string - { - return $this->errorString; - } - - /** - * Determines if the statement is a write-type query or not. - */ - public function isWriteType(): bool - { - return $this->db->isWriteType($this->originalQueryString); - } - - /** - * Swaps out one table prefix for a new one. - * - * @return $this - */ - public function swapPrefix(string $orig, string $swap) - { - $sql = $this->swappedQueryString ?? $this->originalQueryString; - - $from = '/(\W)' . $orig . '(\S)/'; - $to = '\\1' . $swap . '\\2'; - - $this->swappedQueryString = preg_replace($from, $to, $sql); - - unset($this->finalQueryString); - - return $this; - } - - /** - * Returns the original SQL that was passed into the system. - */ - public function getOriginalQuery(): string - { - return $this->originalQueryString; - } - - /** - * Escapes and inserts any binds into the finalQueryString property. - * - * @see https://regex101.com/r/EUEhay/5 - */ - protected function compileBinds() - { - $sql = $this->swappedQueryString ?? $this->originalQueryString; - $binds = $this->binds; - - if (empty($binds)) { - $this->finalQueryString = $sql; - - return; - } - - if (is_int(array_key_first($binds))) { - $bindCount = count($binds); - $ml = strlen($this->bindMarker); - - $this->finalQueryString = $this->matchSimpleBinds($sql, $binds, $bindCount, $ml); - } else { - // Reverse the binds so that duplicate named binds - // will be processed prior to the original binds. - $binds = array_reverse($binds); - - $this->finalQueryString = $this->matchNamedBinds($sql, $binds); - } - } - - /** - * Match bindings - */ - protected function matchNamedBinds(string $sql, array $binds): string - { - $replacers = []; - - foreach ($binds as $placeholder => $value) { - // $value[1] contains the boolean whether should be escaped or not - $escapedValue = $value[1] ? $this->db->escape($value[0]) : $value[0]; - - // In order to correctly handle backlashes in saved strings - // we will need to preg_quote, so remove the wrapping escape characters - // otherwise it will get escaped. - if (is_array($value[0])) { - $escapedValue = '(' . implode(',', $escapedValue) . ')'; - } - - $replacers[":{$placeholder}:"] = $escapedValue; - } - - return strtr($sql, $replacers); - } - - /** - * Match bindings - */ - protected function matchSimpleBinds(string $sql, array $binds, int $bindCount, int $ml): string - { - if ($c = preg_match_all("/'[^']*'/", $sql, $matches)) { - $c = preg_match_all('/' . preg_quote($this->bindMarker, '/') . '/i', str_replace($matches[0], str_replace($this->bindMarker, str_repeat(' ', $ml), $matches[0]), $sql, $c), $matches, PREG_OFFSET_CAPTURE); - - // Bind values' count must match the count of markers in the query - if ($bindCount !== $c) { - return $sql; - } - } elseif (($c = preg_match_all('/' . preg_quote($this->bindMarker, '/') . '/i', $sql, $matches, PREG_OFFSET_CAPTURE)) !== $bindCount) { - return $sql; - } - - do { - $c--; - $escapedValue = $binds[$c][1] ? $this->db->escape($binds[$c][0]) : $binds[$c][0]; - - if (is_array($escapedValue)) { - $escapedValue = '(' . implode(',', $escapedValue) . ')'; - } - - $sql = substr_replace($sql, $escapedValue, $matches[0][$c][1], $ml); - } while ($c !== 0); - - return $sql; - } - - /** - * Returns string to display in debug toolbar - */ - public function debugToolbarDisplay(): string - { - // Key words we want bolded - static $highlight = [ - 'AND', - 'AS', - 'ASC', - 'AVG', - 'BY', - 'COUNT', - 'DESC', - 'DISTINCT', - 'FROM', - 'GROUP', - 'HAVING', - 'IN', - 'INNER', - 'INSERT', - 'INTO', - 'IS', - 'JOIN', - 'LEFT', - 'LIKE', - 'LIMIT', - 'MAX', - 'MIN', - 'NOT', - 'NULL', - 'OFFSET', - 'ON', - 'OR', - 'ORDER', - 'RIGHT', - 'SELECT', - 'SUM', - 'UPDATE', - 'VALUES', - 'WHERE', - ]; - - $sql = htmlspecialchars($this->getQuery(), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8', true); - - /** - * @see https://stackoverflow.com/a/20767160 - * @see https://regex101.com/r/hUlrGN/4 - */ - $search = '/\b(?:' . implode('|', $highlight) . ')\b(?![^(')]*'(?:(?:[^(')]*'){2})*[^(')]*$)/'; - - return preg_replace_callback($search, static fn ($matches) => '' . str_replace(' ', ' ', $matches[0]) . '', $sql); - } - - /** - * Return text representation of the query - */ - public function __toString(): string - { - return $this->getQuery(); - } -}