- Move 12 markdown files from root to docs/ subdirectories - Organize documentation by category: • docs/troubleshooting/ (1 file) - Technical troubleshooting guides • docs/deployment/ (4 files) - Deployment and security documentation • docs/guides/ (3 files) - Feature-specific guides • docs/planning/ (4 files) - Planning and improvement proposals Root directory cleanup: - Reduced from 16 to 4 markdown files in root - Only essential project files remain: • CLAUDE.md (AI instructions) • README.md (Main project readme) • CLEANUP_PLAN.md (Current cleanup plan) • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements) This improves: ✅ Documentation discoverability ✅ Logical organization by purpose ✅ Clean root directory ✅ Better maintainability
767 lines
20 KiB
Markdown
767 lines
20 KiB
Markdown
# 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
|
|
|
|
---
|
|
|
|
## 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.
|