# Development Guidelines

Entwicklungsrichtlinien für das Custom PHP Framework.

## Code Style Principles

### Fundamental Code Standards

- **PSR-12 Coding Standards**: Befolge PSR-12 für einheitliche Code-Formatierung
- **PHP-CS-Fixer**: Automatische Code-Formatierung verwenden
- **Strict Types**: `declare(strict_types=1)` in allen PHP-Dateien

### Framework-Specific Principles

**No Inheritance Principle**:
```php
// ❌ Avoid extends - problematisch für Wartbarkeit
class UserController extends BaseController
{
    // Problematische Vererbung
}

// ✅ Use composition - flexibler und testbarer
final readonly class UserController
{
    public function __construct(
        private readonly AuthService $auth,
        private readonly UserRepository $userRepository,
        private readonly Logger $logger
    ) {}
}
```

**Immutable by Design**:
```php
// ✅ Unveränderliche Objekte bevorzugen
final readonly class User
{
    public function __construct(
        public string $id,
        public Email $email,
        public string $name
    ) {}
    
    // Neue Instanz für Änderungen
    public function changeName(string $newName): self
    {
        return new self($this->id, $this->email, $newName);
    }
}
```

**Readonly Everywhere**:
```php
// ✅ Classes und Properties readonly wo möglich
final readonly class ProductService
{
    public function __construct(
        private readonly ProductRepository $repository,
        private readonly PriceCalculator $calculator
    ) {}
}
```

**Final by Default**:
```php
// ✅ Klassen sind final außer wenn explizit für Erweiterung designt
final readonly class OrderProcessor
{
    // Implementation
}

// Nur wenn bewusst erweiterbar
readonly class BaseValidator
{
    // Designed for extension
}
```

**Property Hooks Usage**:
```php
// ❌ Property Hooks in readonly Klassen - TECHNISCH NICHT MÖGLICH
final readonly class User
{
    public string $fullName {
        get => $this->firstName . ' ' . $this->lastName; // PHP Fehler!
    }
}

// ✅ Normale Methoden in readonly Klassen verwenden
final readonly class User
{
    public function getFullName(): string
    {
        return $this->firstName . ' ' . $this->lastName;
    }
}

// ✅ Property Hooks nur in mutable Klassen verwenden
final class ConfigManager
{
    private array $cache = [];
    
    public string $apiKey {
        set (string $value) {
            if (empty($value)) {
                throw new InvalidArgumentException('API key cannot be empty');
            }
            $this->apiKey = $value;
            $this->cache = []; // Clear cache on change
        }
    }
    
    public array $settings {
        get => $this->cache ?: $this->cache = $this->loadSettings();
    }
}

// ✅ private(set) für kontrollierte Mutation
final class EventStore
{
    public private(set) array $events = [];
    
    public function addEvent(DomainEvent $event): void
    {
        $this->events[] = $event;
    }
}
```

**Property Hooks Richtlinien**:
- **TECHNISCH NICHT MÖGLICH** in `readonly` Klassen - PHP verbietet Property Hooks in readonly Klassen
- **Nur in mutable Klassen** verwenden
- **Alternative**: Normale Methoden in `readonly` Klassen für computed values
- **Use Cases**: Validation beim Setzen, Lazy Loading, Cache Invalidation
- **private(set)** für kontrollierte Array-Mutation in mutable Klassen

## Value Objects over Primitives

**Verwende Value Objects statt Arrays oder Primitives**:

```php
// ❌ Primitive Obsession vermeiden
function createUser(string $email, array $preferences): array
{
    // Problematisch: keine Typsicherheit, versteckte Struktur
}

// ✅ Value Objects für Domain-Konzepte
function createUser(Email $email, UserPreferences $preferences): User
{
    // Typsicher, selbstdokumentierend, validiert
}
```

