feat(Production): Complete production deployment infrastructure

- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace App\Framework\LiveComponents\Contracts;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\LiveComponents\Caching\VaryBy;
/**
* Interface for LiveComponents that support caching
*
* Components implementing this interface can cache their rendered output
* and state to improve performance for expensive operations.
*
* Advanced Features:
* - varyBy: Cache key variations based on context (user, locale, etc.)
* - Stale-While-Revalidate: Serve stale content while refreshing
* - Tags: Grouped cache invalidation
*/
interface Cacheable
{
/**
* Get cache key for this component
*
* The cache key should uniquely identify this component's state.
* Include dynamic data that affects rendering.
*
* Note: This is the base key. VaryBy will add context-based suffixes.
*
* @return string Unique cache key
*/
public function getCacheKey(): string;
/**
* Get cache TTL (Time To Live)
*
* @return Duration How long to cache this component
*/
public function getCacheTTL(): Duration;
/**
* Check if component should be cached
*
* Allows dynamic cache control based on state or conditions.
*
* @return bool True if component should be cached
*/
public function shouldCache(): bool;
/**
* Get cache tags for invalidation
*
* Tags allow invalidating multiple cached components at once.
*
* @return array<string> List of cache tags
*/
public function getCacheTags(): array;
/**
* Get varyBy specification for cache key generation
*
* Defines which context factors should vary the cache key.
* Return null for global cache (no context variation).
*
* Examples:
* - VaryBy::userId() - Cache per user
* - VaryBy::locale() - Cache per language
* - VaryBy::userAndLocale() - Cache per user and language
* - VaryBy::custom(['device_type']) - Custom variations
*
* @return VaryBy|null VaryBy specification or null for global cache
*/
public function getVaryBy(): ?VaryBy;
/**
* Get Stale-While-Revalidate (SWR) duration
*
* If set, stale content will be served while background refresh happens.
* This improves perceived performance for expensive components.
*
* Example: TTL = 5 minutes, SWR = 1 hour
* - 0-5min: Serve fresh cache
* - 5min-1h: Serve stale cache + trigger background refresh
* - >1h: Force fresh render
*
* @return Duration|null SWR duration or null to disable
*/
public function getStaleWhileRevalidate(): ?Duration;
}

View File

@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace App\Framework\LiveComponents\Contracts;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
/**
* Interface for Component Registry
*
* Provides testable contract for component registration and resolution.
* Handles BOTH LiveComponents and StaticComponents:
* - LiveComponents: Dynamic, stateful components with #[LiveComponent] attribute
* - StaticComponents: Server-rendered components with #[ComponentName] attribute
*
* Enables mocking in tests without relying on final class implementations.
*
* Framework Pattern: Interface-driven design for testability
*
* State Handling (LiveComponents only):
* - Components use type-safe State Value Objects (CounterState, SearchState, etc.)
* - Registry creates State VOs from arrays by reading component's $state property type
* - Components receive fully constructed State VOs in constructor
*/
interface ComponentRegistryInterface
{
/**
* Resolve component instance from component ID
*
* @param ComponentId $componentId Unique component identifier
* @param array|null $stateData Initial component state as array (null for new instances)
* @return LiveComponentContract Resolved component instance
* @throws \InvalidArgumentException If component not registered
*/
public function resolve(ComponentId $componentId, ?array $stateData = null): LiveComponentContract;
/**
* Render component to HTML
*
* @param LiveComponentContract $component Component to render
* @return string Rendered HTML
*/
public function render(LiveComponentContract $component): string;
/**
* Render component with wrapper (for initial page load)
*
* Includes data-component-id and data-component-state attributes.
*
* @param LiveComponentContract $component Component to render
* @return string Rendered HTML with wrapper
*/
public function renderWithWrapper(LiveComponentContract $component): string;
/**
* Check if component is registered
*
* @param string $componentName Component name (e.g., 'datatable', 'counter')
* @return bool True if registered, false otherwise
*/
public function isRegistered(string $componentName): bool;
/**
* Get component class name
*
* @param string $componentName Component name
* @return string|null Fully qualified class name or null if not found
*/
public function getClassName(string $componentName): ?string;
/**
* Get all registered component names
*
* @return array<int, string> List of registered component names
*/
public function getAllComponentNames(): array;
}

View File

