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

20 KiB

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

// ✅ 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

// ✅ 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

// ❌ 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

// ✅ 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

// ✅ 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

// ✅ 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

// ✅ 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)

// ✅ 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

// ✅ 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

// ✅ 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

// ✅ 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

// ✅ 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:

// 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

// ✅ 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}

// ✅ 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

// ✅ 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

// ✅ 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

// ✅ 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

// ✅ 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

// ❌ 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.