From 5011997516a245f8a1d466bd9b002b8ac26a0746 Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Fri, 21 Feb 2025 18:52:39 +0100 Subject: [PATCH 1/3] prepare for v2.1.5 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 128c88f..7127cbf 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "prefer-stable": true, "require": { "php": ">=8.2", - "php-flasher/flasher": "^2.1.4", + "php-flasher/flasher": "^2.1.5", "symfony/config": "^7.0", "symfony/console": "^7.0", "symfony/dependency-injection": "^7.0", From 14bd1ba6bbd1184bde0300a5b02455e886845cea Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Fri, 21 Feb 2025 21:02:50 +0100 Subject: [PATCH 2/3] bump flasher to v2.1.6 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7127cbf..b8f20db 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "prefer-stable": true, "require": { "php": ">=8.2", - "php-flasher/flasher": "^2.1.5", + "php-flasher/flasher": "^2.1.6", "symfony/config": "^7.0", "symfony/console": "^7.0", "symfony/dependency-injection": "^7.0", From a2329ca20a74fd45761b7268ee7a80c872620428 Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Sun, 9 Mar 2025 00:28:06 +0000 Subject: [PATCH 3/3] docs: enhance code documentation with comprehensive PHPDoc comments --- .github/FUNDING.yml | 1 - Attribute/AsFlasherFactory.php | 25 +++++ Attribute/AsFlasherPresenter.php | 26 +++++ Command/InstallCommand.php | 97 ++++++++++++++++++- Component/FlasherComponent.php | 34 ++++++- .../Compiler/EventListenerCompilerPass.php | 17 ++++ .../Compiler/PresenterCompilerPass.php | 17 ++++ EventListener/FlasherListener.php | 33 +++++++ EventListener/SessionListener.php | 32 ++++++ Factory/NotificationFactoryLocator.php | 33 ++++++- FlasherSymfonyBundle.php | 32 ++++++ Http/Request.php | 22 +++++ Http/Response.php | 17 ++++ Profiler/FlasherDataCollector.php | 53 ++++++++++ Storage/FallbackSession.php | 17 +++- Storage/FallbackSessionInterface.php | 11 ++- Storage/SessionBag.php | 47 +++++++++ Support/PluginBundle.php | 56 ++++++++++- Support/PluginBundleInterface.php | 24 +++++ Template/TwigTemplateEngine.php | 26 +++++ Translation/Translator.php | 38 ++++++++ Twig/FlasherTwigExtension.php | 24 +++++ 22 files changed, 673 insertions(+), 9 deletions(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 895dabf..c621ab0 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1 @@ github: yoeunes -custom: https://www.paypal.com/paypalme/yoeunes diff --git a/Attribute/AsFlasherFactory.php b/Attribute/AsFlasherFactory.php index 22cdcd5..e05e0cb 100644 --- a/Attribute/AsFlasherFactory.php +++ b/Attribute/AsFlasherFactory.php @@ -4,9 +4,34 @@ namespace Flasher\Symfony\Attribute; +/** + * AsFlasherFactory - Attribute for tagging notification factories. + * + * This attribute enables auto-configuration of notification factory services in Symfony. + * When applied to a factory class, it automatically registers the class with the container + * using the specified alias, making it available to the PHPFlasher system. + * + * Design patterns: + * - Attribute-based Configuration: Uses PHP 8 attributes for declarative service configuration + * - Service Tagging: Implements Symfony's tag-based service discovery mechanism + * + * Usage: + * ```php + * #[AsFlasherFactory('toastr')] + * class ToastrFactory implements NotificationFactoryInterface + * { + * // ... + * } + * ``` + */ #[\Attribute(\Attribute::TARGET_CLASS)] final readonly class AsFlasherFactory { + /** + * Creates a new AsFlasherFactory attribute. + * + * @param string $alias The unique alias for the notification factory (e.g., 'toastr', 'noty') + */ public function __construct(public string $alias) { } diff --git a/Attribute/AsFlasherPresenter.php b/Attribute/AsFlasherPresenter.php index 33affd2..20e8a02 100644 --- a/Attribute/AsFlasherPresenter.php +++ b/Attribute/AsFlasherPresenter.php @@ -4,9 +4,35 @@ namespace Flasher\Symfony\Attribute; +/** + * AsFlasherPresenter - Attribute for tagging response presenters. + * + * This attribute enables auto-configuration of response presenter services in Symfony. + * When applied to a presenter class, it automatically registers the class with the + * container using the specified alias, making it available to PHPFlasher's response system. + * + * Design patterns: + * - Attribute-based Configuration: Uses PHP 8 attributes for declarative service configuration + * - Service Tagging: Implements Symfony's tag-based service discovery mechanism + * - Strategy Pattern: Supports pluggable response formatting strategies + * + * Usage: + * ```php + * #[AsFlasherPresenter('html')] + * class HtmlPresenter implements PresenterInterface + * { + * // ... + * } + * ``` + */ #[\Attribute(\Attribute::TARGET_CLASS)] final readonly class AsFlasherPresenter { + /** + * Creates a new AsFlasherPresenter attribute. + * + * @param string $alias The unique alias for the presenter (e.g., 'html', 'json') + */ public function __construct(public string $alias) { } diff --git a/Command/InstallCommand.php b/Command/InstallCommand.php index 27a29e2..1bdda6a 100644 --- a/Command/InstallCommand.php +++ b/Command/InstallCommand.php @@ -16,13 +16,33 @@ use Symfony\Component\Finder\Finder; use Symfony\Component\HttpKernel\KernelInterface; +/** + * InstallCommand - Console command for installing PHPFlasher resources. + * + * This command provides a CLI interface for installing PHPFlasher resources, + * including assets (JS and CSS files) and configuration files. It discovers + * all registered PHPFlasher plugins and installs their resources. + * + * Design patterns: + * - Command Pattern: Implements the command pattern for console interaction + * - Discovery Pattern: Automatically discovers and processes registered plugins + * - Template Method Pattern: Defines a structured workflow with specific steps + */ final class InstallCommand extends Command { + /** + * Creates a new InstallCommand instance. + * + * @param AssetManagerInterface $assetManager Manager for handling PHPFlasher assets + */ public function __construct(private readonly AssetManagerInterface $assetManager) { parent::__construct(); } + /** + * Configure the command options and help text. + */ protected function configure(): void { $this @@ -33,8 +53,20 @@ protected function configure(): void ->addOption('symlink', 's', InputOption::VALUE_NONE, 'Symlink PHPFlasher assets instead of copying them.'); } + /** + * Execute the command to install PHPFlasher resources. + * + * This method processes each registered bundle that implements PluginBundleInterface, + * installing its assets and configuration files as requested. + * + * @param InputInterface $input The input interface + * @param OutputInterface $output The output interface + * + * @return int Command status code (SUCCESS or FAILURE) + */ protected function execute(InputInterface $input, OutputInterface $output): int { + // Display PHPFlasher banner and info message $output->writeln(''); $output->writeln(' ██████╗ ██╗ ██╗██████╗ ███████╗██╗ █████╗ ███████╗██╗ ██╗███████╗██████╗ @@ -50,11 +82,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln(' INFO Copying PHPFlasher resources...'); $output->writeln(''); + // Get application and validate it's a Symfony application $application = $this->getApplication(); if (!$application instanceof Application) { return self::SUCCESS; } + // Process command options $useSymlinks = (bool) $input->getOption('symlink'); if ($useSymlinks) { $output->writeln('Using symlinks to publish assets.'); @@ -67,6 +101,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln('Publishing configuration files.'); } + // Prepare directories $publicDir = $this->getPublicDir().'/vendor/flasher/'; $configDir = $this->getConfigDir(); @@ -74,6 +109,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $filesystem->remove($publicDir); $filesystem->mkdir($publicDir); + // Process each plugin bundle $files = []; $exitCode = self::SUCCESS; @@ -87,15 +123,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int $configFile = $bundle->getConfigurationFile(); try { + // Install assets and config $files[] = $this->publishAssets($plugin, $publicDir, $useSymlinks); if ($publishConfig) { $this->publishConfig($plugin, $configDir, $configFile); } + // Report success $status = \sprintf('%s', '\\' === \DIRECTORY_SEPARATOR ? 'OK' : "\xE2\x9C\x94" /* HEAVY CHECK MARK (U+2714) */); $output->writeln(\sprintf(' %s %s', $status, $plugin->getAlias())); } catch (\Exception $e) { + // Report failure $exitCode = self::FAILURE; $status = \sprintf('%s', '\\' === \DIRECTORY_SEPARATOR ? 'ERROR' : "\xE2\x9C\x98" /* HEAVY BALLOT X (U+2718) */); $output->writeln(\sprintf(' %s %s %s', $status, $plugin->getAlias(), $e->getMessage())); @@ -104,6 +143,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln(''); + // Display final status message if (self::SUCCESS === $exitCode) { $message = 'PHPFlasher resources have been successfully installed.'; if ($publishConfig) { @@ -117,6 +157,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln(' ERROR An error occurred during the installation of PHPFlasher resources.'); } + // Create asset manifest $this->assetManager->createManifest(array_merge([], ...$files)); $output->writeln(''); @@ -125,7 +166,16 @@ protected function execute(InputInterface $input, OutputInterface $output): int } /** - * @return string[] + * Publishes assets from the plugin's assets directory to the public directory. + * + * This method copies or symlinks asset files from the plugin's assets directory + * to the public directory for web access. + * + * @param PluginInterface $plugin The plugin containing assets + * @param string $publicDir Target directory for assets + * @param bool $useSymlinks Whether to symlink files instead of copying + * + * @return string[] List of target paths for generated manifest */ private function publishAssets(PluginInterface $plugin, string $publicDir, bool $useSymlinks): array { @@ -157,6 +207,16 @@ private function publishAssets(PluginInterface $plugin, string $publicDir, bool return $files; } + /** + * Publishes configuration files to the application's config directory. + * + * This method copies plugin configuration files to the Symfony config directory, + * but only if the target file doesn't already exist (to avoid overwriting user customizations). + * + * @param PluginInterface $plugin The plugin containing configuration + * @param string|null $configDir Target config directory + * @param string $configFile Source configuration file path + */ private function publishConfig(PluginInterface $plugin, ?string $configDir, string $configFile): void { if (null === $configDir || !file_exists($configFile)) { @@ -172,6 +232,15 @@ private function publishConfig(PluginInterface $plugin, ?string $configDir, stri $filesystem->copy($configFile, $target); } + /** + * Gets the path to the public directory. + * + * This method tries to locate the public directory using multiple strategies: + * 1. First, it looks for a standard 'public' directory in the project + * 2. If not found, it falls back to the composer.json configuration + * + * @return string|null Path to the public directory or null if not found + */ private function getPublicDir(): ?string { $projectDir = $this->getProjectDir(); @@ -188,6 +257,15 @@ private function getPublicDir(): ?string return $this->getComposerDir('public-dir'); } + /** + * Gets the path to the config directory. + * + * This method tries to locate the config/packages directory using multiple strategies: + * 1. First, it looks for a standard 'config/packages' directory in the project + * 2. If not found, it falls back to the composer.json configuration + * + * @return string|null Path to the config directory or null if not found + */ private function getConfigDir(): ?string { $projectDir = $this->getProjectDir(); @@ -205,6 +283,11 @@ private function getConfigDir(): ?string return $this->getComposerDir('config-dir'); } + /** + * Gets the project root directory from the kernel. + * + * @return string|null The project directory path or null if not available + */ private function getProjectDir(): ?string { $kernel = $this->getKernel(); @@ -220,6 +303,13 @@ private function getProjectDir(): ?string return \is_string($projectDir) ? $projectDir : null; } + /** + * Gets a directory path from composer.json extra configuration. + * + * @param string $dir The directory key to look for in composer.json extra section + * + * @return string|null The directory path or null if not found + */ private function getComposerDir(string $dir): ?string { $projectDir = $this->getProjectDir(); @@ -240,6 +330,11 @@ private function getComposerDir(string $dir): ?string return $composerConfig['extra'][$dir] ?? null; } + /** + * Gets the kernel instance from the application. + * + * @return KernelInterface|null The Symfony kernel or null if not available + */ private function getKernel(): ?KernelInterface { $application = $this->getApplication(); diff --git a/Component/FlasherComponent.php b/Component/FlasherComponent.php index b97d35d..c3e17f8 100644 --- a/Component/FlasherComponent.php +++ b/Component/FlasherComponent.php @@ -4,13 +4,43 @@ namespace Flasher\Symfony\Component; +/** + * FlasherComponent - Twig component for rendering notifications. + * + * This class implements a Twig component that can be used in templates to render + * PHPFlasher notifications. It supports customizing the filtering criteria, + * presenter format, and rendering context. + * + * Design patterns: + * - Component-based Architecture: Implements Twig's component pattern + * - Data Transfer Object: Holds configuration for notification rendering + * + * Usage in templates: + * ```twig + * + * ``` + */ final class FlasherComponent { - /** @var array */ + /** + * Filtering criteria for notifications. + * + * @var array + */ public array $criteria = []; + /** + * Presentation format (e.g., 'html', 'json'). + */ public string $presenter = 'html'; - /** @var array */ + /** + * Additional context for rendering. + * + * @var array + */ public array $context = []; } diff --git a/DependencyInjection/Compiler/EventListenerCompilerPass.php b/DependencyInjection/Compiler/EventListenerCompilerPass.php index 3f90aa2..4bcf553 100644 --- a/DependencyInjection/Compiler/EventListenerCompilerPass.php +++ b/DependencyInjection/Compiler/EventListenerCompilerPass.php @@ -8,8 +8,25 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; +/** + * EventListenerCompilerPass - Registers event listeners with the event dispatcher. + * + * This compiler pass finds all services tagged with 'flasher.event_listener' + * and registers them with the PHPFlasher event dispatcher. This allows for + * automatic discovery and registration of event listeners. + * + * Design patterns: + * - Compiler Pass: Modifies container definitions during compilation + * - Service Discovery: Automatically discovers tagged services + * - Observer Pattern: Helps set up the event dispatch/observer system + */ final class EventListenerCompilerPass implements CompilerPassInterface { + /** + * Process the container to register event listeners. + * + * @param ContainerBuilder $container The service container builder + */ public function process(ContainerBuilder $container): void { $definition = $container->findDefinition('flasher.event_dispatcher'); diff --git a/DependencyInjection/Compiler/PresenterCompilerPass.php b/DependencyInjection/Compiler/PresenterCompilerPass.php index c76c20f..31558c0 100644 --- a/DependencyInjection/Compiler/PresenterCompilerPass.php +++ b/DependencyInjection/Compiler/PresenterCompilerPass.php @@ -9,8 +9,25 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; +/** + * PresenterCompilerPass - Registers response presenters with the response manager. + * + * This compiler pass finds all services tagged with 'flasher.presenter' + * and registers them with the PHPFlasher response manager. This allows for + * automatic discovery and registration of response presenters. + * + * Design patterns: + * - Compiler Pass: Modifies container definitions during compilation + * - Service Discovery: Automatically discovers tagged services + * - Strategy Pattern: Helps set up pluggable response presentation strategies + */ final class PresenterCompilerPass implements CompilerPassInterface { + /** + * Process the container to register presenters. + * + * @param ContainerBuilder $container The service container builder + */ public function process(ContainerBuilder $container): void { $definition = $container->findDefinition('flasher.response_manager'); diff --git a/EventListener/FlasherListener.php b/EventListener/FlasherListener.php index 1fcfec9..639d202 100644 --- a/EventListener/FlasherListener.php +++ b/EventListener/FlasherListener.php @@ -10,12 +10,37 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\ResponseEvent; +/** + * FlasherListener - Injects PHPFlasher assets into responses. + * + * This event subscriber listens for kernel.response events and injects + * PHPFlasher JavaScript and CSS assets into appropriate HTTP responses. + * It adapts Symfony's request and response objects to PHPFlasher's interfaces. + * + * Design patterns: + * - Observer Pattern: Observes Symfony's kernel events + * - Adapter Pattern: Adapts Symfony's request/response to PHPFlasher's interfaces + * - Event Subscriber: Subscribes to Symfony's event dispatcher system + */ final readonly class FlasherListener implements EventSubscriberInterface { + /** + * Creates a new FlasherListener instance. + * + * @param ResponseExtensionInterface $responseExtension Service for extending responses with notifications + */ public function __construct(private ResponseExtensionInterface $responseExtension) { } + /** + * Processes the response to inject PHPFlasher assets. + * + * This handler adapts Symfony's request and response objects to PHPFlasher's + * interfaces, then delegates to the response extension for asset injection. + * + * @param ResponseEvent $event The response event + */ public function onKernelResponse(ResponseEvent $event): void { $request = new Request($event->getRequest()); @@ -24,6 +49,14 @@ public function onKernelResponse(ResponseEvent $event): void $this->responseExtension->render($request, $response); } + /** + * {@inheritdoc} + * + * Returns events this subscriber listens to and their corresponding handlers. + * The low priority (-20) ensures this runs after most other response listeners. + * + * @return array> The events and handlers + */ public static function getSubscribedEvents(): array { return [ diff --git a/EventListener/SessionListener.php b/EventListener/SessionListener.php index 7ad2e64..040550e 100644 --- a/EventListener/SessionListener.php +++ b/EventListener/SessionListener.php @@ -10,12 +10,37 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\ResponseEvent; +/** + * SessionListener - Processes session flash messages. + * + * This event subscriber listens for kernel.response events and converts + * Symfony's session flash messages to PHPFlasher notifications. This enables + * PHPFlasher to work with existing code that uses Symfony's flash messaging. + * + * Design patterns: + * - Observer Pattern: Observes Symfony's kernel events + * - Adapter Pattern: Adapts Symfony's flash messages to PHPFlasher notifications + * - Transformer Pattern: Transforms data from one format to another + */ final readonly class SessionListener implements EventSubscriberInterface { + /** + * Creates a new SessionListener instance. + * + * @param RequestExtensionInterface $requestExtension Service for processing request flash messages + */ public function __construct(private RequestExtensionInterface $requestExtension) { } + /** + * Processes the request to convert flash messages to notifications. + * + * This handler adapts Symfony's request and response objects to PHPFlasher's + * interfaces, then delegates to the request extension for flash processing. + * + * @param ResponseEvent $event The response event + */ public function onKernelResponse(ResponseEvent $event): void { $request = new Request($event->getRequest()); @@ -24,6 +49,13 @@ public function onKernelResponse(ResponseEvent $event): void $this->requestExtension->flash($request, $response); } + /** + * {@inheritdoc} + * + * Returns events this subscriber listens to and their corresponding handlers. + * + * @return array> The events and handlers + */ public static function getSubscribedEvents(): array { return [ diff --git a/Factory/NotificationFactoryLocator.php b/Factory/NotificationFactoryLocator.php index 41d22b8..9315fe6 100644 --- a/Factory/NotificationFactoryLocator.php +++ b/Factory/NotificationFactoryLocator.php @@ -8,20 +8,51 @@ use Flasher\Prime\Factory\NotificationFactoryLocatorInterface; use Symfony\Component\DependencyInjection\ServiceLocator; +/** + * NotificationFactoryLocator - Locator for notification factories using Symfony's ServiceLocator. + * + * This class adapts Symfony's ServiceLocator to PHPFlasher's NotificationFactoryLocatorInterface, + * enabling runtime discovery and lazy-loading of notification factories. + * + * Design patterns: + * - Adapter Pattern: Adapts Symfony's ServiceLocator to PHPFlasher's interface + * - Service Locator Pattern: Provides unified access to notification factory services + * - Lazy Loading: Services are only instantiated when requested + */ final readonly class NotificationFactoryLocator implements NotificationFactoryLocatorInterface { /** - * @param ServiceLocator $serviceLocator + * Creates a new NotificationFactoryLocator instance. + * + * @param ServiceLocator $serviceLocator Symfony's service locator */ public function __construct(private ServiceLocator $serviceLocator) { } + /** + * {@inheritdoc} + * + * Checks if a notification factory with the given ID exists. + * + * @param string $id The factory identifier + * + * @return bool True if the factory exists, false otherwise + */ public function has(string $id): bool { return $this->serviceLocator->has($id); } + /** + * {@inheritdoc} + * + * Gets a notification factory by ID. + * + * @param string $id The factory identifier + * + * @return NotificationFactoryInterface The notification factory + */ public function get(string $id): NotificationFactoryInterface { return $this->serviceLocator->get($id); diff --git a/FlasherSymfonyBundle.php b/FlasherSymfonyBundle.php index 24351a1..df8dd25 100644 --- a/FlasherSymfonyBundle.php +++ b/FlasherSymfonyBundle.php @@ -13,8 +13,25 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; +/** + * FlasherSymfonyBundle - Main bundle for PHPFlasher Symfony integration. + * + * This bundle serves as the entry point for integrating PHPFlasher with Symfony. + * It registers compiler passes, configures the container extension, and sets up + * the global container instance for PHPFlasher. + * + * Design patterns: + * - Bundle: Implements Symfony's bundle pattern for packaging functionality + * - Registry: Sets up the container registry for PHPFlasher + * - Extension: Extends the base plugin bundle with PHPFlasher-specific functionality + */ final class FlasherSymfonyBundle extends Support\PluginBundle // Symfony\Component\HttpKernel\Bundle\Bundle { + /** + * Set up the global container reference when the bundle boots. + * + * This allows PHPFlasher to access services from Symfony's container. + */ public function boot(): void { if ($this->container instanceof ContainerInterface) { @@ -22,17 +39,32 @@ public function boot(): void } } + /** + * Register compiler passes with the container. + * + * @param ContainerBuilder $container The container builder + */ public function build(ContainerBuilder $container): void { $container->addCompilerPass(new EventListenerCompilerPass()); $container->addCompilerPass(new PresenterCompilerPass()); } + /** + * Get the container extension for this bundle. + * + * @return ExtensionInterface The bundle extension + */ public function getContainerExtension(): ExtensionInterface { return new FlasherExtension($this->createPlugin()); } + /** + * Create the core PHPFlasher plugin. + * + * @return FlasherPlugin The core PHPFlasher plugin + */ public function createPlugin(): FlasherPlugin { return new FlasherPlugin(); diff --git a/Http/Request.php b/Http/Request.php index fbb1f4d..5d28d53 100644 --- a/Http/Request.php +++ b/Http/Request.php @@ -10,8 +10,25 @@ use Symfony\Component\HttpFoundation\Session\FlashBagAwareSessionInterface; use Symfony\Component\HttpFoundation\Session\SessionInterface; +/** + * Request - Adapter for Symfony's HTTP request. + * + * This class implements PHPFlasher's RequestInterface for Symfony's HTTP request, + * providing a consistent interface for request inspection and session interaction + * regardless of the underlying framework. + * + * Design patterns: + * - Adapter Pattern: Adapts framework-specific request to PHPFlasher's interface + * - Decorator Pattern: Adds PHPFlasher-specific functionality to request objects + * - Null Object Pattern: Gracefully handles missing sessions + */ final readonly class Request implements RequestInterface { + /** + * Creates a new Request adapter. + * + * @param SymfonyRequest $request The underlying Symfony request object + */ public function __construct(private SymfonyRequest $request) { } @@ -75,6 +92,11 @@ public function forgetType(string $type): void $this->getType($type); } + /** + * Gets the session from the request, with graceful handling of missing sessions. + * + * @return SessionInterface|null The session or null if not available + */ private function getSession(): ?SessionInterface { try { diff --git a/Http/Response.php b/Http/Response.php index 890b9c5..66097d4 100644 --- a/Http/Response.php +++ b/Http/Response.php @@ -8,8 +8,25 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response as SymfonyResponse; +/** + * Response - Adapter for Symfony's HTTP response. + * + * This class implements PHPFlasher's ResponseInterface for Symfony's HTTP response, + * providing a consistent interface for response manipulation regardless of the framework. + * It allows PHPFlasher to work with Symfony responses in a framework-agnostic way. + * + * Design patterns: + * - Adapter Pattern: Adapts framework-specific response to PHPFlasher's interface + * - Decorator Pattern: Adds PHPFlasher-specific functionality to response objects + * - Composition: Uses composition to delegate to the underlying response object + */ final readonly class Response implements ResponseInterface { + /** + * Creates a new Response adapter. + * + * @param SymfonyResponse $response The underlying Symfony response object + */ public function __construct(private SymfonyResponse $response) { } diff --git a/Profiler/FlasherDataCollector.php b/Profiler/FlasherDataCollector.php index ba8dbe1..2acd4a0 100644 --- a/Profiler/FlasherDataCollector.php +++ b/Profiler/FlasherDataCollector.php @@ -15,6 +15,17 @@ use Symfony\Component\VarDumper\Cloner\Data; /** + * FlasherDataCollector - Collects PHPFlasher data for the Symfony profiler. + * + * This data collector captures information about PHPFlasher notifications, + * both dispatched and displayed, for the Symfony web profiler. It also collects + * configuration and version information. + * + * Design patterns: + * - Data Collector: Implements Symfony's data collector pattern for profiling + * - Late Collection: Collects data after a request is completed + * - Type Safety: Uses PHPDoc annotations for complex type declarations + * * @phpstan-type NotificationShape array{ * title: string, * message: string, @@ -51,6 +62,11 @@ final class FlasherDataCollector extends AbstractDataCollector implements LateDataCollectorInterface { /** + * Creates a new FlasherDataCollector instance. + * + * @param NotificationLoggerListener $logger The notification logger for accessing dispatched notifications + * @param array $config The PHPFlasher configuration + * * @phpstan-param ConfigShare $config */ public function __construct( @@ -59,11 +75,25 @@ public function __construct( ) { } + /** + * Initial data collection - called during request processing. + * + * This implementation doesn't collect data here, deferring to lateCollect. + * + * @param Request $request The request object + * @param Response $response The response object + * @param \Throwable|null $exception Any exception that occurred + */ public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { // No action needed here since we're collecting data in lateCollect } + /** + * Late data collection - called after response is sent. + * + * Collects information about notifications, configuration, and versions. + */ public function lateCollect(): void { $this->data = [ @@ -81,6 +111,8 @@ public function lateCollect(): void } /** + * Gets the collector data. + * * @return DataShape|Data */ public function getData(): array|Data @@ -88,11 +120,19 @@ public function getData(): array|Data return $this->data; } + /** + * Gets the collector name for the profiler panel. + * + * @return string The collector name + */ public function getName(): string { return 'flasher'; } + /** + * Resets the collector between requests when using kernel.reset. + */ public function reset(): void { $this->logger->reset(); @@ -100,6 +140,8 @@ public function reset(): void } /** + * Gets the displayed notification envelopes. + * * @return NotificationShape[]|Data */ public function getDisplayedEnvelopes(): array|Data @@ -108,6 +150,8 @@ public function getDisplayedEnvelopes(): array|Data } /** + * Gets the dispatched notification envelopes. + * * @return NotificationShape[]|Data */ public function getDispatchedEnvelopes(): array|Data @@ -116,6 +160,8 @@ public function getDispatchedEnvelopes(): array|Data } /** + * Gets the PHPFlasher configuration. + * * @phpstan-return ConfigShare|Data */ public function getConfig(): array|Data @@ -124,6 +170,8 @@ public function getConfig(): array|Data } /** + * Gets version information. + * * @return array{php_flasher: string, php: string, symfony: string}|Data */ public function getVersions(): array|Data @@ -131,6 +179,11 @@ public function getVersions(): array|Data return $this->data['versions'] ?? []; } + /** + * Gets the template path for the profiler panel. + * + * @return string The template path + */ public static function getTemplate(): string { return '@FlasherSymfony/profiler/flasher.html.twig'; diff --git a/Storage/FallbackSession.php b/Storage/FallbackSession.php index 9651f12..768fe3b 100644 --- a/Storage/FallbackSession.php +++ b/Storage/FallbackSession.php @@ -5,11 +5,24 @@ namespace Flasher\Symfony\Storage; /** - * FallbackSession acts as a stand-in when the regular session is not available. + * FallbackSession - In-memory session storage fallback. + * + * This class provides a simple in-memory storage mechanism that can be used + * when the regular Symfony session is not available. It stores values in a + * static array, making them available for the duration of the request. + * + * Design patterns: + * - Null Object Pattern: Provides a non-failing alternative to a missing session + * - In-Memory Repository: Stores data in memory as a simple alternative to persistence + * - Singleton-like Pattern: Uses a static storage array shared across instances */ final class FallbackSession implements FallbackSessionInterface { - /** @var array */ + /** + * In-memory storage for session data. + * + * @var array + */ private static array $storage = []; public function get(string $name, mixed $default = null): mixed diff --git a/Storage/FallbackSessionInterface.php b/Storage/FallbackSessionInterface.php index f5908f0..bf7b790 100644 --- a/Storage/FallbackSessionInterface.php +++ b/Storage/FallbackSessionInterface.php @@ -5,7 +5,16 @@ namespace Flasher\Symfony\Storage; /** - * FallbackSession acts as a stand-in when the regular session is not available. + * FallbackSessionInterface - Contract for alternative session storage. + * + * This interface defines methods for a fallback storage mechanism when the + * regular Symfony session is not available. This is particularly useful in + * stateless contexts or when the session hasn't been started. + * + * Design patterns: + * - Interface Segregation: Defines a minimal interface for session-like storage + * - Strategy Pattern: Allows different storage implementations to be used + * - Fallback Strategy: Provides an alternative when primary storage is unavailable */ interface FallbackSessionInterface { diff --git a/Storage/SessionBag.php b/Storage/SessionBag.php index c3bf4c1..8002b8e 100644 --- a/Storage/SessionBag.php +++ b/Storage/SessionBag.php @@ -10,17 +10,49 @@ use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Session\SessionInterface; +/** + * SessionBag - Symfony session storage for PHPFlasher notifications. + * + * This class implements PHPFlasher's storage interface using Symfony's session + * system, providing persistence for notifications across requests. It includes + * fallback behavior for stateless contexts. + * + * Design patterns: + * - Adapter Pattern: Adapts Symfony's session to PHPFlasher's storage interface + * - Strategy Pattern: Uses different storage strategies based on context + * - Fallback Strategy: Falls back to in-memory storage when session unavailable + * - Repository Pattern: Provides CRUD operations for notification storage + */ final readonly class SessionBag implements BagInterface { + /** + * Session key for storing notification envelopes. + */ public const ENVELOPES_NAMESPACE = 'flasher::envelopes'; + /** + * Fallback storage for contexts where session is unavailable. + */ private FallbackSessionInterface $fallbackSession; + /** + * Creates a new SessionBag instance. + * + * @param RequestStack $requestStack Symfony's request stack for accessing session + * @param FallbackSessionInterface|null $fallbackSession Optional custom fallback storage + */ public function __construct(private RequestStack $requestStack, ?FallbackSessionInterface $fallbackSession = null) { $this->fallbackSession = $fallbackSession ?: new FallbackSession(); } + /** + * {@inheritdoc} + * + * Gets all notification envelopes from storage. + * + * @return Envelope[] The stored notification envelopes + */ public function get(): array { $session = $this->getSession(); @@ -31,6 +63,13 @@ public function get(): array return $envelopes; } + /** + * {@inheritdoc} + * + * Stores notification envelopes in storage. + * + * @param array $envelopes The notification envelopes to store + */ public function set(array $envelopes): void { $session = $this->getSession(); @@ -38,6 +77,14 @@ public function set(array $envelopes): void $session->set(self::ENVELOPES_NAMESPACE, $envelopes); } + /** + * Gets the appropriate session storage implementation. + * + * Uses Symfony session if available and request is not stateless, + * otherwise falls back to the fallback session implementation. + * + * @return SessionInterface|FallbackSessionInterface The storage implementation + */ private function getSession(): SessionInterface|FallbackSessionInterface { try { diff --git a/Support/PluginBundle.php b/Support/PluginBundle.php index bbcc10d..f6a7d2c 100644 --- a/Support/PluginBundle.php +++ b/Support/PluginBundle.php @@ -10,12 +10,41 @@ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symfony\Component\HttpKernel\Bundle\AbstractBundle; +/** + * PluginBundle - Base class for PHPFlasher plugin bundles. + * + * This abstract class provides common functionality for all PHPFlasher plugin bundles. + * It extends Symfony's AbstractBundle to integrate with the kernel bundle system, + * while implementing PluginBundleInterface to provide PHPFlasher-specific functionality. + * + * Design patterns: + * - Template Method: Defines skeleton of common bundle operations + * - Factory Method: Creates plugin instances + * - Bridge: Connects Symfony bundle system with PHPFlasher plugin system + * - Extension: Extends AbstractBundle with PHPFlasher-specific functionality + */ abstract class PluginBundle extends AbstractBundle implements PluginBundleInterface { + /** + * Creates an instance of the plugin. + * + * This factory method must be implemented by child classes to instantiate + * the specific plugin that the bundle integrates. + * + * @return PluginInterface The plugin instance + */ abstract public function createPlugin(): PluginInterface; /** - * @param array $config + * Loads bundle configuration into the Symfony container. + * + * This method registers the plugin's factory service in the container and + * configures it appropriately. The core FlasherSymfonyBundle is exempt from + * this process since it has special handling. + * + * @param array $config The processed bundle configuration + * @param ContainerConfigurator $container The container configurator + * @param ContainerBuilder $builder The container builder */ public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void { @@ -38,6 +67,15 @@ public function loadExtension(array $config, ContainerConfigurator $container, C } } + /** + * Prepends default plugin configuration for Flasher. + * + * This method adds the plugin's scripts, styles, and options to the Flasher + * configuration before the container is compiled. + * + * @param ContainerConfigurator $container The container configurator + * @param ContainerBuilder $builder The container builder + */ public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void { if ($this instanceof FlasherSymfonyBundle) { @@ -57,11 +95,27 @@ public function prependExtension(ContainerConfigurator $container, ContainerBuil ]); } + /** + * Gets the path to the plugin's configuration file. + * + * Returns the absolute path to the plugin's configuration file + * based on the bundle's path. + * + * @return string Absolute path to the configuration file + */ public function getConfigurationFile(): string { return rtrim($this->getPath(), '/').'/Resources/config/config.yaml'; } + /** + * Gets the bundle's directory path. + * + * Uses reflection to determine the location of the bundle class file, + * then returns its directory. + * + * @return string The bundle directory path + */ public function getPath(): string { if (!isset($this->path)) { diff --git a/Support/PluginBundleInterface.php b/Support/PluginBundleInterface.php index b65d8f4..1b2850d 100644 --- a/Support/PluginBundleInterface.php +++ b/Support/PluginBundleInterface.php @@ -6,9 +6,33 @@ use Flasher\Prime\Plugin\PluginInterface; +/** + * PluginBundleInterface - Contract for PHPFlasher plugin bundles. + * + * This interface defines the basic requirements for a Symfony bundle that + * integrates a PHPFlasher plugin. It focuses on plugin creation and configuration. + * + * Design patterns: + * - Factory Method: Defines interface for creating plugin instances + * - Plugin Architecture: Supports extensible plugin system + * - Bridge: Connects Symfony bundle system with PHPFlasher plugin system + */ interface PluginBundleInterface { + /** + * Creates an instance of the plugin. + * + * This factory method is responsible for instantiating the plugin that + * this bundle integrates with Symfony. + * + * @return PluginInterface The plugin instance + */ public function createPlugin(): PluginInterface; + /** + * Gets the path to the plugin's configuration file. + * + * @return string Absolute path to the configuration file + */ public function getConfigurationFile(): string; } diff --git a/Template/TwigTemplateEngine.php b/Template/TwigTemplateEngine.php index 04fae24..652ef9e 100644 --- a/Template/TwigTemplateEngine.php +++ b/Template/TwigTemplateEngine.php @@ -7,12 +7,38 @@ use Flasher\Prime\Template\TemplateEngineInterface; use Twig\Environment; +/** + * TwigTemplateEngine - Adapter for Symfony's Twig template engine. + * + * This class adapts Symfony's Twig environment to PHPFlasher's template engine + * interface, enabling notification templates to be rendered using Twig. + * + * Design patterns: + * - Adapter Pattern: Adapts Twig to PHPFlasher's template interface + * - Null Object Pattern: Gracefully handles a missing Twig dependency + * - Bridge Pattern: Bridges PHPFlasher's templating needs with Symfony's templating system + */ final readonly class TwigTemplateEngine implements TemplateEngineInterface { + /** + * Creates a new TwigTemplateEngine instance. + * + * @param Environment|null $twig The Twig environment or null if Twig is not available + */ public function __construct(private ?Environment $twig = null) { } + /** + * Renders a template using Twig. + * + * @param string $name The template name or path + * @param array $context The template variables + * + * @return string The rendered template + * + * @throws \LogicException If Twig is not available + */ public function render(string $name, array $context = []): string { if (null === $this->twig) { diff --git a/Translation/Translator.php b/Translation/Translator.php index fa076cd..63a3fa1 100644 --- a/Translation/Translator.php +++ b/Translation/Translator.php @@ -8,12 +8,43 @@ use Symfony\Component\Translation\TranslatorBagInterface; use Symfony\Contracts\Translation\TranslatorInterface as SymfonyTranslatorInterface; +/** + * Translator - Adapter for Symfony's translation service. + * + * This class adapts Symfony's translation system to PHPFlasher's TranslatorInterface, + * enabling notification messages to be translated using Symfony's translation capabilities. + * It searches for messages in multiple domains with cascading fallbacks. + * + * Design patterns: + * - Adapter Pattern: Adapts Symfony's translator interface to PHPFlasher's interface + * - Facade Pattern: Simplifies the translation process with a unified interface + * - Chain of Responsibility: Tries multiple translation domains in sequence + */ final readonly class Translator implements TranslatorInterface { + /** + * Creates a new Translator instance. + * + * @param SymfonyTranslatorInterface $translator The Symfony translator service + */ public function __construct(private SymfonyTranslatorInterface $translator) { } + /** + * Translates a message using Symfony's translation system. + * + * This method attempts to translate the message in the following order: + * 1. In the 'flasher' domain (flasher-specific translations) + * 2. In the 'messages' domain (application-wide translations) + * 3. Returns the original ID if no translation is found + * + * @param string $id The message ID or key + * @param array $parameters The translation parameters + * @param string|null $locale The locale or null to use the default + * + * @return string The translated string + */ public function translate(string $id, array $parameters = [], ?string $locale = null): string { if (!$this->translator instanceof TranslatorBagInterface) { @@ -31,6 +62,13 @@ public function translate(string $id, array $parameters = [], ?string $locale = return $id; } + /** + * Gets the current locale from Symfony's translator. + * + * Falls back to system default locale if translator doesn't provide one. + * + * @return string The current locale code + */ public function getLocale(): string { if (method_exists($this->translator, 'getLocale')) { // @phpstan-ignore-line diff --git a/Twig/FlasherTwigExtension.php b/Twig/FlasherTwigExtension.php index af6af17..fcbc020 100644 --- a/Twig/FlasherTwigExtension.php +++ b/Twig/FlasherTwigExtension.php @@ -8,12 +8,34 @@ use Twig\Extension\AbstractExtension; use Twig\TwigFunction; +/** + * FlasherTwigExtension - Twig extension for rendering notifications. + * + * This class provides Twig functions that allow notification rendering + * directly from Twig templates. It exposes PHPFlasher's rendering + * capabilities to template files. + * + * Design patterns: + * - Extension Pattern: Extends Twig's functionality + * - Adapter Pattern: Adapts PHPFlasher's API for Twig templates + * - Delegation: Delegates actual rendering to the Flasher service + */ final class FlasherTwigExtension extends AbstractExtension { + /** + * Creates a new FlasherTwigExtension instance. + * + * @param FlasherInterface $flasher The PHPFlasher service + */ public function __construct(private readonly FlasherInterface $flasher) { } + /** + * Returns the Twig functions provided by this extension. + * + * @return TwigFunction[] Array of Twig functions + */ public function getFunctions(): array { return [ @@ -27,6 +49,8 @@ public function getFunctions(): array * @param array $criteria the criteria to filter the notifications * @param "html"|"json"|string $presenter The presenter format for rendering the notifications (e.g., 'html', 'json'). * @param array $context additional context or options for rendering + * + * @return mixed The rendered output (HTML string, JSON string, etc.) */ public function render(array $criteria = [], string $presenter = 'html', array $context = []): mixed {