nils-framework/nils-database
最新稳定版本:v1.0
Composer 安装命令:
composer require nils-framework/nils-database
包简介
Composant d'accès aux données, système de migration et Query Builder fluide pour le framework NILS
README 文档
README
Couche d'accès aux données du framework NILS : un module léger, sans dépendance lourde, construit directement au-dessus de PDO. Il fournit une façade de connexion résiliente, un générateur de requêtes fluide, un système de migrations et un outil d'introspection de schéma, le tout compatible MySQL, MariaDB, PostgreSQL et SQLite.
La philosophie est volontairement minimaliste : pas d'attributs, pas de génération de proxys, pas de conteneur. Une API statique simple et des requêtes préparées partout.
Sommaire
- Caractéristiques
- Prérequis
- Installation
- Initialisation
- Exécution de requêtes
- Transactions
- QueryBuilder
- Migrations & schéma
- Introspection (SchemaReader)
- Pilotes (Drivers)
- Sécurité
- Gestion des erreurs
- Limitations & notes importantes
- Architecture
- Tests
- Référence rapide de l'API
Caractéristiques
- Multi-SGBD : MySQL, MariaDB, PostgreSQL, SQLite via une interface de pilote unifiée.
- Connexion paresseuse (lazy loading) en pattern Singleton, compatible workers asynchrones (Swoole, RoadRunner, FrankenPHP).
- Rejeu réseau sécurisé : reconnexion automatique sur coupure, restreinte aux requêtes de lecture hors transaction.
- Requêtes préparées systématiques et échappement strict des identifiants (anti-injection SQL).
- QueryBuilder fluide pour les SELECT, avec liste blanche d'opérateurs et bindings nommés anti-collision.
- Migrations déclaratives via un
Blueprintorienté objet. - Introspection (rétro-ingénierie) du schéma existant vers des
Blueprint. - Curseur paresseux par générateur pour le parcours de gros volumes.
Prérequis
- PHP ≥ 8.0 (utilise
match, la promotion de propriétés, les arguments nommés, l'opérateur nullsafe). - Extension PDO activée, ainsi que le pilote PDO correspondant à votre SGBD :
pdo_mysql(MySQL / MariaDB)pdo_pgsql(PostgreSQL)pdo_sqlite(SQLite)
Installation
Via Composer (ajustez le nom du paquet à celui déclaré dans votre composer.json) :
composer require nils/database
Puis chargez l'autoloader :
require 'vendor/autoload.php';
L'autoload PSR-4 mappe le namespace NilsDatabase\ sur le dossier src/.
Initialisation
Le module est agnostique vis-à-vis de votre gestion de configuration : on lui injecte un simple tableau associatif via Database::initialiser(). La connexion réelle n'est ouverte qu'au premier accès (lazy loading).
use NilsDatabase\Database;
Database::initialiser([
'driver' => 'mysql', // mysql | mariadb | pgsql | postgresql | sqlite
'host' => '127.0.0.1',
'port' => 3306, // optionnel (défaut : 3306, ou 5432 pour PostgreSQL)
'database' => 'nils_core',
'username' => 'root',
'password' => 'secret',
'charset' => 'utf8mb4', // optionnel (défaut : utf8mb4)
'timeout' => 5, // optionnel (défaut : 5 s)
]);
SQLite
Database::initialiser([
'driver' => 'sqlite',
'database' => '/chemin/vers/base.sqlite', // ou ':memory:'
]);
Clés de configuration
| Clé | Obligatoire | Défaut | Remarque |
|---|---|---|---|
driver | non | mysql | mysql, mariadb, pgsql, postgresql, sqlite |
host | non | 127.0.0.1 | ignoré pour SQLite |
port | non | 3306 / 5432 | ignoré pour SQLite |
database | oui | — | nom de la base, ou chemin du fichier pour SQLite |
username | non | null | ignoré pour SQLite |
password | non | null | ignoré pour SQLite |
charset | non | utf8mb4 | liste blanche : utf8mb4, utf8, utf8mb3, latin1, ascii |
timeout | non | 5 | délai de connexion en secondes |
Cycle de vie de la connexion
Dans un contexte de worker persistant (Swoole, RoadRunner…), fermez la connexion entre deux cycles afin de réinitialiser l'état du Singleton :
Database::fermer();
Exécution de requêtes
Toutes les méthodes acceptent des marqueurs nommés (:nom) ou anonymes (?) et lient les valeurs de manière sécurisée.
// Plusieurs lignes -> array[]
$lignes = Database::recupererTout(
"SELECT * FROM utilisateurs WHERE actif = :actif",
['actif' => 1]
);
// Une seule ligne -> array|null
$user = Database::recupererUn(
"SELECT * FROM utilisateurs WHERE id = :id",
['id' => 42]
);
// Insertion -> int (identifiant généré)
$id = Database::inserer(
"INSERT INTO utilisateurs (nom, email) VALUES (:nom, :email)",
['nom' => 'Léa', 'email' => 'lea@exemple.fr']
);
// Mise à jour -> int (nombre de lignes affectées)
$n = Database::mettreAJour(
"UPDATE utilisateurs SET actif = :a WHERE id = :id",
['a' => 0, 'id' => 42]
);
// Suppression -> int (nombre de lignes supprimées)
$n = Database::supprimer(
"DELETE FROM utilisateurs WHERE id = :id",
['id' => 42]
);
// Méthode bas niveau -> \PDOStatement
$stmt = Database::executer("SELECT COUNT(*) AS total FROM utilisateurs");
Curseur paresseux (gros volumes)
Pour parcourir un grand jeu de résultats sans tout charger d'un coup :
foreach (Database::curseur("SELECT * FROM journal") as $ligne) {
traiter($ligne);
}
Note de performance. Sur MySQL / MariaDB, PDO bufferise par défaut l'ensemble du résultat côté client. Pour un véritable streaming sur ces moteurs, il faut activer
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY = false(non activé par défaut car la connexion du Singleton est partagée). Sur PostgreSQL et SQLite, le comportement diffère.
Transactions
Database::debut();
try {
Database::inserer("INSERT INTO comptes (solde) VALUES (:s)", ['s' => 100]);
Database::mettreAJour("UPDATE comptes SET solde = solde - :m WHERE id = :id", ['m' => 30, 'id' => 1]);
Database::valider(); // COMMIT
} catch (\Throwable $e) {
Database::annuler(); // ROLLBACK
throw $e;
}
L'ouverture est idempotente : debut() ne tente pas de transaction imbriquée si une transaction est déjà active. valider() et annuler() ne font rien s'il n'y a aucune transaction en cours.
QueryBuilder
Générateur fluide pour les requêtes SELECT. Il s'instancie (il n'est pas statique) et s'auto-réinitialise après chaque exécution.
use NilsDatabase\QueryBuilder;
$resultats = (new QueryBuilder())
->table('utilisateurs')
->select(['id', 'nom', 'email'])
->where('actif', '=', 1)
->where('nom', 'LIKE', '%traore%')
->whereIn('role', ['admin', 'editeur'])
->orderBy('created_at DESC')
->limit(20)
->offset(0)
->recupererTout();
$premier = (new QueryBuilder())
->table('utilisateurs')
->where('email', '=', 'lea@exemple.fr')
->recupererUn(); // array|null
Opérateurs autorisés (clause where)
Liste blanche stricte : =, !=, <, >, <=, >=, LIKE, NOT LIKE. Tout autre opérateur lève une QueryException.
Conditions ensemblistes (IN / NOT IN)
Les comparaisons à une liste passent par whereIn() (et non par where(), qui rejette les tableaux) :
$qb->whereIn('statut', ['actif', 'en_attente']); // IN (...)
$qb->whereIn('statut', ['supprime', 'banni'], true); // NOT IN (...)
Une liste vide produit une condition toujours fausse (IN) ou toujours vraie (NOT IN), ce qui évite un SQL invalide.
Tri (orderBy)
Comme les clauses de tri ne sont pas paramétrables, la chaîne est validée par une expression régulière stricte n'autorisant que colonne ou table.colonne, suivie en option de ASC / DESC. Toute autre forme lève une QueryException.
Méthodes utilitaires
$sql = $qb->versSql(); // chaîne SQL compilée (débogage)
$qb->reinitialiser(); // remet l'instance à zéro (appelée automatiquement après recupererTout/recupererUn)
Migrations & schéma
Définir une migration
Chaque migration hérite de la classe abstraite Migration et implémente up() (application) et down() (retour arrière).
use NilsDatabase\Migration\Migration;
use NilsDatabase\Migration\Schema;
use NilsDatabase\Migration\Blueprint;
class CreerTableArticles extends Migration
{
public function up(): void
{
Schema::create('articles', function (Blueprint $table) {
$table->id();
$table->string('titre', 200);
$table->string('slug', 200);
$table->integer('auteur_id');
$table->boolean('publie');
$table->timestamps();
$table->unique('slug');
$table->index('auteur_id');
$table->foreign('auteur_id', 'utilisateurs', 'id')
->onDelete('cascade')
->onUpdate('cascade');
});
}
public function down(): void
{
Schema::dropIfExists('articles');
}
}
Schema::create() compile le Blueprint puis exécute les requêtes dans une transaction (voir l'avertissement DDL dans les limitations).
API du Blueprint
| Méthode | Effet |
|---|---|
id(string $nom = 'id') | Clé primaire entière auto-incrémentée |
string(string $nom, int $longueur = 255) | Colonne VARCHAR |
integer(string $nom) | Colonne INT |
boolean(string $nom) | Colonne booléenne (BOOLEAN / TINYINT(1)) |
timestamps() | Colonnes created_at et updated_at |
unique(string $colonne) | Contrainte UNIQUE |
index(string $colonne) | Index de recherche (CREATE INDEX séparé) |
foreign(string $local, string $tableRef, string $colRef = 'id') | Clé étrangère, renvoie un ForeignKey chaînable |
Sur l'objet ForeignKey :
->onDelete('cascade') // RESTRICT (défaut) | CASCADE | SET NULL | NO ACTION
->onUpdate('cascade')
Supprimer une table
Schema::dropIfExists('articles');
Introspection (SchemaReader)
Rétro-ingénierie d'une base existante vers des Blueprint.
use NilsDatabase\Migration\SchemaReader;
// Une table -> Blueprint
$blueprint = SchemaReader::lireTable('utilisateurs');
// Toute la base -> ['nom_table' => Blueprint, ...]
$schema = SchemaReader::lireBaseDeDonnees();
// Le nom de base est facultatif : à défaut, celui passé à Database::initialiser() est utilisé.
L'introspection interroge INFORMATION_SCHEMA (MySQL / PostgreSQL), sqlite_master et les commandes PRAGMA (SQLite).
Pilotes (Drivers)
Chaque pilote implémente DriverInterface :
| Pilote | DSN | Échappement des identifiants |
|---|---|---|
MysqlDriver | mysql:host=…;port=…;dbname=…;charset=… | backticks ` (dédoublés) |
MariaDbDriver | hérité de MysqlDriver | hérité de MysqlDriver |
PostgresDriver | pgsql:host=…;port=…;dbname=… | guillemets doubles " (dédoublés) |
SqliteDriver | sqlite:/chemin ou sqlite::memory: | guillemets doubles " (dédoublés) |
DriverInterface expose deux méthodes : construireDsn() et echapperIdentifiant(). Le pilote actif est récupérable via Database::obtenirPilote().
Sécurité
- Requêtes préparées sur tous les chemins d'exécution : les valeurs ne sont jamais interpolées directement.
- Échappement des identifiants : noms de tables et de colonnes encadrés par les délimiteurs natifs du SGBD, avec dédoublement des délimiteurs internes (interdit la rupture de chaîne).
- Liste blanche d'opérateurs dans le
QueryBuilder. - Validation stricte de la clause
ORDER BY(regex) et du jeu de caractères (liste blanche), tous deux non paramétrables et donc validés en amont. - Valeurs
DEFAULTdes migrations : encadrées et échappées (guillemets simples dédoublés) lors de la compilation du schéma.
XSS — hors périmètre. Cette couche protège contre l'injection SQL (en entrée vers la base). La défense XSS se traite à l'affichage (échappement HTML au rendu), pas ici : stockez vos données brutes et échappez-les en sortie.
Gestion des erreurs
| Exception | Levée par | Fabriques / cas |
|---|---|---|
DatabaseException | connexion / configuration | connexionEchouee(), driverNonSupporte() |
QueryException | exécution de requêtes, QueryBuilder | requeteEchouee() (opérateur refusé, table absente, échec SQL…) |
\InvalidArgumentException | Blueprint::compiler() | type de colonne inconnu |
Le mode d'erreur PDO est ERRMODE_EXCEPTION et l'émulation des requêtes préparées est désactivée.
Limitations & notes importantes
- DDL non transactionnel sous MySQL / MariaDB.
Schema::create()enveloppe ses requêtes dans une transaction, mais sur ces moteurs les instructions DDL (CREATE TABLE,CREATE INDEX…) provoquent un COMMIT implicite : un échec en cours de route ne peut pas être annulé. La protection par rollback n'est réellement effective que sur PostgreSQL et SQLite. lastInsertId()sous PostgreSQL nécessite le nom de la séquence ; préférezINSERT … RETURNING idconsommé viarecupererUn().- Nullabilité / valeurs par défaut. Les colonnes sont
NOT NULLpar défaut ; l'API fluide duBlueprintn'expose pas encore de réglagenullable()oudefault()(seules les clés étrangères sont chaînables). QueryBuildercouvre uniquement le SELECT ; les écritures passent parDatabase::inserer/mettreAJour/supprimer.- Détection de dialecte par nom de classe.
BlueprintetSchemaReaderdéterminent le SGBD viastr_contains(get_class($driver), …), ce qui reste sensible à un renommage de classe. curseur()et le buffering MySQL : voir la note de la section Exécution de requêtes.
Architecture
nils-database
├── src
│ ├── Database.php # Façade de connexion + moteur d'exécution
│ ├── QueryBuilder.php # Générateur fluide (SELECT)
│ ├── Driver
│ │ ├── DriverInterface.php
│ │ ├── MysqlDriver.php
│ │ ├── MariaDbDriver.php
│ │ ├── PostgresDriver.php
│ │ └── SqliteDriver.php
│ ├── Exception
│ │ ├── DatabaseException.php
│ │ └── QueryException.php
│ └── Migration
│ ├── Migration.php # Classe abstraite up()/down()
│ ├── Schema.php # Façade create()/dropIfExists()
│ ├── Blueprint.php # Modélisation + compiler()
│ ├── Colonne.php # DTO de colonne
│ ├── ForeignKey.php # Contrainte relationnelle
│ └── SchemaReader.php # Introspection
└── tests
├── Unit
├── Integration
└── Functional
Tests
La suite repose sur PHPUnit :
vendor/bin/phpunit
Trois familles de tests : Unit (logique pure : QueryBuilder, Blueprint), Integration (connexion réelle, persistance, exécution de schéma) et Functional (SchemaReader).
Référence rapide de l'API
Database (statique)
initialiser(array $config): void
connecter(): void
fermer(): void
executer(string $sql, array $params = []): \PDOStatement
curseur(string $sql, array $params = []): \Generator
recupererTout(string $sql, array $params = []): array
recupererUn(string $sql, array $params = []): ?array
inserer(string $sql, array $params = []): int
mettreAJour(string $sql, array $params = []): int
supprimer(string $sql, array $params = []): int
debut(): void
valider(): void
annuler(): void
obtenirPilote(): DriverInterface
obtenirNomBase(): string
QueryBuilder (instancié)
table(string $table): self
select(array $colonnes): self
where(string $colonne, string $operateur, mixed $valeur): self
whereIn(string $colonne, array $valeurs, bool $negation = false): self
orderBy(string $clause): self
limit(int $limit): self
offset(int $offset): self
recupererTout(): array
recupererUn(): ?array
versSql(): string
reinitialiser(): self
Schema (statique)
create(string $table, callable $callback): void
dropIfExists(string $table): void
SchemaReader (statique)
lireTable(string $table): Blueprint
lireBaseDeDonnees(string $nomBase = ""): Blueprint[]
Module nils-database — framework NILS. Auteur : Traore.
统计信息
- 总下载量: 1
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 6
- 依赖项目数: 1
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-06-09