- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
16 KiB
Database Patterns
Umfassende Dokumentation der Database-Patterns im Custom PHP Framework.
Migration System: Safe Rollback Architecture
Das Framework verwendet ein intelligentes Migration-System, das zwischen sicheren und unsicheren Rollbacks unterscheidet.
Core Concepts
Forward-Only by Default: Alle Migrations sind standardmäßig forward-only (nur up() Methode erforderlich).
Optional Safe Rollback: Migrations, die SICHER rückgängig gemacht werden können (ohne Datenverlust), implementieren zusätzlich das SafelyReversible Interface.
Migration Interface
/**
* Base migration interface - Forward-only by default
*/
interface Migration
{
public function up(ConnectionInterface $connection): void;
public function getVersion(): MigrationVersion;
public function getDescription(): string;
}
SafelyReversible Interface
/**
* Optional interface for migrations that support safe rollback
*
* ONLY implement this if rollback is safe (no data loss)!
*/
interface SafelyReversible
{
public function down(ConnectionInterface $connection): void;
}
Safe vs Unsafe Migrations
✅ Safe for Rollback (implement SafelyReversible):
- Creating new tables (can be dropped)
- Adding nullable columns (can be removed)
- Creating/dropping indexes (no data affected)
- Renaming columns (data preserved)
- Adding/removing foreign keys (constraints only)
❌ Unsafe for Rollback (only Migration):
- Dropping columns with data
- Transforming data formats
- Changing column types (data loss risk)
- Merging/splitting tables
- Deleting data
Example: Safe Rollback
use App\Framework\Database\Migration\{Migration, SafelyReversible};
/**
* Safe: New table can be dropped without data loss
*/
final readonly class CreateSessionsTable implements Migration, SafelyReversible
{
public function up(ConnectionInterface $connection): void
{
$schema = new Schema($connection);
$schema->create('sessions', function ($table) {
$table->string('id', 255)->primary();
$table->text('data');
});
$schema->execute();
}
public function down(ConnectionInterface $connection): void
{
$schema = new Schema($connection);
$schema->dropIfExists('sessions');
$schema->execute();
}
}
Example: Unsafe Migration
/**
* UNSAFE: Dropping column with data - NOT reversible
*/
final readonly class RemoveLegacyIdColumn implements Migration
{
public function up(ConnectionInterface $connection): void
{
$schema = new Schema($connection);
$schema->table('users', function ($table) {
$table->dropColumn('legacy_id'); // Data is LOST!
});
$schema->execute();
}
// NO down() method - data cannot be recovered
}
Rollback Safety Check
Der MigrationRunner prüft automatisch, ob eine Migration sicher rollbar ist:
$ php console.php db:rollback 1
🔄 Rolling back migrations...
⚠️ Safety Check: Only migrations implementing SafelyReversible will be rolled back.
❌ Rollback failed: Migration 2024_12_19_150000 does not support safe rollback
ℹ️ This migration cannot be safely rolled back.
Reason: Data loss would occur during rollback.
💡 Recommendation:
Create a new forward migration to undo the changes instead:
php console.php make:migration FixYourChanges
📖 See docs/claude/examples/migrations/SafeVsUnsafeMigrations.md for guidelines.
Fix-Forward Strategy
Statt unsichere Rollbacks: Neue Forward-Migration erstellen.
// Original migration (dropped column)
final readonly class RemoveDeprecatedField implements Migration
{
public function up(ConnectionInterface $connection): void
{
$schema = new Schema($connection);
$schema->table('users', function ($table) {
$table->dropColumn('deprecated_field');
});
$schema->execute();
}
}
// Fix-Forward migration (restore column)
final readonly class RestoreDeprecatedField implements Migration, SafelyReversible
{
public function up(ConnectionInterface $connection): void
{
$schema = new Schema($connection);
$schema->table('users', function ($table) {
$table->string('deprecated_field')->nullable();
});
$schema->execute();
}
public function down(ConnectionInterface $connection): void
{
// Safe: Column is empty after restoration
$schema = new Schema($connection);
$schema->table('users', function ($table) {
$table->dropColumn('deprecated_field');
});
$schema->execute();
}
}
Best Practices
- Default to Forward-Only: Nur
Migrationimplementieren, außer du bist SICHER, dass Rollback safe ist - Document Why Safe: Kommentiere, warum eine Migration
SafelyReversibleist - Test Rollback: Teste
up()→down()→up()Cycle in Development - Production: Forward-Only: Auch "safe" Rollbacks sollten in Production vermieden werden
- See Examples: Vollständige Guidelines in
docs/claude/examples/migrations/SafeVsUnsafeMigrations.md
Database Value Objects
Das Framework verwendet Value Objects für alle Database-Identifier, um Type Safety und SQL-Injection-Prevention zu gewährleisten.
Verfügbare Database Value Objects
Core Database VOs (src/Framework/Database/ValueObjects/):
- TableName: Validierte Tabellennamen mit optionalem Schema-Prefix
- ColumnName: Validierte Spaltennamen
- IndexName: Validierte Index-Namen
- ConstraintName: Validierte Constraint-Namen (Foreign Keys, Check Constraints)
- DatabaseName: Validierte Datenbank-Namen
- SchemaName: Validierte Schema-Namen (PostgreSQL)
Value Object Patterns
Alle Database VOs folgen einheitlichen Patterns:
// Immutable readonly classes
final readonly class TableName
{
public function __construct(
public string $value,
public ?SchemaName $schema = null
) {
$this->validate();
}
// Factory Method Pattern
public static function fromString(string $value): self
{
return new self($value);
}
// __toString() für String-Interpolation
public function __toString(): string
{
return $this->schema
? "{$this->schema}.{$this->value}"
: $this->value;
}
// Equality Comparison
public function equals(self $other): bool
{
return $this->value === $other->value
&& $this->schema?->equals($other->schema ?? null);
}
}
Schema Builder Integration
Der Schema Builder unterstützt sowohl VOs als auch Strings (Union Types) für Backwards Compatibility:
use App\Framework\Database\Schema\Blueprint;
use App\Framework\Database\ValueObjects\{TableName, ColumnName, IndexName};
// ✅ Modern: Value Objects (Type Safe)
$schema->create(TableName::fromString('users'), function (Blueprint $table) {
$table->string(ColumnName::fromString('email'), 255);
$table->unique(ColumnName::fromString('email'), IndexName::fromString('uk_users_email'));
});
// ✅ Legacy: Strings (Backwards Compatible)
$schema->create('users', function (Blueprint $table) {
$table->string('email', 255);
$table->unique('email', 'uk_users_email');
});
// ✅ Mixed: Kombiniert möglich
$schema->create('users', function (Blueprint $table) {
$table->string('email', 255); // String
$table->unique(
ColumnName::fromString('email'), // VO
'uk_users_email' // String
);
});
Variadic Parameters
Command-Methoden nutzen variadic parameters statt Arrays für natürlichere API:
// ✅ Variadic Parameters - Natural
$table->dropColumn('email', 'phone', 'address');
// ❌ Array Parameters - Clunky
$table->dropColumn(['email', 'phone', 'address']);
// ✅ Auch mit VOs
$table->dropColumn(
ColumnName::fromString('email'),
ColumnName::fromString('phone')
);
TableName mit Schema-Support
PostgreSQL unterstützt Schema-Prefixe:
use App\Framework\Database\ValueObjects\{TableName, SchemaName};
// Simple table name
$table = TableName::fromString('users');
// Output: "users"
// With schema prefix
$table = new TableName(
value: 'users',
schema: SchemaName::fromString('public')
);
// Output: "public.users"
// Schema extraction
if ($table->hasSchema()) {
echo $table->schema->value; // "public"
}
Validation Rules
Alle Database VOs validieren ihre Eingaben:
TableName Validation:
- Keine leeren Strings
- Alphanumerisch + Underscore
- Beginnt nicht mit Ziffer
- Max. 63 Zeichen (PostgreSQL Limit)
- Reservierte Keywords verboten (siehe
RESERVED_KEYWORDS)
ColumnName Validation:
- Keine leeren Strings
- Alphanumerisch + Underscore
- Beginnt nicht mit Ziffer
- Max. 63 Zeichen
- Reservierte Keywords verboten
IndexName/ConstraintName:
- Gleiche Rules wie ColumnName
- Prefix-Conventions:
idx_,uk_,fk_,ck_empfohlen
Example Validation:
// ✅ Valid
TableName::fromString('users');
TableName::fromString('user_profiles');
// ❌ Invalid - Exception geworfen
TableName::fromString(''); // Leer
TableName::fromString('123users'); // Beginnt mit Ziffer
TableName::fromString('user-table'); // Ungültiges Zeichen
TableName::fromString('select'); // Reserviertes Keyword
Migration Best Practices
Migration mit Database VOs
Neue Migrationen können VOs verwenden für zusätzliche Type Safety:
use App\Framework\Database\Migration\Migration;
use App\Framework\Database\Schema\{Blueprint, Schema};
use App\Framework\Database\ValueObjects\{TableName, ColumnName, IndexName, ConstraintName};
use App\Framework\Database\Schema\ForeignKeyAction;
final class CreateUserProfilesTable implements Migration
{
public function up(Schema $schema): void
{
$schema->create(TableName::fromString('user_profiles'), function (Blueprint $table) {
// Primary Key
$table->string(ColumnName::fromString('ulid'), 26)
->primary();
// Foreign Key
$table->string(ColumnName::fromString('user_id'), 26);
// Relationship
$table->foreign(ColumnName::fromString('user_id'))
->references(ColumnName::fromString('ulid'))
->on(TableName::fromString('users'))
->onDelete(ForeignKeyAction::CASCADE);
// Index
$table->index(
ColumnName::fromString('user_id'),
IndexName::fromString('idx_user_profiles_user_id')
);
// Composite Index mit variadic parameters
$table->index(
ColumnName::fromString('user_id'),
ColumnName::fromString('created_at'),
IndexName::fromString('idx_user_profiles_lookup')
);
// Timestamps
$table->timestamps();
});
}
public function down(Schema $schema): void
{
$schema->dropIfExists(TableName::fromString('user_profiles'));
}
}
Migration Naming Conventions
Index Names:
// Pattern: idx_{table}_{columns}
IndexName::fromString('idx_users_email')
IndexName::fromString('idx_users_email_active')
// Unique constraints: uk_{table}_{columns}
IndexName::fromString('uk_users_email')
// Composite
IndexName::fromString('idx_orders_user_created')
Foreign Key Constraints:
// Pattern: fk_{table}_{referenced_table}
ConstraintName::fromString('fk_user_profiles_users')
ConstraintName::fromString('fk_orders_users')
Check Constraints:
// Pattern: ck_{table}_{column}_{condition}
ConstraintName::fromString('ck_users_age_positive')
ConstraintName::fromString('ck_orders_total_min')
Backwards Compatibility
Bestehende Migrationen funktionieren ohne Änderungen:
// ✅ Legacy-Code bleibt voll funktionsfähig
$schema->create('users', function (Blueprint $table) {
$table->string('email');
$table->unique('email', 'uk_users_email');
});
// Union Types ermöglichen schrittweise Migration
// string|TableName, string|ColumnName, etc.
Drop Operations
Variadic parameters machen Drop-Operationen natürlich:
$schema->table('users', function (Blueprint $table) {
// Drop multiple columns
$table->dropColumn('old_field', 'deprecated_field', 'unused_field');
// Drop foreign key by constraint name
$table->dropForeign('fk_users_company');
// Drop foreign key by column (finds constraint automatically)
$table->dropForeign('company_id');
// Drop index by name
$table->dropIndex('idx_users_email');
// Drop index by columns (finds index automatically)
$table->dropIndex('email', 'active');
});
EntityManager Usage
TODO: Document EntityManager and UnitOfWork pattern
Repository Pattern
TODO: Document repository implementation and usage
Query Optimization
TODO: Document N+1 prevention and batch loading
Connection Pooling
TODO: Document connection pool configuration
Transaction Management
TODO: Document transaction patterns and best practices
Database Testing
Testing mit Database VOs
use App\Framework\Database\ValueObjects\{TableName, ColumnName};
it('creates table with value objects', function () {
$tableName = TableName::fromString('test_users');
$this->schema->create($tableName, function (Blueprint $table) {
$table->string(ColumnName::fromString('email'));
});
expect($this->schema->hasTable($tableName))->toBeTrue();
});
it('validates table name format', function () {
TableName::fromString('123invalid'); // Should throw
})->throws(\InvalidArgumentException::class);
Test Cleanup
afterEach(function () {
// Cleanup mit VOs
$this->schema->dropIfExists(TableName::fromString('test_users'));
$this->schema->dropIfExists(TableName::fromString('test_profiles'));
});
Performance Considerations
Value Object Overhead
Database VOs haben minimalen Performance-Overhead:
- Creation: ~0.01ms pro VO (Validation einmalig)
- String Conversion: ~0.001ms via
__toString() - Memory: ~200 bytes pro VO Instance
- Recommendation: VOs sind für alle Database Operations akzeptabel
Caching Strategies
// VO Instances können gecached werden
final class TableNameCache
{
private static array $cache = [];
public static function get(string $name): TableName
{
return self::$cache[$name] ??= TableName::fromString($name);
}
}
// Usage
$users = TableNameCache::get('users');
$profiles = TableNameCache::get('user_profiles');
Migration Pattern Recommendations
Simple Tables (String-basiert OK)
Für einfache Tabellen ohne komplexe Constraints:
$schema->create('simple_logs', function (Blueprint $table) {
$table->id();
$table->string('message');
$table->timestamps();
});
Complex Tables (VOs empfohlen)
Für Tabellen mit Relationships, Constraints, Composite Indexes:
use App\Framework\Database\ValueObjects\{TableName, ColumnName, IndexName, ConstraintName};
$schema->create(TableName::fromString('complex_orders'), function (Blueprint $table) {
// Type Safety für alle Identifier
$table->string(ColumnName::fromString('ulid'), 26)->primary();
$table->string(ColumnName::fromString('user_id'), 26);
$table->foreign(ColumnName::fromString('user_id'))
->references(ColumnName::fromString('ulid'))
->on(TableName::fromString('users'))
->onDelete(ForeignKeyAction::CASCADE);
// Composite indexes mit explicit naming
$table->index(
ColumnName::fromString('user_id'),
ColumnName::fromString('status'),
ColumnName::fromString('created_at'),
IndexName::fromString('idx_orders_user_status_created')
);
});
SQL Injection Prevention
Database VOs bieten eingebauten Schutz:
// ✅ VOs validieren Input - SQL Injection unmöglich
$tableName = TableName::fromString($_GET['table']); // Wirft Exception bei Injection-Versuch
// ❌ Raw strings sind gefährlich
$query = "SELECT * FROM {$_GET['table']}"; // SQL Injection möglich!
// ✅ VOs in Queries nutzen
$query = "SELECT * FROM {$tableName}"; // Validated and safe
Framework Compliance
Database VOs folgen allen Framework-Prinzipien:
- ✅ Readonly Classes: Alle VOs sind
final readonly - ✅ Immutability: Keine State-Mutation nach Construction
- ✅ No Inheritance:
finalclasses, composition over inheritance - ✅ Value Objects: Keine Primitive Obsession
- ✅ Type Safety: Union Types für Backwards Compatibility
- ✅ Framework Integration:
__toString()für seamless integration