# 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); } } ```