**Domain Modeling mit Value Objects**:
```php
final readonly class Price
{
    public function __construct(
        public int $cents,
        public Currency $currency
    ) {
        if ($cents < 0) {
            throw new \InvalidArgumentException('Preis kann nicht negativ sein');
        }
    }
    
    public function toEuros(): float
    {
        return $this->cents / 100.0;
    }
    
    public function add(self $other): self
    {
        if (!$this->currency->equals($other->currency)) {
            throw new \InvalidArgumentException('Currencies must match');
        }
        
        return new self($this->cents + $other->cents, $this->currency);
    }
}
```

## Testing Standards

### Test Organization

**Mixed Testing Approach**:
- **Pest Framework**: Bevorzugt für neue Tests (moderne Syntax)
- **PHPUnit**: Traditionelle Tests beibehalten
- **Test Structure**: Tests spiegeln Source-Verzeichnisstruktur wider

```php
// ✅ Pest Test Beispiel
it('can calculate order total with tax', function () {
    $order = new Order([
        new OrderItem(new Price(1000, Currency::EUR), quantity: 2),
        new OrderItem(new Price(500, Currency::EUR), quantity: 1)
    ]);
    
    $calculator = new OrderCalculator(new TaxRate(0.19));
    $total = $calculator->calculateTotal($order);
    
    expect($total->cents)->toBe(2975); // 25€ + 19% tax
});
```

### Test Categories

- **Unit Tests**: Domain Logic, Value Objects, Services
- **Integration Tests**: Web Controller, Database Operations  
- **Feature Tests**: End-to-End User Workflows
- **Performance Tests**: Critical Path Performance

## Security Guidelines

### Authentication & Authorization

**IP-based Authentication** für Admin Routes:
```php
#[Route(path: '/admin/dashboard', method: Method::GET)]
#[Auth(strategy: 'ip', allowedIps: ['127.0.0.1', '::1'])]
public function dashboard(HttpRequest $request): ViewResult
{
    // Nur von erlaubten IP-Adressen erreichbar
}
```

**Route Protection**:
```php
#[Route(path: '/api/users', method: Method::POST)]
#[Auth(strategy: 'session', roles: ['admin'])]
public function createUser(CreateUserRequest $request): JsonResult
{
    // Authentifizierung und Autorisierung erforderlich
}
```

### Input Validation

**Request Objects** für Validation:
```php
final readonly class CreateUserRequest implements ControllerRequest
{
    public function __construct(
        public Email $email,
        public string $name,
        public ?string $company = null
    ) {}
    
    public static function fromHttpRequest(HttpRequest $request): self
    {
        $data = $request->parsedBody->toArray();
        
        return new self(
            email: new Email($data['email'] ?? ''),
            name: trim($data['name'] ?? ''),
            company: !empty($data['company']) ? trim($data['company']) : null
        );
    }
}
```

### Server Data Access

**Verwende Request::server statt Superglobals**:
```php
// ❌ Keine Superglobals verwenden
public function handleRequest(): JsonResult
{
    $userAgent = $_SERVER['HTTP_USER_AGENT']; // Schlecht
    $clientIp = $_SERVER['REMOTE_ADDR'];      // Schlecht
}

// ✅ Request::server verwenden
public function handleRequest(HttpRequest $request): JsonResult
{
    $userAgent = $request->server->getUserAgent();
    $clientIp = $request->server->getClientIp();
    $referer = $request->server->getSafeRefererUrl('/dashboard');
    
    return new JsonResult([
        'user_agent' => $userAgent->toString(),
        'client_ip' => (string) $clientIp,
        'safe_referer' => $referer
    ]);
}

### OWASP Security Events

**Security Event Logging**:
```php
// Automatisches Security Event Logging
final readonly class AuthenticationGuard
{
    public function authenticate(LoginAttempt $attempt): AuthResult
    {
        if ($attempt->isRateLimited()) {
            $this->eventLogger->logSecurityEvent(
                new AuthenticationFailedEvent(
                    reason: 'Rate limit exceeded',
                    ipAddress: $attempt->ipAddress,
                    userAgent: $attempt->userAgent
                )
            );
            
            throw new AuthenticationException('Too many attempts');
        }
        
        // Authentication logic
    }
}
```

## Performance Guidelines

### Database Optimization

**EntityManager Usage**:
```php
// ✅ Bulk Operations verwenden
public function updateMultipleUsers(array $userUpdates): void
{
    $this->entityManager->beginTransaction();
    
    try {
        foreach ($userUpdates as $update) {
            $user = $this->entityManager->find(User::class, $update->userId);
            $user->updateProfile($update->profileData);
            // Keine sofortige Persistierung
        }
        
        $this->entityManager->flush(); // Bulk flush
        $this->entityManager->commit();
    } catch (\Exception $e) {
        $this->entityManager->rollback();
        throw $e;
    }
}
```

**N+1 Query Prevention**:
```php
// ✅ Eager Loading verwenden
$users = $this->userRepository->findWithProfiles($userIds);

