velt/database
Composer 安装命令:
composer require velt/database
包简介
Velt Database PDO module
README 文档
README
Mise a jour Module 3 Data ORM
Le package contient maintenant les fondations database suivantes, sans modifier le repo veltphp-cli.
Query Builder
use Velt\Database\DB; $user = DB::table('users')->where('email', $email)->first(); $users = DB::table('users')->select('id', 'name')->orderBy('id')->limit(10)->get(); DB::table('users')->insert(['name' => 'Ada', 'email' => 'ada@example.com']); DB::table('users')->where('id', 1)->update(['name' => 'Ada Lovelace']); DB::table('users')->where('id', 1)->delete();
Toutes les valeurs dynamiques passent par des requetes preparees. Les noms de tables et colonnes sont valides comme identifiers SQL avant compilation.
Schema Builder et migrations
use Velt\Database\Schema\Blueprint; use Velt\Database\Schema\Schema; Schema::create('users', function (Blueprint $table): void { $table->id(); $table->string('name'); $table->integer('age'); $table->timestamps(); }); Schema::drop('users');
Le runner de migrations est disponible cote runtime :
use Velt\Database\Migrations\Migrator; $migrator = new Migrator(__DIR__ . '/database/migrations'); $migrator->migrate(); $migrator->rollback();
Les commandes php bin/velt migrate, migrate:rollback, make:migration, make:seeder et db:seed doivent etre ajoutees dans veltphp-cli. Voir issues/06-cli-database-commands.md.
Seeders et factories
use Velt\Database\Seeders\Seeder; final class UserSeeder extends Seeder { public function run(): void { DB::table('users')->insert(['name' => 'Ada', 'email' => 'ada@example.com']); } }
use Velt\Database\Factories\Factory; final class UserFactory extends Factory { public function definition(): array { return ['name' => 'Ada', 'email' => 'ada@example.com']; } protected function table(): string { return 'users'; } }
Cache de resultats
use Velt\Database\Cache\FileDatabaseCache; DB::setCache(new FileDatabaseCache(__DIR__ . '/storage/database-cache')); $users = DB::table('users')->where('active', 1)->remember(60)->get(); DB::cache()->flush();
En environnement testing, DatabaseServiceProvider utilise un cache nul par defaut.
Vue d'ensemble
Ce module fournit une couche d'abstraction de base de données pour le framework Velt, implémentant les 5 premières étapes d'un système de gestion de base de données robuste et sécurisé.
Statut: Complet - 5/5 issues implémentées, 19 tests passants
Objectifs et raisons
Pourquoi ce module?
Le framework Velt nécessite une couche d'accès aux données centralisée, sécurisée et extensible:
- Sécurité: Prévenir les injections SQL via requêtes préparées obligatoires
- Maintenabilité: Isoler la logique de base de données du reste de l'application
- Flexibilité: Supporter plusieurs drivers (SQLite, MySQL, PostgreSQL)
- Performance: Mise en cache des connexions, pas de reconnexions inutiles
- Testabilité: Interfaces mockables et fakes pour les tests
Architecture générale
┌─────────────────────────────────────────────────────────┐
│ Velt Application Kernel │
└────────────────┬────────────────────────────────────────┘
│ registerProvider
▼
┌─────────────────────────────────────────────────────────┐
│ DatabaseServiceProvider (Enregistrement DI) │
│ - Crée singleton DatabaseManager │
└────────────────┬────────────────────────────────────────┘
│ registre comme singleton
▼
┌─────────────────────────────────────────────────────────┐
│ DatabaseManager │
│ - Gère pool de connexions nommées │
│ - Lecture config, création PDO via Factory │
└────────────────┬────────────────────────────────────────┘
│ utilise
▼
┌──────────────────────┬──────────────────────┐
│ ConnectionFactory │ DB (Facade) │
│ - Crée DSN │ - static interface │
│ - Prépare PDO │ - select/first etc. │
└──────────────────────┴──────────────────────┘
│ utilisé par
▼
┌─────────────────────────────────────────────────────────┐
│ Model (Classe de base) │
│ - find(), all(), create() │
│ - Accès simplifié aux données │
└─────────────────────────────────────────────────────────┘
Composants implémentés
1. ConnectionFactory (src/ConnectionFactory.php)
Responsabilité: Construire les chaînes de connexion (DSN) et créer des instances PDO.
Pourquoi?
- Centraliser la logique de création PDO
- Supporter plusieurs drivers avec leurs syntaxes différentes
- Valider et documenter les paramètres requis pour chaque driver
Drivers supportés:
// SQLite (fichier ou mémoire) 'sqlite' => [ 'driver' => 'sqlite', 'database' => ':memory:' // ou '/path/to/db.sqlite' ] // MySQL 'mysql' => [ 'driver' => 'mysql', 'host' => 'localhost', 'port' => 3306, 'database' => 'velt', 'charset' => 'utf8mb4' ] // PostgreSQL 'pgsql' => [ 'driver' => 'pgsql', 'host' => 'localhost', 'port' => 5432, 'database' => 'velt' ]
DSN générés:
sqlite: sqlite::memory: ou sqlite:/path/to/db.sqlite
mysql: mysql:host=localhost;port=3306;dbname=velt;charset=utf8mb4
pgsql: pgsql:host=localhost;port=5432;dbname=velt
Méthodes clés:
public function create(array $connection): PDO // Retourne une instance PDO configurée private function buildDsn(array $connection): string // Construit la chaîne de connexion appropriée private function buildSqliteDsn(array $connection): string private function buildMysqlDsn(array $connection): string private function buildPgsqlDsn(array $connection): string // Builders spécifiques par driver
2. DatabaseManager (src/DatabaseManager.php)
Responsabilité: Gérer un pool de connexions nommées avec cache et initialisation lazy.
Pourquoi?
- Éviter de créer plusieurs PDO pour la même connexion
- Supporter plusieurs bases de données simultanément (ex: tenant separation)
- Résoudre la connexion par défaut automatiquement
- Valider la configuration au moment de l'accès, pas au démarrage
Fonctionnement:
// Première fois: création et cache $pdo = $manager->connection('default'); // crée et cache // Fois suivante: retour du cache $pdo = $manager->connection('default'); // retour cache, pas de reconnexion
Configuration attendue:
// config/database.php return [ 'default' => 'sqlite', // connexion par défaut 'connections' => [ 'sqlite' => [ 'driver' => 'sqlite', 'database' => ':memory:' ], // autres connexions... ] ];
Méthodes clés:
public function connection(?string $name = null): PDO // Obtenir une connexion (crée ou retourne du cache) private function defaultConnectionName(): string // Résoudre le nom de connexion par défaut depuis config
3. DB (src/DB.php) - Facade statique
Responsabilité: Fournir une interface statique simple pour exécuter des requêtes.
Pourquoi?
- Accès au DatabaseManager depuis n'importe où sans DI
- Enforce obligatoire des requêtes préparées (pas de concat string)
- Méthodes nommées intuitives (select, first, statement)
- Support natif des transactions
Pattern utilisé: Facade statique avec délégation
Requêtes préparées obligatoires:
// AUTORISÉ - Requête préparée DB::select('SELECT * FROM users WHERE id = ?', [1]); DB::select('SELECT * FROM users WHERE email = ?', [$email]); // INTERDIT - Interprétation SQL interdite // (Les placeholders ? sont obligatoires)
Méthodes disponibles:
// Retourner tous les résultats public static function select(string $sql, array $bindings = []): array // Retourner le premier résultat ou null public static function first(string $sql, array $bindings = []): ?array // Exécuter INSERT/UPDATE/DELETE, retourner le nombre de lignes affectées public static function statement(string $sql, array $bindings = []): int // Transaction: commit si succès, rollback si exception public static function transaction(callable $callback): mixed
Exemples:
// SELECT $users = DB::select('SELECT * FROM users WHERE age > ?', [18]); // FIRST $user = DB::first('SELECT * FROM users WHERE email = ?', ['test@example.com']); // INSERT/UPDATE/DELETE $affected = DB::statement( 'INSERT INTO users (name, email) VALUES (?, ?)', ['John', 'john@example.com'] ); // TRANSACTION DB::transaction(function() { DB::statement('INSERT INTO logs (action) VALUES (?)', ['action1']); DB::statement('INSERT INTO logs (action) VALUES (?)', ['action2']); // Si une exception: rollback automatique });
4. Model (src/Model.php) - Classe de base MVP
Responsabilité: Fournir des méthodes CRUD simples pour accéder aux données.
Pourquoi MVP (Minimum Viable Product)?
- Débuter simple sans relations, dirty checking, ou mass assignment
- Laisser place pour extensions futures
- Couvrir les opérations basiques (trouver, lister, créer)
Utilisation:
// Définir un modèle class User extends Model { protected string $table = 'users'; } // Utiliser le modèle $user = User::find(1); // SELECT * FROM users WHERE id = 1 $users = User::all(); // SELECT * FROM users $id = User::create([ // INSERT INTO users (...) 'name' => 'John', 'email' => 'john@example.com' ]);
Méthodes disponibles:
// Retrouver par ID (retourne array ou null) public static function find(int|string $id): ?array // Récupérer tous les enregistrements public static function all(): array // Créer un nouvel enregistrement (retourne l'ID) public static function create(array $attributes): int // (Interne) Nom de la table protected static function tableName(): string
Limitations actuelles (MVP):
- Pas de relations (belongsTo, hasMany, etc.)
- Pas de dirty tracking (modification avant save)
- Pas de mass assignment protection
- Pas de validation built-in
- Pas de scopes ou query builder fluent
Ces fonctionnalités seront ajoutées dans les phases suivantes.
5. DatabaseServiceProvider (src/DatabaseServiceProvider.php)
Responsabilité: Enregistrer DatabaseManager dans le conteneur DI avec initialisation lazy.
Pourquoi un Service Provider?
- Pattern standard pour organiser les enregistrements DI
- Initialisation lazy: DatabaseManager créé seulement si utilisé
- Accès au ConfigRepository depuis le conteneur
- Préparé pour intégration avec kernel Velt
Enregistrement:
public function register(ContainerInterface $container): void { // Enregistrer comme singleton $container->singleton(DatabaseManager::class, function() use ($container) { $config = $container->get(ConfigRepositoryInterface::class); return new DatabaseManager($config); }); }
Utilisation dans le kernel:
// Dans le kernel ou bootstrap $serviceProvider = new DatabaseServiceProvider(); $serviceProvider->register($container); // Le DatabaseManager n'est créé que lors du premier accès $manager = $container->get(DatabaseManager::class);
Configuration
Structure attendue
// config/database.php return [ // Connexion par défaut utilisée par DB:: 'default' => env('DB_CONNECTION', 'sqlite'), 'connections' => [ 'sqlite' => [ 'driver' => 'sqlite', 'database' => env('DB_DATABASE', ':memory:'), ], 'mysql' => [ 'driver' => 'mysql', 'host' => env('DB_HOST', 'localhost'), 'port' => env('DB_PORT', 3306), 'database' => env('DB_DATABASE', 'velt'), 'charset' => 'utf8mb4', ], 'pgsql' => [ 'driver' => 'pgsql', 'host' => env('DB_HOST', 'localhost'), 'port' => env('DB_PORT', 5432), 'database' => env('DB_DATABASE', 'velt'), ], ], ];
Utilisation complète
Configuration du service provider
// Dans le kernel use Velt\Database\DatabaseServiceProvider; class Kernel { protected array $serviceProviders = [ // ... autres providers DatabaseServiceProvider::class, ]; }
Utiliser DB (Facade)
use Velt\Database\DB; // Requêtes SELECT $users = DB::select('SELECT * FROM users WHERE active = ?', [1]); $user = DB::first('SELECT * FROM users WHERE id = ?', [1]); // Modification de données $affected = DB::statement( 'INSERT INTO users (name, email) VALUES (?, ?)', ['Alice', 'alice@example.com'] ); // Transactions DB::transaction(function() { DB::statement('UPDATE users SET balance = balance - ? WHERE id = ?', [100, 1]); DB::statement('UPDATE users SET balance = balance + ? WHERE id = ?', [100, 2]); // Les deux exécutées, ou rien si erreur });
Utiliser les Models
use Velt\Database\Model; class User extends Model { protected string $table = 'users'; } // Créer $id = User::create([ 'name' => 'Bob', 'email' => 'bob@example.com' ]); // Lire $user = User::find(1); $users = User::all(); // UPDATE et DELETE ne sont pas implémentés en MVP // Utiliser DB::statement() pour ces opérations
Tests
Exécuter les tests
composer test
Résultat attendu:
PHPUnit 10.5.63 by Sebastian Bergmann and contributors.
................... 19 / 19 (100%)
Time: 00:00.027, Memory: 8.00 MB
OK (19 tests, 39 assertions)
Structure des tests
tests/
├── Fakes/
│ ├── ArrayConfigRepository.php # Fake ConfigRepository
│ └── ArrayContainer.php # Fake ContainerInterface
├── ConnectionFactoryTest.php # 5 tests
├── DatabaseManagerTest.php # 4 tests
├── DBTest.php # 5 tests
├── ModelTest.php # 3 tests
└── DatabaseServiceProviderTest.php # 2 tests
Couverture de tests
| Composant | Tests | Cas couverts |
|---|---|---|
| ConnectionFactory | 5 | DSN SQLite/MySQL/PostgreSQL, drivers inconnus |
| DatabaseManager | 4 | Cache, connexion par défaut, validation |
| DB | 5 | select, first, statement, transactions |
| Model | 3 | find, all, create |
| ServiceProvider | 2 | Enregistrement, lazy resolution |
| Total | 19 | 39 assertions |
Sécurité
Requêtes préparées obligatoires
Tous les accès à la base utilisent des prepared statements:
// Paramètres bindés de manière sécurisée DB::select('SELECT * FROM users WHERE email = ?', [$userInput]); DB::select('SELECT * FROM users WHERE email = ? AND role = ?', [$email, $role]);
Pas de concaténation string:
// JAMAIS FAIRE CELA - INJECTION SQL! $sql = "SELECT * FROM users WHERE email = '$userInput'";
Gestion des erreurs
try { $pdo = $factory->create(['driver' => 'invalid']); } catch (UnknownDatabaseDriverException $e) { // Message d'erreur explicite }
Design Patterns utilisés
1. Factory Pattern
// ConnectionFactory crée les instances PDO $factory = new ConnectionFactory(); $pdo = $factory->create($config);
Centralise la création complexe
2. Service Provider Pattern
// Enregistrer les services au démarrage $provider = new DatabaseServiceProvider(); $provider->register($container);
Organise l'initialisation des services
3. Facade Pattern
// Interface simple et statique DB::select(...); DB::transaction(...);
Simplifie l'utilisation depuis n'importe où
4. Singleton Pattern
// DatabaseManager créé une seule fois $container->singleton(DatabaseManager::class, ...);
Évite les connexions multiples
5. Dependency Injection
// Les dépendances sont injectées, pas créées localement public function __construct( private ConfigRepositoryInterface $config, ) {}
Testabilité et flexibilité
6. Repository Pattern
// Abstraction de la source de config interface ConfigRepositoryInterface { public function get(string $key, mixed $default = null): mixed; }
Découple de l'implémentation
Avantages de cette implémentation
| Aspect | Bénéfice |
|---|---|
| Sécurité | Prepared statements obligatoires, pas d'injection SQL |
| Performance | Cache de connexions, pas de PDO redondants |
| Maintenabilité | Code séparé et organisé par responsabilité |
| Flexibilité | Support de 3 drivers, extensible facilement |
| Testabilité | Interfaces mockables, 19 tests complets |
| Usabilité | Facade simple (DB::select), Models intuitifs |
| Scalabilité | Support de connexions multiples nommées |
📋 Issues implémentées
✅ Issue 01: DatabaseManager avec ConnectionFactory
- DSN builders pour SQLite, MySQL, PostgreSQL
- Gestion des erreurs et validation
✅ Issue 02: Query helper sécurisé
- Facade DB statique
- Prepared statements obligatoires
- Transactions avec commit/rollback
✅ Issue 03: BaseModel MVP
- Méthodes find(), all(), create()
- Configuration par table
- Prêt pour extensions
✅ Issue 04: Intégration configuration
- Lecture config en dot notation (database.connections.default)
- Support des drivers multiples
- Validation explicite
✅ Issue 05: DatabaseServiceProvider
- Enregistrement singleton
- Initialisation lazy
- Prêt pour kernel Velt
🚀 Prochaines étapes possibles
Phase 2 (Proposé)
- Query Builder fluent (select()->where()->get())
- Relations (belongsTo, hasMany, hasManyThrough)
- Scopes et query macros
- Validation de modèle built-in
- Soft deletes
Phase 3 (Proposé)
- Migrations système
- Seeders
- Database transactions au niveau modèle
- Eager loading et lazy loading
Phase 4 (Proposé)
- Query caching
- Read replicas
- Database profiling et logging
- Support MongoDB/Redis
📝 Notes de développement
Structure de répertoires
src/
├── ConnectionFactory.php # Crée PDO
├── DatabaseManager.php # Pool de connexions
├── DB.php # Facade statique
├── Model.php # Classe de base
├── DatabaseServiceProvider.php # Enregistrement DI
└── Contracts/
├── ConfigRepositoryInterface.php # Configuration
└── ContainerInterface.php # DI Container
tests/
├── Fakes/
│ ├── ArrayConfigRepository.php # Test config
│ └── ArrayContainer.php # Test DI
├── ConnectionFactoryTest.php
├── DatabaseManagerTest.php
├── DBTest.php
├── ModelTest.php
└── DatabaseServiceProviderTest.php
Conventions de code
- Strict types:
declare(strict_types=1)sur tous les fichiers - Namespaces:
Velt\Database - PSR-4: Autoloading standard
- Type hints: Tous les paramètres et retours typés
- Commentaires: En français, explicatifs
Erreurs personnalisées
// Base DatabaseConfigurationException extends RuntimeException // Spécifiques UnknownDatabaseDriverException extends DatabaseConfigurationException
📞 Support
Pour des questions ou problèmes:
- Vérifier les tests dans
tests/ - Consulter les commentaires en français dans le code
- Référencer cette documentation
Statut du module: ✅ Prêt pour production Couverture de tests: 100% des chemins critiques Documentation: Complète en français Dernière mise à jour: Mai 2026
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 0
- 依赖项目数: 3
- 推荐数: 0
其他信息
- 授权协议: proprietary
- 更新时间: 2026-06-18