@@ -0,0 +1,112 @@
<?php
declare(strict_types=1);
namespace App\Framework\LiveComponents\Contracts;
/**
* LifecycleAware Interface
*
* Optional interface for LiveComponents that need lifecycle hooks.
* Components can implement this interface to receive lifecycle callbacks.
*
* ⚠️ IMPORTANT: Lifecycle Hooks in Readonly Components
*
* Since components are readonly, lifecycle hooks CANNOT modify component state.
* - All lifecycle hooks return void (no state modification)
* - State changes ONLY via Actions that return ComponentData
* - Use lifecycle hooks for side effects only:
* - Logging (error_log, logger->info)
* - External events (eventBus->dispatch)
* - External services (api calls, cache invalidation)
* - Resource management (open/close connections, start/stop timers)
*
* Usage:
* ```php
* final readonly class TimerComponent implements LiveComponentContract, LifecycleAware
* {
* // ✅ CORRECT: Side effects only
* public function onMount(): void
* {
* error_log("Component {$this->id->toString()} mounted");
* $this->eventBus->dispatch(new ComponentMountedEvent($this->id));
* }
*
* // ❌ WRONG: Cannot mutate state in readonly class
* public function onMount(): void
* {
* $this->data['mounted'] = true; // PHP Error: Cannot modify readonly property!
* }
*
* // ✅ CORRECT: State changes via Actions
* public function mount(): ComponentData
* {
* $state = $this->data->toArray();
* $state['mounted'] = true;
* return ComponentData::fromArray($state);
* }
* }
* ```
*
* Lifecycle Flow:
* 1. onMount() - Called once after component is first created (server-side)
* 2. onUpdate() - Called after each action that updates state
* 3. onDestroy() - Called when component is removed (client-side via JavaScript)
*/
interface LifecycleAware
{
/**
* Called once after component is first mounted (server-side)
*
* ⚠️ SIDE EFFECTS ONLY - Cannot modify component state!
*
* Use for:
* - Log component mount (error_log, logger)
* - Dispatch domain events (eventBus->dispatch)
* - Initialize external resources (open connections, start timers)
* - Subscribe to external events
* - Start background processes
*
* ❌ DO NOT: Modify $this->data or any component property
* ✅ DO: External side effects only
*/
public function onMount(): void;
/**
* Called after each state update (server-side)
*
* ⚠️ SIDE EFFECTS ONLY - Cannot modify component state!
*
* Use for:
* - Log state transitions
* - Dispatch state change events
* - Update external resources (cache, search index)
* - Sync with external services
* - Validate state consistency (throw exceptions if invalid)
*
* ❌ DO NOT: Modify $this->data or return new state
* ✅ DO: React to state changes with external side effects
*/
public function onUpdate(): void;
/**
* Called before component is destroyed (client-side)
*
* ⚠️ SIDE EFFECTS ONLY - Cannot modify component state!
*
* Use for:
* - Stop timers and background processes
* - Close database connections
* - Unsubscribe from events
* - Cleanup resources
* - Persist important state to storage
* - Log component removal
*
* ❌ DO NOT: Modify $this->data or expect state changes
* ✅ DO: Cleanup external resources
*
* Note: This hook is primarily called client-side via JavaScript
* when component element is removed from DOM.
*/
public function onDestroy(): void;
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace App\Framework\LiveComponents\Contracts;
use App\Application\LiveComponents\LiveComponentState;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
use App\Framework\LiveComponents\ValueObjects\ComponentRenderData;
/**
* LiveComponent Contract
*
* Defines the minimal interface for LiveComponents.
*
* Components have public readonly properties for id and state, so no getters needed.
* Action handling is delegated to LiveComponentHandler (composition over inheritance).
* Rendering is delegated to LiveComponentRenderer in the View module.
*/
interface LiveComponentContract
{
public ComponentId $id {get;}
public LiveComponentState $state {get;}
/**
* Get render data for the component
*
* Returns template path and data instead of rendered HTML.
* Rendering is delegated to the View module (LiveComponentRenderer).
*/
public function getRenderData(): ComponentRenderData;
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Framework\LiveComponents\Contracts;
use App\Application\LiveComponents\LiveComponentState;
use App\Framework\LiveComponents\ValueObjects\ComponentData;
interface Pollable
{
/**
* Poll for new data
*
* @return ComponentData Updated component data
*/
public function poll(): LiveComponentState;
/**
* Get polling interval in milliseconds
*/
public function getPollInterval(): int;
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace App\Framework\LiveComponents\Contracts;
use App\Application\LiveComponents\LiveComponentState;
use App\Framework\Http\UploadedFile;
use App\Framework\LiveComponents\ComponentEventDispatcher;
/**
* Interface for LiveComponents that support file uploads
*
* Components implementing this interface can handle file uploads
* through the LiveComponent upload endpoint.
*
* Following the same pattern as regular LiveComponent actions,
* handleUpload() returns the component's State object with updated state.
* The Handler will create the ComponentUpdate from the returned state.
*/
interface SupportsFileUpload
{
/**
* Handle file upload
*
* @param UploadedFile $file The uploaded file
* @param ComponentEventDispatcher|null $events Optional event dispatcher
* @return LiveComponentState Updated component state
*/
public function handleUpload(UploadedFile $file, ?ComponentEventDispatcher $events = null): LiveComponentState;
/**
* Validate uploaded file
*
* @param UploadedFile $file The file to validate
* @return array Array of validation errors (empty if valid)
*/
public function validateUpload(UploadedFile $file): array;
/**
* Get allowed MIME types for upload
*
* @return array<string> List of allowed MIME types (empty = allow all)
*/
public function getAllowedMimeTypes(): array;
/**
* Get maximum file size in bytes
*
* @return int Maximum file size (0 = no limit)
*/
public function getMaxFileSize(): int;
}

View File

@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace App\Framework\LiveComponents\Contracts;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
/**
* Supports Nesting Contract
*
* Components implementing this interface can have child components.
* Enables complex UI composition with parent-child relationships.
*
* Features:
* - Child component registration
* - Parent-to-child communication
* - Child state synchronization
* - Lifecycle management for children
*
* Example:
* ```php
* #[LiveComponent('todo-list')]
* final readonly class TodoListComponent implements SupportsNesting
* {
* public function getChildComponents(): array
* {
* return [
* 'todo-item:1',
* 'todo-item:2',
* 'todo-item:3'
* ];
* }
*
* public function onChildEvent(ComponentId $childId, string $eventName, array $payload): void
* {
* if ($eventName === 'item-completed') {
* // Handle child event
* }
* }
* }
* ```
*/
interface SupportsNesting
{
/**
* Get list of child component IDs
*
* Returns array of component ID strings that are children of this component.
* Framework will ensure proper lifecycle management for all children.
*
* @return array<string> Array of component ID strings
*/
public function getChildComponents(): array;
/**
* Handle event from child component
*
* Called when a child component dispatches an event that should bubble up.
* Parent can handle the event and optionally prevent further bubbling.
*
* @param ComponentId $childId Child component that dispatched the event
* @param string $eventName Name of the event
* @param array $payload Event payload data
* @return bool Return false to stop event bubbling, true to continue
*/
public function onChildEvent(ComponentId $childId, string $eventName, array $payload): bool;
/**
* Validate child component compatibility
*
* Called before adding a child component. Parent can reject incompatible children.
*
* @param ComponentId $childId Child component ID to validate
* @return bool True if child is compatible, false otherwise
*/
public function canHaveChild(ComponentId $childId): bool;
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace App\Framework\LiveComponents\Contracts;
use App\Framework\LiveComponents\ValueObjects\SlotContent;
use App\Framework\LiveComponents\ValueObjects\SlotContext;
use App\Framework\LiveComponents\ValueObjects\SlotDefinition;
/**
* Supports Slots Contract
*
* Components implementing this interface can define slots that can be filled
* with content from parent components or consumers.
*
* Similar to Vue's slots or React's children/render props pattern.
*
* Example:
* ```php
* #[LiveComponent('card')]
* final readonly class CardComponent implements SupportsSlots
* {
* public function getSlotDefinitions(): array
* {
* return [
* SlotDefinition::default('<p>Default content</p>'),
* SlotDefinition::named('header'),
* SlotDefinition::named('footer'),
* ];
* }
*
* public function getSlotContext(string $slotName): SlotContext
* {
* return SlotContext::create([
* 'card_title' => $this->state->title,
* 'card_type' => $this->state->type
* ]);
* }
* }
* ```
*/
interface SupportsSlots
{
/**
* Get slot definitions for this component
*
* Returns array of SlotDefinition objects that define available slots.
*
* @return array<SlotDefinition> Array of slot definitions
*/
public function getSlotDefinitions(): array;
/**
* Get context data for a specific slot (scoped slots)
*
* Returns data that will be available to slot content.
* Used for scoped slots where parent needs access to child data.
*
* @param string $slotName Name of the slot
* @return SlotContext Context data for the slot
*/
public function getSlotContext(string $slotName): SlotContext;
/**
* Process slot content before rendering
*
* Optional hook to transform or validate slot content.
* Default implementation should return content unchanged.
*
* @param SlotContent $content Slot content to process
* @return SlotContent Processed slot content
*/
public function processSlotContent(SlotContent $content): SlotContent;
/**
* Validate that required slots are filled
*
* Called before rendering to ensure all required slots have content.
*
* @param array<SlotContent> $providedSlots Slots provided by parent
* @return array<string> Array of validation error messages (empty if valid)
*/
public function validateSlots(array $providedSlots): array;
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace App\Framework\LiveComponents\Contracts;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\LiveComponents\ValueObjects\UploadedComponentFile;
interface Uploadable
{
/**
* Handle file upload
*
* @return array Updated component data
*/
public function handleUpload(UploadedComponentFile $file): array;
/**
* Validate uploaded file
*/
public function validateFile(UploadedComponentFile $file): bool;
/**
* Get maximum file size
*/
public function getMaxFileSize(): Byte;
/**
* Get allowed MIME types
*
* @return array<string>
*/
public function getAllowedMimeTypes(): array;
}