// Statt lazy loading in Schleife
// foreach ($users as $user) {
//     $profile = $user->getProfile(); // N+1 Problem
// }
```

### Caching Strategy

**Framework Cache Interface mit Value Objects**:
```php
use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheKey;
use App\Framework\Cache\CacheItem;
use App\Framework\Core\ValueObjects\Duration;

final readonly class UserService
{
    public function __construct(
        private readonly Cache $cache,  // SmartCache ist Standard-Implementation
        private readonly UserRepository $repository
    ) {}
    
    public function getUser(string $userId): User
    {
        $cacheKey = CacheKey::fromString("user_{$userId}");
        $ttl = Duration::fromHours(1);
        
        // Remember Pattern mit Value Objects
        $cacheItem = $this->cache->remember(
            key: $cacheKey,
            callback: fn() => $this->repository->find($userId),
            ttl: $ttl
        );
        
        return $cacheItem->value;
    }
    
    public function cacheMultipleUsers(array $users): bool
    {
        $cacheItems = [];
        
        foreach ($users as $user) {
            $cacheItems[] = CacheItem::forSetting(
                key: CacheKey::fromString("user_{$user->id}"),
                value: $user,
                ttl: Duration::fromHours(1)
            );
        }
        
        // Batch-Operation mit SmartCache
        return $this->cache->set(...$cacheItems);
    }
}
```

**Advanced Cache Patterns**:
```php
// Cache mit Tags für gruppierte Invalidierung
$userKey = CacheKey::fromString("user_{$userId}");
$teamTag = CacheTag::fromString("team_{$teamId}");

$cacheItem = CacheItem::forSetting(
    key: $userKey,
    value: $user,
    ttl: Duration::fromHours(2),
    tags: [$teamTag]
);

$this->cache->set($cacheItem);

// Alle Team-bezogenen Caches invalidieren
$this->cache->forget($teamTag);

## Configuration Management

### Typed Configuration mit Environment Klasse

**Framework Environment Klasse verwenden**:
```php
final readonly class DatabaseConfig
{
    public function __construct(
        public string $host,
        public int $port,
        public string $database,
        public string $username,
        public string $password,
        public string $driver = 'mysql'
    ) {}
    
    public static function fromEnvironment(Environment $env): self
    {
        return new self(
            host: $env->get(EnvKey::DB_HOST, 'localhost'),
            port: $env->getInt(EnvKey::DB_PORT, 3306),
            database: $env->require(EnvKey::DB_NAME),
            username: $env->require(EnvKey::DB_USER),
            password: $env->require(EnvKey::DB_PASS),
            driver: $env->get(EnvKey::DB_DRIVER, 'mysql')
        );
    }
}
```

**EnvKey Enum für Type Safety**:
```php
// Environment Keys als Enum definieren
enum EnvKey: string 
{
    case DB_HOST = 'DB_HOST';
    case DB_PORT = 'DB_PORT';
    case DB_NAME = 'DB_NAME';
    case DB_USER = 'DB_USER';
    case DB_PASS = 'DB_PASS';
    case DB_DRIVER = 'DB_DRIVER';
    case APP_ENV = 'APP_ENV';
}
```

