Files
michaelschiemer/docs/claude/naming-conventions.md
Michael Schiemer 5050c7d73a docs: consolidate documentation into organized structure
- 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
2025-10-05 11:05:04 +02:00

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.