Framework/NOTES.md

6.8 KiB

Phred Framework: Project Notes & Brainstorming

This document serves as a mind-map and repository for ideas, architectural decisions, and future considerations for the Phred Framework. It is a living document that captures the "why" behind the "what" defined in the specifications and milestones.

Core Vision & Philosophy

  • Batteries-Included but Swappable: Provide sensible defaults (REST, JSON:API, Eyrie, Pairity) while ensuring every core component can be swapped via Service Providers.
  • Standards First: Strict adherence to PSRs (PSR-7, PSR-11, PSR-14, PSR-15, PSR-18).
  • Modular by Nature: Django-style "Apps" (Modules) where features are encapsulated.
  • Developer Happiness (DX): Zero-config where possible, robust scaffolding, and clear documentation.

Architectural Brainstorming

1. The HTTP Pipeline (PSR-15)

  • Use Relay for the middleware stack.
  • Middleware Layers:
    • Global: CORS, Security Headers, Error Handling (Problem Details).
    • Route-specific: Auth (JWT), Validation, Rate Limiting.
    • Negotiation: ContentNegotiationMiddleware to handle REST vs JSON:API vs HTML.

2. Pluggability & Service Providers

  • Contracts Package: Define all core interfaces in Phred\Support\Contracts.
  • Driver Selection: Use .env keys like ORM_DRIVER, TEMPLATE_DRIVER, CACHE_DRIVER.
  • Lifecycle: Providers should have register() (binding to container) and boot() (executing logic like route registration).

3. MVC & View Layer

  • Invokable Controllers: "One action, one class" to prevent controller bloat.
  • View Objects: A dedicated layer for data transformation/preparation before the template. This keeps controllers focused on flow and templates focused on markup.
  • Response Factories: Abstract the creation of REST vs JSON:API responses.

4. Modular Architecture (Django-style)

  • All user code lives in modules/.
  • Contract-First Persistence: Modules should define pure domain models (POPOs) and repository interfaces. The framework uses a "Bridge" architecture to map these to a specific ORM.
  • Scaffolding: CLI should be able to generate an entire module structure in one command.

5. Persistence Bridge Strategy (ORM-Agnostic)

To achieve true decoupling, Phred adopts a "Persistence Bridge" pattern, mirroring the TaskerBridges architecture. This removes the need for driver-specific directories (like Persistence/Pairity/) within modules and moves implementation details to the framework's infrastructure layer.

Implementation Concept

  1. Flattened Directory Structure:

    • modules/<Module>/Models/: Contains the data objects/entities (POPOs).
    • modules/<Module>/Repositories/: Contains the repository interfaces.
  2. The Bridge as a Translator:

    • The framework boots an active ORM Bridge (e.g., PairityBridge, EloquentBridge) based on .env.
    • The Bridge is responsible for taking a module's model and explaining it to its respective persistence engine using PHP 8 Attributes (metadata).
  3. Automated Discovery & Wiring:

    • Similar to Tasker's command discovery, Phred scans the Models/ directory to register entities.
    • Phred auto-binds repository implementations to their corresponding interfaces in the PSR-11 container.
  4. Environment-Driven: Connection details and driver selection are handled exclusively via environment variables, keeping module code pure.

  5. Migration & Schema Management:

    • Auto-Discovery: Bridges scan Models/ to detect schema changes.
    • Versioned Snapshots: The framework can cache a "last-known" state of the models to generate diff-based migrations.
    • Tooling: CLI commands can generate migration files by comparing current POPO attributes against the database or a cached snapshot.

Example Domain Model (Attribute-Driven)

namespace App\Modules\User\Models;

use Phred\Persistence\Attributes\Entity;
use Phred\Persistence\Attributes\Column;

#[Entity(table: 'users')]
class User
{
    #[Column(primary: true, autoIncrement: true)]
    public int $id;

    #[Column(unique: true)]
    public string $email;

    #[Column(nullable: true)]
    public ?string $name;

    #[Column(name: 'created_at')]
    public \DateTimeImmutable $createdAt;
}

Benefits

  • Zero-Config Persistence: Developers only write the Model and the Interface; Phred handles the connection and injection.
  • Multi-ORM Support: The same model can work across different ORMs by switching the Bridge.
  • Simplified DX: No deeply nested folders for every module; logic is localized and clean.

6. Command Discovery Strategy (Tasker Integration)

To provide a "zero-configuration" experience for developers, the framework should implement an automated directory-based command loader that bridges the application's src/Commands directory with Tasker.

Proposed Implementation Flow

  1. Directory Scan: Use RecursiveDirectoryIterator to find all PHP files in src/Commands.

  2. Class Resolution: Map file paths to fully qualified class names following PSR-4 conventions.

  3. Container-First Loading:

    • Check if the class is already registered in the PSR-11 container.
    • If found, retrieve the instance from the container (ensuring dependency injection).
    • If not found, Tasker's Runner::register($className) will instantiate it directly.
  4. Registration: Pass the resolved class name or instance to $taskerRunner->register().

  5. Migration & Schema Management:

    • Auto-Discovery: Bridges scan Models/ to detect schema changes.
    • Versioned Snapshots: The framework can cache a "last-known" state of the models to generate diff-based migrations.
    • Tooling: CLI commands can generate migration files by comparing current POPO attributes against the database or a cached snapshot.

Benefits

  • Zero Config: Developers just drop a class into the directory.
  • DI Support: Automatically respects container-managed services.
  • Type Safety: Tasker validates that the class implements CommandInterface or uses the HasAttributes trait.

Example Discovery Logic

$commandPath = $appRoot . '/src/Commands';
$namespace = 'App\Commands\';

$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($commandPath));

foreach ($files as $file) {
    if ($file->getExtension() !== 'php') {
        continue;
    }

    $className = $namespace . str_replace(
        ['/', '.php'],
        ['\', ''],
        substr($file->getPathname(), strlen($commandPath) + 1)
    );

    $taskerRunner->register($className);
}

7. Security & Auth

  • JWT by default for APIs.
  • CSRF protection for traditional web routes.
  • Feature Flags: Native integration with FlagPole.

8. Documentation & Discovery

  • Documentation site built with Phred.
  • Dynamic Help: A CLI command that opens the online docs for a specific framework command.
  • OpenAPI: Auto-generation from annotations.