# Naming Conventions Framework-weite Naming Conventions für das Custom PHP Framework. ## Philosophie **Kernprinzipien:** 1. **Keine redundanten Suffixe** - Domain-Code ist suffixfrei, Infrastructure hat Suffixe für Klarheit 2. **Semantische Klarheit** - Namen beschreiben Absicht, nicht technische Implementation 3. **Namespace-Kontext** - Namespace gibt primären Kontext, Namen sind prägnant 4. **Konsistenz** - Einheitliche Patterns über das gesamte Framework ## Übersicht | Komponente | Pattern | Beispiel | Suffix/Prefix | |------------|---------|----------|---------------| | **View Controller** | `Show{Feature}` | `ShowContact`, `ShowHome` | - | | **Action Controller** | `{Verb}{Feature}` | `SubmitContact`, `CreateUser` | - | | **Repository Interface** | `{Entity}Repository` | `UserRepository` | ✅ Repository | | **Repository Implementation** | `{Type}{Entity}Repository` | `InMemoryUserRepository` | ✅ Repository | | **Service** | `{Domain}Service` | `EmailService`, `UserService` | ✅ Service | | **Manager** | `{Capability}Manager` | `SessionManager`, `CacheManager` | ✅ Manager | | **Handler (Generic)** | `{Capability}Handler` | `ErrorHandler`, `FileHandler` | ✅ Handler | | **Handler (Command)** | `{Command}Handler` | `StoreContactHandler` | ✅ Handler | | **CQRS Command** | `{Verb}{Noun}` | `SendEmail`, `CreateUser` | - | | **Console Command** | `{Feature}Command` | `HealthCheckCommand` | ✅ Command | | **Domain Exception** | `{Condition}` | `UserNotFound`, `PaymentFailed` | - | | **Technical Exception** | `{Type}Exception` | `DatabaseException` | ✅ Exception | | **Value Object** | `{Concept}` | `Email`, `Price`, `Duration` | - | | **Event** | `{Entity}{Action}` | `UserCreated`, `OrderPlaced` | - | | **Middleware** | `{Purpose}Middleware` | `AuthMiddleware` | ✅ Middleware | | **Initializer** | `{Service}Initializer` | `DatabaseInitializer` | ✅ Initializer | ## Detaillierte Patterns ### 1. Controller Naming **Strikte Trennung zwischen View und Business Logic.** #### View Controllers - `Show{Feature}` **Zweck:** Nur View-Rendering, keine Business Logic ```php // ✅ View-Only Controller final readonly class ShowContact { #[Route('/contact', method: Method::GET)] public function __invoke(): ViewResult { return new ViewResult('contact', $metaData); } } final readonly class ShowHome { #[Route('/', method: Method::GET)] public function __invoke(): ViewResult { return new ViewResult('home', $metaData); } } ``` **Regeln:** - ✅ NUR GET-Requests - ✅ NUR ViewResult als Return Type - ✅ Darf Daten aus Repository laden (für View) - ❌ KEINE POST/PUT/DELETE Routes - ❌ KEINE Business Logic (CommandBus, Services) #### Action Controllers - `{Verb}{Feature}` **Zweck:** Business Logic, Form Processing, API Operations ```php // ✅ Action Controller mit Business Logic final readonly class SubmitContact { #[Route('/contact', method: Method::POST)] public function __invoke(ContactRequest $request): ActionResult { $this->commandBus->dispatch(new StoreContact(...)); return new Redirect('/contact/success'); } } final readonly class CreateUser { #[Route('/users', method: Method::POST)] public function __invoke(CreateUserRequest $request): JsonResult { $user = $this->userService->create($request); return new JsonResult($user, Status::CREATED); } } ``` **Verfügbare Verben:** - `Submit{Feature}` - Form submission - `Create{Feature}` - Resource creation (POST) - `Update{Feature}` - Resource update (PUT/PATCH) - `Delete{Feature}` - Resource deletion (DELETE) - `Handle{Feature}` - Generic processing (Webhooks, Events) - `Process{Feature}` - Complex operations (Payments, Orders) - `Execute{Feature}` - Task execution (Jobs, Commands) **Regeln:** - ✅ POST/PUT/DELETE Operations - ✅ Business Logic (CommandBus, Services, Repositories) - ✅ Alle Result-Typen (ViewResult, JsonResult, Redirect, etc.) #### Anti-Patterns ```php // ❌ FALSCH: "Controller" Suffix ist redundant class ContactController { } class UserController { } // ❌ FALSCH: View und Logic vermischt class ShowContact { #[Route('/contact', GET)] public function show(): ViewResult { } #[Route('/contact', POST)] // ❌ Sollte SubmitContact sein public function submit(): ActionResult { } } ``` --- ### 2. Repository Pattern **Pattern: Interface + Implementations mit Type-Prefix** ```php // ✅ Repository Interface interface UserRepository { public function find(UserId $id): ?User; public function save(User $user): void; } // ✅ Repository Implementations mit Type-Prefix final readonly class InMemoryUserRepository implements UserRepository { public function find(UserId $id): ?User { } public function save(User $user): void { } } final readonly class DatabaseUserRepository implements UserRepository { public function find(UserId $id): ?User { } public function save(User $user): void { } } final readonly class CacheUserRepository implements UserRepository { private readonly UserRepository $decorated; public function find(UserId $id): ?User { return $this->cache->remember("user_{$id}", fn() => $this->decorated->find($id) ); } } ``` **Type-Prefixes:** - `InMemory{Entity}Repository` - In-Memory Implementation - `Database{Entity}Repository` - Database-backed - `Cache{Entity}Repository` - Caching Decorator - `File{Entity}Repository` - File-based Storage **Regeln:** - ✅ Interface ohne Type-Prefix - ✅ Implementations mit Type-Prefix - ✅ Suffix "Repository" immer vorhanden - ❌ Kein Suffix-Pattern: `UserRepositoryDatabase` --- ### 3. Service Layer **Pattern: `{Domain}Service` oder `{Capability}Service`** ```php // ✅ Domain Services final readonly class UserService { public function createUser(Email $email, UserName $name): User { } public function findUserById(UserId $id): ?User { } } final readonly class EmailService { public function sendWelcomeEmail(User $user): void { } public function sendPasswordResetEmail(Email $email, ResetToken $token): void { } } // ✅ Capability Services final readonly class JobCleanupService { public function cleanupExpiredJobs(): int { } } final readonly class MediaCleanupService { public function cleanupOrphanedFiles(): void { } } ``` **Regeln:** - ✅ "Service" Suffix immer vorhanden - ✅ Beschreibender Prefix (Domain oder Capability) - ❌ Keine generischen Namen: `DataService`, `HelperService` --- ### 4. Manager Pattern **Pattern: `{Capability}Manager` für komplexe State-Verwaltung** ```php // ✅ Manager für State/Resource Management final readonly class SessionManager { public function start(): void { } public function regenerate(): void { } public function destroy(): void { } } final readonly class CacheManager { public function clear(): void { } public function warmup(): void { } public function getStats(): CacheStats { } } final readonly class EntityManager { public function persist(object $entity): void { } public function flush(): void { } public function clear(): void { } } ``` **Unterschied Service vs Manager:** - **Service**: Business Logic, stateless Operations - **Manager**: State/Resource Management, komplexe Lifecycle --- ### 5. Handler Pattern **Pattern: Namespace-basierte Unterscheidung** #### Log Handlers ```php // ✅ Log Handlers (Namespace gibt Kontext) namespace App\Framework\Logging\Handlers; final readonly class FileHandler implements LogHandler { } final readonly class ConsoleHandler implements LogHandler { } final readonly class SyslogHandler implements LogHandler { } ``` #### Command Handlers (CQRS) ```php // ✅ Command Handlers namespace App\Application\Contact; final readonly class StoreContactHandler { public function handle(StoreContact $command): void { } } namespace App\Framework\Mail\Commands; final readonly class SendEmailCommandHandler { public function handle(SendEmailCommand $command): void { } } ``` **Regeln:** - ✅ "Handler" Suffix immer vorhanden - ✅ Namespace gibt primären Kontext - ✅ Kein redundanter Prefix (kein "LogFileHandler") --- ### 6. Command Pattern **Zwei Typen: CQRS Commands (kein Suffix) vs Console Commands (mit Suffix)** #### CQRS Commands - Business Logic ```php // ✅ CQRS Commands - KEIN Suffix namespace App\Application\Contact; final readonly class SendEmail { public function __construct( public Email $to, public string $subject, public string $body ) {} } final readonly class StoreContact { public function __construct( public Email $email, public string $name, public string $message ) {} } final readonly class CreateUser { public function __construct( public Email $email, public UserName $name ) {} } ``` #### Console Commands - Infrastructure ```php // ✅ Console Commands - MIT "Command" Suffix namespace App\Framework\Console\Commands; final readonly class HealthCheckCommand implements ConsoleCommand { public function execute(ConsoleInput $input): int { } } namespace App\Framework\Database\Migration\Commands; final readonly class MakeMigrationCommand implements ConsoleCommand { public function execute(ConsoleInput $input): int { } } ``` **Regeln:** - ✅ CQRS/Business Logic: Verb-Noun ohne Suffix - ✅ Console/CLI: Noun + "Command" Suffix - ❌ Keine Vermischung --- ### 7. Exception Pattern **Hybrid-Ansatz: Domain ohne Suffix, Technical mit Suffix** #### Domain Exceptions - OHNE Suffix ```php // ✅ Domain Exceptions - ausdrucksstark, kein Suffix namespace App\Domain\User\Exceptions; final class UserNotFound extends FrameworkException { public static function byId(UserId $id): self { return self::create( ErrorCode::ENTITY_NOT_FOUND, "User with ID '{$id}' not found" )->withData(['user_id' => $id->toString()]); } public static function byEmail(Email $email): self { return self::create( ErrorCode::ENTITY_NOT_FOUND, "User with email not found" )->withData(['email' => $email->getMasked()]); } } final class PaymentFailed extends FrameworkException { public static function becauseOf(string $reason): self { return self::create( ErrorCode::PAYMENT_FAILED, "Payment failed: {$reason}" ); } } ``` #### Technical Exceptions - MIT Suffix ```php // ✅ Technical/Infrastructure Exceptions - mit Suffix für Klarheit namespace App\Framework\Database\Exception; final class DatabaseException extends FrameworkException { } final class SerializationException extends FrameworkException { } namespace App\Framework\Validation\Exceptions; final class ValidationException extends FrameworkException { } ``` **Regeln:** - ✅ Domain/Business Logic: KEIN "Exception" Suffix - ✅ Framework/Infrastructure: MIT "Exception" Suffix - ✅ Factory Methods für expressiveness: `::byId()`, `::becauseOf()` - ✅ Base Exception behält Suffix: `FrameworkException` **Verwendung:** ```php // Sehr lesbar und ausdrucksstark throw UserNotFound::byEmail($email); throw PaymentFailed::becauseOf('Insufficient funds'); throw OrderCancelled::byUser($userId); // Technical Exceptions bleiben explizit throw new DatabaseException('Connection failed'); throw new ValidationException('Invalid input'); ``` --- ### 8. Value Object Pattern **Pattern: Keine Suffixe, beschreibende Namen** ```php // ✅ Value Objects - kurz und prägnant final readonly class Email { public function __construct(public string $value) { $this->validate(); } public function getMasked(): string { // email → e***@example.com } } final readonly class Price { public function __construct( public int $cents, public Currency $currency ) {} public function toDecimal(): string { return number_format($this->cents / 100, 2); } } final readonly class Duration { private function __construct(public int $seconds) {} public static function fromMinutes(int $minutes): self { return new self($minutes * 60); } public static function fromHours(int $hours): self { return new self($hours * 3600); } } ``` **Regeln:** - ✅ Beschreibende Namen ohne Suffix - ✅ Immutable (readonly) - ✅ Rich Domain Logic - ❌ Kein "VO", "ValueObject" Suffix - ❌ Keine primitiven Properties (nutze Validation) --- ### 9. Event Pattern **Pattern: `{Entity}{PastTenseAction}`** ```php // ✅ Domain Events final readonly class UserCreated { public function __construct( public UserId $userId, public Email $email, public DateTimeImmutable $occurredAt ) {} } final readonly class OrderPlaced { public function __construct( public OrderId $orderId, public UserId $userId, public Money $total, public DateTimeImmutable $occurredAt ) {} } final readonly class PaymentProcessed { public function __construct( public PaymentId $paymentId, public Money $amount, public DateTimeImmutable $occurredAt ) {} } ``` **Regeln:** - ✅ Past Tense (Ereignis ist bereits passiert) - ✅ Immutable Event Data - ✅ `occurredAt` Timestamp - ❌ Kein "Event" Suffix (Namespace gibt Kontext) --- ### 10. Middleware Pattern **Pattern: `{Purpose}Middleware`** ```php // ✅ Middleware final readonly class AuthMiddleware implements Middleware { public function process(Request $request, callable $next): Response { } } final readonly class CsrfMiddleware implements Middleware { public function process(Request $request, callable $next): Response { } } final readonly class LoggingMiddleware implements Middleware { public function process(Request $request, callable $next): Response { } } ``` **Regeln:** - ✅ "Middleware" Suffix immer vorhanden - ✅ Beschreibender Purpose-Prefix --- ### 11. Initializer Pattern **Pattern: `{Service}Initializer` für DI Container Setup** ```php // ✅ Initializers final readonly class DatabaseInitializer { #[Initializer] public function __invoke(Container $container): ConnectionInterface { return new PdoConnection( DatabaseConfig::fromEnvironment($this->env) ); } } final readonly class LoggerInitializer { #[Initializer] public function __invoke(Container $container): Logger { return new DefaultLogger( handlers: [$this->fileHandler, $this->consoleHandler] ); } } ``` **Regeln:** - ✅ "Initializer" Suffix - ✅ `#[Initializer]` Attribute - ✅ Returns configured service instance --- ### 12. Interface Pattern **Pattern: Beschreibender Name OHNE "Interface" Suffix** #### Framework Interfaces ```php // ✅ KORREKT - Beschreibende, behavior-fokussierte Namen namespace App\Framework\Cache; interface Cache { public function get(string $key): mixed; public function set(string $key, mixed $value, int $ttl = 3600): bool; public function delete(string $key): bool; public function clear(): bool; } namespace App\Framework\Logging; interface Logger { public function emergency(string $message, array $context = []): void; public function error(string $message, array $context = []): void; public function info(string $message, array $context = []): void; } namespace App\Domain\User; interface UserRepository { public function find(UserId $id): ?User; public function save(User $user): void; public function findByEmail(Email $email): ?User; } ``` #### Anti-Patterns ```php // ❌ FALSCH - "Interface" Suffix ist redundant interface CacheInterface { } interface LoggerInterface { } interface RepositoryInterface { } interface UserRepositoryInterface { } interface EventDispatcherInterface { } // ❌ FALSCH - Generic "I" Prefix (C# Style) interface ICache { } interface ILogger { } interface IRepository { } ``` **Regeln:** - ✅ Beschreibender Name der das Verhalten/Contract beschreibt - ✅ Kein "Interface" Suffix - ✅ Kein "I" Prefix - ✅ Fokus auf Behavior/Capability: "Was kann es tun?" **Philosophie:** - Interface Namen sollten selbsterklärend sein - Der Typ "Interface" ist aus dem Kontext ersichtlich - Fokus auf Contract/Behavior, nicht auf Implementation Details --- ## Directory Structure **Feature-basierte, flache Struktur (DDD-Style)** ``` src/ ├── Application/ │ ├── Contact/ │ │ ├── ShowContact.php # View Controller │ │ ├── SubmitContact.php # Action Controller │ │ ├── ContactRequest.php # Request VO │ │ ├── StoreContact.php # CQRS Command │ │ └── StoreContactHandler.php # Command Handler │ ├── User/ │ │ ├── ShowUserProfile.php │ │ ├── UpdateUserProfile.php │ │ ├── DeleteUser.php │ │ └── UserService.php │ └── Admin/ │ ├── ShowImageManager.php │ ├── CreateImage.php │ └── DeleteImage.php ├── Domain/ │ ├── User/ │ │ ├── User.php # Entity │ │ ├── UserId.php # Value Object │ │ ├── UserRepository.php # Interface │ │ ├── Exceptions/ │ │ │ ├── UserNotFound.php # Domain Exception │ │ │ └── InvalidEmail.php │ │ └── Events/ │ │ ├── UserCreated.php │ │ └── UserDeleted.php │ └── Order/ │ ├── Order.php │ ├── OrderId.php │ ├── OrderRepository.php │ └── Exceptions/ │ └── OrderNotFound.php └── Framework/ ├── Database/ │ ├── DatabaseManager.php │ ├── EntityManager.php │ ├── Exception/ │ │ └── DatabaseException.php # Technical Exception │ └── Repository/ │ └── EntityRepository.php └── Logging/ ├── Logger.php ├── LoggerInitializer.php └── Handlers/ ├── FileHandler.php ├── ConsoleHandler.php └── SyslogHandler.php ``` **Regeln:** - ✅ Feature-basierte Gruppierung - ✅ Zusammengehörige Klassen im selben Namespace - ✅ Keine `/Controllers/` Subfolder - ✅ Exceptions in `/Exceptions/` Subnamespace - ✅ Events in `/Events/` Subnamespace --- ## Method Naming ### Public API Methods ```php // ✅ Verb-Noun Pattern public function getUser(UserId $id): User { } public function createOrder(CreateOrderData $data): Order { } public function updateProfile(UpdateProfileData $data): void { } public function deleteImage(ImageId $id): void { } // ✅ Boolean Methods public function isValid(): bool { } public function hasExpired(): bool { } public function canAccess(User $user): bool { } public function shouldRetry(): bool { } // ✅ Factory Methods public static function fromArray(array $data): self { } public static function fromString(string $value): self { } public static function create(...$args): self { } // ✅ Transformation Methods public function toArray(): array { } public function toString(): string { } public function toJson(): string { } public function toDecimal(): string { } ``` ### Readonly Classes - Public Properties statt Getters ```php // ✅ Readonly Classes: Public Properties final readonly class User { public function __construct( public UserId $id, public Email $email, public UserName $name ) {} } // Verwendung: $user->email; // ✅ Direkt zugreifen $user->name; // ✅ Kein Getter nötig // ❌ FALSCH bei readonly Classes public function getEmail(): Email { return $this->email; } // Redundant! ``` --- ## Anti-Patterns ### ❌ Vermeiden ```php // ❌ Redundante Suffixe bei Domain Code class UserController { } // Nutze: ShowUser, CreateUser class EmailVO { } // Nutze: Email class SendEmailCommand { } // Nutze: SendEmail (CQRS) // ❌ Generische Namen class Helper { } class Util { } class Manager { } // Ohne Kontext class Service { } // Ohne Kontext // ❌ Technische Details in Namen class MySQLUserRepository { } // Nutze: DatabaseUserRepository class RedisCache { } // Nutze: CacheManager mit Redis-Implementation // ❌ Unklare Abkürzungen class UsrCtrl { } class PrdMgr { } // ❌ Vermischte Patterns class ShowContact { public function show(): ViewResult { } public function submit(): ActionResult { } // Sollte SubmitContact sein } ``` --- ## Zusammenfassung **Domain Code (Business Logic):** - Keine redundanten Suffixe - Ausdrucksstark und semantisch - Beispiele: `ShowContact`, `SubmitContact`, `UserNotFound`, `Email` **Infrastructure Code (Framework/Technical):** - Explizite Suffixe für Klarheit - Beispiele: `DatabaseException`, `HealthCheckCommand`, `FileHandler`, `UserRepository` **Hybride Komponenten:** - Nutze Namespace für primären Kontext - Suffix nur wenn wirklich nötig - Beispiele: `Handler` (verschiedene Typen), `Service` (immer mit Suffix) Diese Conventions schaffen Konsistenz über das gesamte Framework und machen den Code selbstdokumentierend und wartbar.