<?php declare(strict_types=1); namespace Database; /** * Classe permettant de retourner une instance unique et configurée de PDO. * * Ceci permet de ne pas multiplier les connexions au serveur de base de données. * L'instance peut être configurée de trois façons, utilisées dans cet ordre jusqu'à obtenir une configuration valide : * - programmatique ; MyPDO::setConfiguration(DSN, username, password) * - variables d'environnement ; MYPDO_DSN, MYPDO_USERNAME et MYPDO_PASSWORD * - fichier ; [APP_DIR/].mypdo[.MYPDO_ENV].ini où APP_DIR et MYPDO_ENV sont des variables d'environnement * * @startuml * * namespace Database { * class MyPdo { * - {static} dsn : string * - {static} username : string := '' * - {static} password : string := '' * - {static} options : array := [] * * - __construct(\n\tdsn : string,\n\tusername : string := null,\n\tpassword : string := null,\n\toptions : array := null) * - private __clone() : void * + {static} getInstance() : MyPdo * + {static} setConfiguration(\n\tdsn : string,\n\tusername : string := '',\n\tpassword : string := '',\n\toptions : array := []) : void * - {static} hasConfiguration() : bool * - {static} setConfigurationFromEnvironmentVariables() : bool * - {static} setConfigurationFromIniFile() : bool * } * } * * Database\\MyPdo -left-|> PDO * Database\\MyPdo "1" *-- "1\n-<u>myPdoInstance</u>" Database\\MyPdo : contains * * @enduml */ final class MyPdo extends \PDO { /** * Instance unique de PDO. */ private static self $myPdoInstance; /** * DSN pour la connexion BD. */ private static string $dsn; /** * Nom d'utilisateur pour la connexion BD. */ private static string $username = ''; /** * Mot de passe pour la connexion BD. */ private static string $password = ''; /** * Options du pilote BD. */ private static array $options = [ \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC, ]; /** * Constructeur privé. * * Seule la classe MyPDO peut construire une instance de MyPDO. * * @param string $dsn DSN pour la connexion BD * @param string|null $username Utilisateur pour la connexion BD * @param string|null $password Mot de passe pour la connexion BD * @param array|null $options Options du pilote BD */ private function __construct(string $dsn, string $username = null, string $password = null, array $options = null) { parent::__construct($dsn, $username, $password, $options); // La base de données est-elle de type SQLite if ('sqlite' === $this->getAttribute(\PDO::ATTR_DRIVER_NAME)) { // Activer les clés étrangères qui sont désactivées par défaut $this->exec('PRAGMA foreign_keys = ON'); } } /** * Empêcher le clonage, le singleton doit rester unique. */ private function __clone(): void { } /** * Point d'accès à l'instance unique. * * L'instance est créée au premier appel et réutilisée aux appels suivants. * * @return self Instance unique de MyPdo * * @throws \PDOException Si la configuration n'a pas été effectuée */ public static function getInstance(): self { // Instance de la classe présente ? if (!isset(self::$myPdoInstance)) { // Configuration effectuée ? if (!self::hasConfiguration() && !self::setConfigurationFromEnvironmentVariables() && !self::setConfigurationFromIniFile()) { throw new \PDOException(__CLASS__.': Configuration not set'); } // Construire une instance self::$myPdoInstance = new self(self::$dsn, self::$username, self::$password, self::$options); } return self::$myPdoInstance; } /** * Fixer programmatiquement la configuration de la connexion à la BD. * * @param string $dsn DSN pour la connexion BD * @param string $username Utilisateur pour la connexion BD * @param string $password Mot de passe pour la connexion BD * @param array $options Options du pilote BD * * @throws \PDOException Si la variable d'environnement APP_DIR est utilisée, mais n'est pas définie */ public static function setConfiguration( string $dsn, string $username = '', string $password = '', array $options = [] ): void { self::$dsn = $dsn; self::$username = $username; self::$password = $password; self::$options = $options + self::$options; // Remplacer %APP_DIR% par le chemin de l'application si SQLite est utilisé if (preg_match('/^(.*)(%APP_DIR%)(.*)$/', $dsn, $matches)) { if (!($appDir = getenv('APP_DIR'))) { throw new \PDOException(__CLASS__.': APP_DIR environment variable not set'); } self::$dsn = $matches[1].$appDir.$matches[3]; } } /** * Vérifier si la configuration de la connexion à la BD a été effectuée. */ private static function hasConfiguration(): bool { return isset(self::$dsn); } /** * Lire la configuration depuis des variables d'environnement. * * Les variables sont : * - MYPDO_DSN pour le DSN * - MYPDO_USERNAME pour le nom d'utilisateur * - MYPDO_PASSWORD pour le mot de passe. * * @return bool Vrai si la configuration a été trouvée * * @throws \PDOException Si self::setConfiguration() échoue */ private static function setConfigurationFromEnvironmentVariables(): bool { // DSN ? $dsn = getenv('MYPDO_DSN', true); if (false !== $dsn) { // username et password facultatifs $username = getenv('MYPDO_USERNAME', true) ?: ''; $password = getenv('MYPDO_PASSWORD', true) ?: ''; self::setConfiguration($dsn, $username, $password); return true; } return false; } /** * Lire la configuration depuis un fichier ini. * * Le nom du fichier peut être : * - ".mypdo.ini" * - ".mypdo<.environment_name>.ini" (environment_name dans la variable d'environnement MYPDO_ENV) * Le fichier peut être placé : * - dans un répertoire accessible (https://www.php.net/manual/fr/ini.core.php#ini.include-path) * - dans le répertoire défini par la variable d'environnement APP_DIR * Le fichier contient : * [mypdo] * dsn = ... * username = ... * password = ... * * @return bool Vrai si la configuration a été trouvée * * @throws \PDOException Si le fichier des paramètres est invalide */ private static function setConfigurationFromIniFile(): bool { // Environnement MyPdo défini ? $myPdoEnv = getenv('MYPDO_ENV', true) ?: ''; // Chemin du fichier en fonction de APP_DIR $appDir = getenv('APP_DIR'); $directory = false !== $appDir ? $appDir.DIRECTORY_SEPARATOR : ''; $parameterFile = sprintf('%s.mypdo%s.ini', $directory, $myPdoEnv ? ".$myPdoEnv" : ''); // Lecture du fichier de configuration $parameters = @parse_ini_file($parameterFile, true); if (false !== $parameters) { if (!isset($parameters['mypdo'])) { throw new \PDOException('`mypdo` section not found in `'.basename($parameterFile).'`'); } if (!isset($parameters['mypdo']['dsn'])) { throw new \PDOException('`dsn` not found in `'.basename($parameterFile).'`'); } $dsn = $parameters['mypdo']['dsn']; // username et password facultatifs $username = $parameters['mypdo']['username'] ?? ''; $password = $parameters['mypdo']['password'] ?? ''; self::setConfiguration($dsn, $username, $password); return true; } return false; } } /* Exemple de configuration et d'utilisation use Database\MyPdo; MyPDO::setConfiguration('mysql:host=mysql;dbname=cutron01_music;charset=utf8', 'web', 'web'); $stmt = MyPDO::getInstance()->prepare( <<<'SQL' SELECT id, name FROM artist ORDER BY name SQL ); $stmt->execute(); while (($ligne = $stmt->fetch()) !== false) { echo "<p>{$ligne['name']}\n"; } */