**Configuration Initializer Pattern**:
```php
final readonly class DatabaseConfigInitializer implements Initializer
{
    public function __construct(
        private readonly Environment $environment
    ) {}
    
    public function initialize(Container $container): void
    {
        $config = DatabaseConfig::fromEnvironment($this->environment);
        $container->singleton(DatabaseConfig::class, $config);
    }
}
```

## Documentation Standards

### Code Documentation

**Self-Documenting Code bevorzugen**:
```php
// ✅ Code, der sich selbst erklärt
final readonly class OrderTotalCalculator
{
    public function calculateTotalWithTax(
        Order $order,
        TaxRate $taxRate
    ): Money {
        $subtotal = $this->calculateSubtotal($order);
        $taxAmount = $subtotal->multiply($taxRate->asDecimal());
        
        return $subtotal->add($taxAmount);
    }
}
```

**PHPDoc nur wenn notwendig**:
```php
/**
 * Berechnet Gesamtsumme nur für komplexe Business Logic
 * 
 * @param Order $order - Customer order with line items
 * @param TaxRate $taxRate - Applicable tax rate (0.0-1.0)
 * @throws InvalidOrderException wenn Order leer ist
 */
public function calculateComplexTotal(Order $order, TaxRate $taxRate): Money
```

## Dependency Injection Best Practices

### Constructor Injection

**Explizite Dependencies**:
```php
// ✅ Alle Dependencies im Constructor
final readonly class OrderProcessor
{
    public function __construct(
        private readonly PaymentGateway $paymentGateway,
        private readonly InventoryService $inventory,
        private readonly EmailService $emailService,
        private readonly Logger $logger
    ) {}
}
```

**Service Locator Anti-Pattern vermeiden**:
```php
// ❌ Service Locator Pattern
public function processOrder(Order $order): void
{
    $gateway = ServiceLocator::get(PaymentGateway::class); // Schlecht
}

// ✅ Dependency Injection
public function processOrder(Order $order): void
{
    $this->paymentGateway->charge($order->getTotal()); // Gut
}
```

## Framework Integration Patterns

### Environment-Aware Services

**Environment in Service Initialization**:
```php
final readonly class EmailServiceInitializer implements Initializer
{
    public function __construct(
        private readonly Environment $environment
    ) {}
    
    public function initialize(Container $container): void
    {
        $service = match ($this->environment->get(EnvKey::APP_ENV)) {
            'production' => new SmtpEmailService(
                host: $this->environment->require(EnvKey::SMTP_HOST),
                username: $this->environment->require(EnvKey::SMTP_USER),
                password: $this->environment->require(EnvKey::SMTP_PASS)
            ),
            'development' => new LogEmailService(),
            default => new NullEmailService()
        };
        
        $container->singleton(EmailService::class, $service);
    }
}
```

## Database Value Objects Best Practices

### Overview

Das Framework verwendet Value Objects für alle Database-Identifier, um Type Safety und SQL-Injection-Prevention zu gewährleisten. Diese Best Practices ergänzen die allgemeinen Value Object Patterns um database-spezifische Anforderungen.

**Verfügbare Database VOs**: TableName, ColumnName, IndexName, ConstraintName, DatabaseName, SchemaName

### When to Use Database VOs

**Simple Migrations** (String-basiert OK):
```php
// Einfache Tabellen ohne komplexe Constraints
$schema->create('logs', function (Blueprint $table) {
    $table->id();
    $table->string('message');
    $table->timestamps();
});
```

**Complex Migrations** (VOs empfohlen):
```php
use App\Framework\Database\ValueObjects\{TableName, ColumnName, IndexName};

// Tabellen mit Relationships, Constraints, Composite Indexes
$schema->create(TableName::fromString('orders'), function (Blueprint $table) {
    $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 Index mit explicit naming
    $table->index(
        ColumnName::fromString('user_id'),
        ColumnName::fromString('status'),
        ColumnName::fromString('created_at'),
        IndexName::fromString('idx_orders_user_status_created')
    );
});
```

