nils-framework/nils-database 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

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

  • 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 Blueprint orienté 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éObligatoireDéfautRemarque
drivernonmysqlmysql, mariadb, pgsql, postgresql, sqlite
hostnon127.0.0.1ignoré pour SQLite
portnon3306 / 5432ignoré pour SQLite
databaseouinom de la base, ou chemin du fichier pour SQLite
usernamenonnullignoré pour SQLite
passwordnonnullignoré pour SQLite
charsetnonutf8mb4liste blanche : utf8mb4, utf8, utf8mb3, latin1, ascii
timeoutnon5dé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éthodeEffet
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 :

PiloteDSNÉchappement des identifiants
MysqlDrivermysql:host=…;port=…;dbname=…;charset=…backticks ` (dédoublés)
MariaDbDriverhérité de MysqlDriverhérité de MysqlDriver
PostgresDriverpgsql:host=…;port=…;dbname=…guillemets doubles " (dédoublés)
SqliteDriversqlite:/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 DEFAULT des 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

ExceptionLevée parFabriques / cas
DatabaseExceptionconnexion / configurationconnexionEchouee(), driverNonSupporte()
QueryExceptionexécution de requêtes, QueryBuilderrequeteEchouee() (opérateur refusé, table absente, échec SQL…)
\InvalidArgumentExceptionBlueprint::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érez INSERT … RETURNING id consommé via recupererUn().
  • Nullabilité / valeurs par défaut. Les colonnes sont NOT NULL par défaut ; l'API fluide du Blueprint n'expose pas encore de réglage nullable() ou default() (seules les clés étrangères sont chaînables).
  • QueryBuilder couvre uniquement le SELECT ; les écritures passent par Database::inserer/mettreAJour/supprimer.
  • Détection de dialecte par nom de classe. Blueprint et SchemaReader déterminent le SGBD via str_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

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-06-09

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固