velt/database

Velt Database PDO module

Maintainers

Package info

github.com/Velt-PHP/velt-database

pkg:composer/velt/database

Statistics

Installs: 0

Dependents: 3

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-06-16 15:27 UTC

This package is not auto-updated.

Last update: 2026-06-18 02:16:56 UTC


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:

  1. Vérifier les tests dans tests/
  2. Consulter les commentaires en français dans le code
  3. 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