**Decision Criteria**:
- **Use VOs when**: Foreign keys, composite indexes, PostgreSQL schemas, security-critical tables
- **String OK when**: Simple logs, cache tables, temporary tables, development prototypes

### Naming Conventions

**Index Names** folgen Pattern `idx_{table}_{columns}`:
```php
IndexName::fromString('idx_users_email')
IndexName::fromString('idx_users_email_active')
IndexName::fromString('idx_orders_user_created')
```

**Unique Constraints** folgen Pattern `uk_{table}_{columns}`:
```php
IndexName::fromString('uk_users_email')
IndexName::fromString('uk_users_username')
```

**Foreign Key Constraints** folgen Pattern `fk_{table}_{referenced_table}`:
```php
ConstraintName::fromString('fk_user_profiles_users')
ConstraintName::fromString('fk_orders_users')
ConstraintName::fromString('fk_order_items_orders')
```

**Check Constraints** folgen Pattern `ck_{table}_{column}_{condition}`:
```php
ConstraintName::fromString('ck_users_age_positive')
ConstraintName::fromString('ck_orders_total_min')
```

### Validation Patterns

**Automatic Validation** on Construction:
```php
// ✅ Valid - alphanumerisch + underscore, beginnt mit Buchstabe
TableName::fromString('users');
TableName::fromString('user_profiles');
ColumnName::fromString('email_address');

// ❌ Invalid - wirft Exception
TableName::fromString('');           // Leer
TableName::fromString('123users');   // Beginnt mit Ziffer
TableName::fromString('user-table'); // Ungültiges Zeichen
TableName::fromString('select');     // Reserviertes Keyword
```

**Validation Rules** für alle Database VOs:
- Keine leeren Strings
- Alphanumerisch + Underscore nur
- Beginnt nicht mit Ziffer
- Max. 63 Zeichen (PostgreSQL Limit)
- Keine reservierten SQL Keywords

### PostgreSQL Schema Support

**TableName mit Schema-Prefix**:
```php
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"

// In Migration
$schema->create(
    new TableName('audit_logs', SchemaName::fromString('audit')),
    function (Blueprint $table) {
        // Schema-qualifizierte Tabelle
    }
);
```

### Backwards Compatibility

**Union Types** ermöglichen schrittweise Migration:
```php
// Blueprint akzeptiert string|TableName
public function create(string|TableName $table, Closure $callback): void

// Legacy-Code funktioniert weiterhin
$schema->create('users', function (Blueprint $table) {
    $table->string('email'); // String-basiert OK
});

// Neuer Code kann VOs verwenden
$schema->create(TableName::fromString('users'), function (Blueprint $table) {
    $table->string(ColumnName::fromString('email')); // VO-basiert
});

// Mischung ist möglich
$schema->create('users', function (Blueprint $table) {
    $table->string('email'); // String
    $table->unique(
        ColumnName::fromString('email'),  // VO
        'uk_users_email'                  // String
    );
});
```

### Drop Operations mit Variadic Parameters

**Natural API** statt Array-Parameter:
```php
$schema->table('users', function (Blueprint $table) {
    // ✅ Variadic - Natural
    $table->dropColumn('old_field', 'deprecated_field', 'unused_field');

    // ❌ Array - Clunky
    // $table->dropColumn(['old_field', 'deprecated_field']);

    // ✅ Auch mit VOs
    $table->dropColumn(
        ColumnName::fromString('old_field'),
        ColumnName::fromString('deprecated_field')
    );

    // Drop index by name
    $table->dropIndex('idx_users_email');

    // Drop index by columns (finds index automatically)
    $table->dropIndex('email', 'active');

    // Drop foreign key by constraint name
    $table->dropForeign('fk_users_company');

    // Drop foreign key by column (finds constraint automatically)
    $table->dropForeign('company_id');
});
```

### SQL Injection Prevention

**Built-in Protection** durch VO Validation:
```php
// ✅ VOs validieren Input - SQL Injection unmöglich
$tableName = TableName::fromString($_GET['table']);
// Wirft Exception bei Injection-Versuch

// Query-Building ist sicher
$query = "SELECT * FROM {$tableName}"; // Validated and safe

// ❌ Raw strings sind gefährlich
$query = "SELECT * FROM {$_GET['table']}"; // SQL Injection möglich!
```

**Validation verhindert**:
- SQL Keywords als Identifier (`select`, `drop`, `union`)
- Special Characters (`;`, `--`, `/*`, `*/`)
- Path Traversal (`../`, `..\\`)
- Control Characters

### Testing Database VOs

**Unit Tests** für VO Validation:
```php
use App\Framework\Database\ValueObjects\TableName;

it('validates table name format', function () {
    TableName::fromString('123invalid'); // Should throw
})->throws(\InvalidArgumentException::class);

it('accepts valid table names', function () {
    $table = TableName::fromString('users');
    expect($table->value)->toBe('users');
});

it('supports schema-qualified names', function () {
    $table = new TableName('users', SchemaName::fromString('public'));
    expect((string) $table)->toBe('public.users');
});
```

**Integration Tests** für Migration mit VOs:
```php
it('creates table with value objects', function () {
    $tableName = TableName::fromString('test_users');

    $this->schema->create($tableName, function (Blueprint $table) {
        $table->string(ColumnName::fromString('email'));
        $table->unique(
            ColumnName::fromString('email'),
            IndexName::fromString('uk_test_users_email')
        );
    });

    expect($this->schema->hasTable($tableName))->toBeTrue();
    expect($this->schema->hasIndex('test_users', 'uk_test_users_email'))->toBeTrue();
});
```

**Test Cleanup** mit VOs:
```php
afterEach(function () {
    // Cleanup mit VOs
    $this->schema->dropIfExists(TableName::fromString('test_users'));
    $this->schema->dropIfExists(TableName::fromString('test_profiles'));
});
```

### Performance Considerations

**VO Overhead** ist minimal:
- **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 Strategy** für häufig verwendete VOs:
```php
final class TableNameCache
{
    private static array $cache = [];

    public static function get(string $name): TableName
    {
        return self::$cache[$name] ??= TableName::fromString($name);
    }
}

// Usage in Migration
$users = TableNameCache::get('users');
$profiles = TableNameCache::get('user_profiles');
```

### Migration Code Review Checklist

**Before Merge**:
- [ ] Naming Conventions befolgt (`idx_`, `uk_`, `fk_`, `ck_`)
- [ ] Foreign Keys haben explizite ConstraintNames
- [ ] Composite Indexes haben aussagekräftige IndexNames
- [ ] PostgreSQL Schema-Prefix wo benötigt
- [ ] Drop Operations nutzen Variadic Parameters
- [ ] Backwards Compatibility: String-basiert OK für simple tables
- [ ] Test Coverage: Migration Up/Down getestet

**Security Review**:
- [ ] Keine User Input in TableName/ColumnName ohne VO Validation
- [ ] Keine Dynamic Table Names aus unvalidiertem Input
- [ ] Keine SQL Keywords als Identifier

### Framework Compliance

Database VOs folgen allen Framework-Prinzipien:
- ✅ **Readonly Classes**: Alle VOs sind `final readonly`
- ✅ **Immutability**: Keine State-Mutation nach Construction
- ✅ **No Inheritance**: `final` classes, composition only
- ✅ **Value Objects**: Keine Primitive Obsession
- ✅ **Type Safety**: Union Types für Backwards Compatibility
- ✅ **Framework Integration**: `__toString()` für seamless SQL interpolation
- ✅ **Validation**: Constructor-basierte Validation
- ✅ **Explicit**: Factory Methods (`fromString()`) für clarity