From 6aae014dc9ee5d71ceffcabb71fb6c22808ec996 Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Tue, 9 Apr 2024 07:21:20 +0000 Subject: [PATCH 01/26] chore: v2 full rewrite --- .github/FUNDING.yml | 3 - .github/workflows/auto_closer.yaml | 19 +- .phpstorm.meta.php | 8 + Attribute/AsFlasherFactory.php | 13 + Attribute/AsFlasherPresenter.php | 13 + Bridge/Bridge.php | 40 --- Bridge/Command/FlasherCommand.php | 28 -- .../FlasherConfiguration.php | 26 -- .../DependencyInjection/FlasherExtension.php | 27 -- Bridge/FlasherBundle.php | 37 --- Bridge/Legacy/Command/FlasherCommand.php | 25 -- .../FlasherConfiguration.php | 24 -- .../DependencyInjection/FlasherExtension.php | 26 -- Bridge/Legacy/FlasherBundle.php | 45 --- Bridge/Legacy/Twig/FlasherTwigExtension.php | 27 -- Bridge/Twig/FlasherTwigExtension.php | 27 -- Bridge/Typed/Command/FlasherCommand.php | 25 -- .../FlasherConfiguration.php | 24 -- .../DependencyInjection/FlasherExtension.php | 26 -- Bridge/Typed/FlasherBundle.php | 47 ---- Bridge/Typed/Twig/FlasherTwigExtension.php | 26 -- Command/InstallCommand.php | 196 ++++++++----- Component/FlasherComponent.php | 16 ++ Container/SymfonyContainer.php | 30 -- .../Compiler/EventListenerCompilerPass.php | 21 ++ .../Compiler/EventSubscriberCompilerPass.php | 34 --- .../Compiler/FactoryCompilerPass.php | 33 --- .../Compiler/FlasherAwareCompilerPass.php | 31 --- .../Compiler/PresenterCompilerPass.php | 20 +- DependencyInjection/Configuration.php | 178 ++++++------ DependencyInjection/FlasherExtension.php | 262 ++++++------------ EventListener/FlasherListener.php | 32 +-- EventListener/SessionListener.php | 32 +-- Factory/NotificationFactoryLocator.php | 29 ++ FlasherBundle.php | 40 +++ FlasherSymfonyBundle.php | 57 ---- Http/Request.php | 96 ++++--- Http/Response.php | 72 +++-- LICENSE | 2 +- README.md | 8 +- Resources/config/config.yaml | 177 ++---------- Resources/config/services.php | 235 ++++++++++------ Resources/translations/flasher.ar.php | 9 +- Resources/translations/flasher.de.php | 5 + Resources/translations/flasher.en.php | 9 +- Resources/translations/flasher.es.php | 5 + Resources/translations/flasher.fr.php | 9 +- Resources/translations/flasher.pt.php | 5 + Resources/translations/flasher.ru.php | 5 + Resources/translations/flasher.zh.php | 5 + Resources/views/bootstrap.html.twig | 30 ++ Resources/views/components/flasher.html.twig | 3 + Resources/views/tailwindcss.html.twig | 52 ++++ Resources/views/tailwindcss_bg.html.twig | 48 ++++ Resources/views/tailwindcss_r.html.twig | 53 ++++ Storage/FallbackSession.php | 30 +- Storage/FallbackSessionInterface.php | 29 ++ Storage/SessionBag.php | 78 ++---- Support/Bundle.php | 39 --- Support/Configuration.php | 60 ---- Support/Extension.php | 116 -------- Support/PluginBundle.php | 75 +++++ Support/PluginBundleInterface.php | 14 + Support/PluginExtension.php | 52 ++++ Template/TwigTemplateEngine.php | 23 +- Translation/Translator.php | 53 +--- Twig/FlasherTwigExtension.php | 35 +-- composer.json | 64 ++--- 68 files changed, 1215 insertions(+), 1828 deletions(-) create mode 100644 .phpstorm.meta.php create mode 100644 Attribute/AsFlasherFactory.php create mode 100644 Attribute/AsFlasherPresenter.php delete mode 100644 Bridge/Bridge.php delete mode 100644 Bridge/Command/FlasherCommand.php delete mode 100644 Bridge/DependencyInjection/FlasherConfiguration.php delete mode 100644 Bridge/DependencyInjection/FlasherExtension.php delete mode 100644 Bridge/FlasherBundle.php delete mode 100644 Bridge/Legacy/Command/FlasherCommand.php delete mode 100644 Bridge/Legacy/DependencyInjection/FlasherConfiguration.php delete mode 100644 Bridge/Legacy/DependencyInjection/FlasherExtension.php delete mode 100644 Bridge/Legacy/FlasherBundle.php delete mode 100644 Bridge/Legacy/Twig/FlasherTwigExtension.php delete mode 100644 Bridge/Twig/FlasherTwigExtension.php delete mode 100644 Bridge/Typed/Command/FlasherCommand.php delete mode 100644 Bridge/Typed/DependencyInjection/FlasherConfiguration.php delete mode 100644 Bridge/Typed/DependencyInjection/FlasherExtension.php delete mode 100644 Bridge/Typed/FlasherBundle.php delete mode 100644 Bridge/Typed/Twig/FlasherTwigExtension.php create mode 100644 Component/FlasherComponent.php delete mode 100644 Container/SymfonyContainer.php create mode 100644 DependencyInjection/Compiler/EventListenerCompilerPass.php delete mode 100644 DependencyInjection/Compiler/EventSubscriberCompilerPass.php delete mode 100644 DependencyInjection/Compiler/FactoryCompilerPass.php delete mode 100644 DependencyInjection/Compiler/FlasherAwareCompilerPass.php create mode 100644 Factory/NotificationFactoryLocator.php create mode 100644 FlasherBundle.php delete mode 100644 FlasherSymfonyBundle.php create mode 100644 Resources/translations/flasher.de.php create mode 100644 Resources/translations/flasher.es.php create mode 100644 Resources/translations/flasher.pt.php create mode 100644 Resources/translations/flasher.ru.php create mode 100644 Resources/translations/flasher.zh.php create mode 100644 Resources/views/bootstrap.html.twig create mode 100644 Resources/views/components/flasher.html.twig create mode 100644 Resources/views/tailwindcss.html.twig create mode 100644 Resources/views/tailwindcss_bg.html.twig create mode 100644 Resources/views/tailwindcss_r.html.twig create mode 100644 Storage/FallbackSessionInterface.php delete mode 100644 Support/Bundle.php delete mode 100644 Support/Configuration.php delete mode 100644 Support/Extension.php create mode 100644 Support/PluginBundle.php create mode 100644 Support/PluginBundleInterface.php create mode 100644 Support/PluginExtension.php diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index c386363..895dabf 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,5 +1,2 @@ github: yoeunes -patreon: yoeunes -ko_fi: yoeunes -open_collective: php-flasher custom: https://www.paypal.com/paypalme/yoeunes diff --git a/.github/workflows/auto_closer.yaml b/.github/workflows/auto_closer.yaml index f807ac5..ba4fb61 100644 --- a/.github/workflows/auto_closer.yaml +++ b/.github/workflows/auto_closer.yaml @@ -2,21 +2,22 @@ name: Auto Closer PR on: pull_request_target: - types: [opened] + types: [ opened ] jobs: run: + name: 🤖 PR Auto-Closure runs-on: ubuntu-latest steps: - - uses: superbrothers/close-pull-request@v3 - with: - comment: | - Hi, thank you for your contribution. + - uses: superbrothers/close-pull-request@v3 + with: + comment: | + Hi there 👋, - Unfortunately, this repository is read-only. It's a split from our main monorepo repository. + First off, thanks for your effort! 🎉 Unfortunately, this repository is read-only because it's split from our primary monorepo repository. - We'd like to kindly ask you to move the contribution there - https://github.com/php-flasher/php-flasher. + 🙏 We kindly ask if you could direct your valuable contribution to our main repository at https://github.com/php-flasher/php-flasher. - We'll check it, review it and give you feed back right way. + Once you've moved your contribution there, we'll review it and provide feedback. 🕵️‍♂️ - Thank you. + Thanks again for your understanding and cooperation. We really appreciate it! 🙌 diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php new file mode 100644 index 0000000..5484be8 --- /dev/null +++ b/.phpstorm.meta.php @@ -0,0 +1,8 @@ + - */ - -namespace Flasher\Symfony\Bridge; - -use Symfony\Component\HttpKernel\Kernel; - -final class Bridge -{ - /** - * @return bool - */ - public static function isLegacy() - { - return self::versionCompare('6', '<'); - } - - /** - * @param string $version - * @param string $operator - * - * @return bool - */ - public static function versionCompare($version, $operator = '=') - { - return version_compare(Kernel::VERSION, $version, $operator); - } - - /** - * @return bool - */ - public static function canLoadAliases() - { - return self::versionCompare('3.0', '>='); - } -} diff --git a/Bridge/Command/FlasherCommand.php b/Bridge/Command/FlasherCommand.php deleted file mode 100644 index 3320498..0000000 --- a/Bridge/Command/FlasherCommand.php +++ /dev/null @@ -1,28 +0,0 @@ - - */ - -namespace Flasher\Symfony\Bridge\Command; - -use Flasher\Symfony\Bridge\Bridge; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; - -$class = Bridge::versionCompare('6.4', '>=') - ? 'Flasher\Symfony\Bridge\Typed\Command\FlasherCommand' - : 'Flasher\Symfony\Bridge\Legacy\Command\FlasherCommand'; - -class_alias($class, 'Flasher\Symfony\Bridge\Command\FlasherCommand'); - -if (false) { /** @phpstan-ignore-line */ - abstract class FlasherCommand - { - /** - * @return int - */ - abstract protected function flasherExecute(InputInterface $input, OutputInterface $output); - } -} diff --git a/Bridge/DependencyInjection/FlasherConfiguration.php b/Bridge/DependencyInjection/FlasherConfiguration.php deleted file mode 100644 index 903cd89..0000000 --- a/Bridge/DependencyInjection/FlasherConfiguration.php +++ /dev/null @@ -1,26 +0,0 @@ - - */ - -namespace Flasher\Symfony\Bridge\DependencyInjection; - -use Flasher\Symfony\Bridge\Bridge; - -$class = Bridge::versionCompare('6.4', '>=') - ? 'Flasher\Symfony\Bridge\Typed\DependencyInjection\FlasherConfiguration' - : 'Flasher\Symfony\Bridge\Legacy\DependencyInjection\FlasherConfiguration'; - -class_alias($class, 'Flasher\Symfony\Bridge\DependencyInjection\FlasherConfiguration'); - -if (false) { /** @phpstan-ignore-line */ - abstract class FlasherConfiguration - { - /** - * @return string - */ - abstract protected function getFlasherConfigTreeBuilder(); - } -} diff --git a/Bridge/DependencyInjection/FlasherExtension.php b/Bridge/DependencyInjection/FlasherExtension.php deleted file mode 100644 index 2eec4ad..0000000 --- a/Bridge/DependencyInjection/FlasherExtension.php +++ /dev/null @@ -1,27 +0,0 @@ - - */ - -namespace Flasher\Symfony\Bridge\DependencyInjection; - -use Flasher\Symfony\Bridge\Bridge; -use Symfony\Component\HttpKernel\DependencyInjection\Extension; - -$class = Bridge::isLegacy() - ? 'Flasher\Symfony\Bridge\Legacy\DependencyInjection\FlasherExtension' - : 'Flasher\Symfony\Bridge\Typed\DependencyInjection\FlasherExtension'; - -class_alias($class, 'Flasher\Symfony\Bridge\DependencyInjection\FlasherExtension'); - -if (false) { /** @phpstan-ignore-line */ - abstract class FlasherExtension extends Extension - { - /** - * @return string - */ - abstract protected function getFlasherAlias(); - } -} diff --git a/Bridge/FlasherBundle.php b/Bridge/FlasherBundle.php deleted file mode 100644 index 89c2013..0000000 --- a/Bridge/FlasherBundle.php +++ /dev/null @@ -1,37 +0,0 @@ - - */ - -namespace Flasher\Symfony\Bridge; - -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; - -$class = Bridge::isLegacy() - ? 'Flasher\Symfony\Bridge\Legacy\FlasherBundle' - : 'Flasher\Symfony\Bridge\Typed\FlasherBundle'; - -class_alias($class, 'Flasher\Symfony\Bridge\FlasherBundle'); - -if (false) { /** @phpstan-ignore-line */ - abstract class FlasherBundle - { - /** - * @return void - */ - protected function flasherBuild(ContainerBuilder $container) - { - } - - /** - * @return ?ExtensionInterface - */ - protected function getFlasherContainerExtension() - { - return null; - } - } -} diff --git a/Bridge/Legacy/Command/FlasherCommand.php b/Bridge/Legacy/Command/FlasherCommand.php deleted file mode 100644 index 19ab601..0000000 --- a/Bridge/Legacy/Command/FlasherCommand.php +++ /dev/null @@ -1,25 +0,0 @@ - - */ - -namespace Flasher\Symfony\Bridge\Legacy\Command; - -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; - -abstract class FlasherCommand extends Command -{ - protected function execute(InputInterface $input, OutputInterface $output) - { - return $this->flasherExecute($input, $output); - } - - /** - * @return int - */ - abstract protected function flasherExecute(InputInterface $input, OutputInterface $output); -} diff --git a/Bridge/Legacy/DependencyInjection/FlasherConfiguration.php b/Bridge/Legacy/DependencyInjection/FlasherConfiguration.php deleted file mode 100644 index ec43c69..0000000 --- a/Bridge/Legacy/DependencyInjection/FlasherConfiguration.php +++ /dev/null @@ -1,24 +0,0 @@ - - */ - -namespace Flasher\Symfony\Bridge\Legacy\DependencyInjection; - -use Symfony\Component\Config\Definition\Builder\TreeBuilder; -use Symfony\Component\Config\Definition\ConfigurationInterface; - -abstract class FlasherConfiguration implements ConfigurationInterface -{ - public function getConfigTreeBuilder() - { - return $this->getFlasherConfigTreeBuilder(); - } - - /** - * @return TreeBuilder - */ - abstract public function getFlasherConfigTreeBuilder(); -} diff --git a/Bridge/Legacy/DependencyInjection/FlasherExtension.php b/Bridge/Legacy/DependencyInjection/FlasherExtension.php deleted file mode 100644 index 77ccd90..0000000 --- a/Bridge/Legacy/DependencyInjection/FlasherExtension.php +++ /dev/null @@ -1,26 +0,0 @@ - - */ - -namespace Flasher\Symfony\Bridge\Legacy\DependencyInjection; - -use Symfony\Component\HttpKernel\DependencyInjection\Extension; - -abstract class FlasherExtension extends Extension -{ - /** - * {@inheritdoc} - */ - public function getAlias() - { - return $this->getFlasherAlias(); - } - - /** - * @return string - */ - abstract protected function getFlasherAlias(); -} diff --git a/Bridge/Legacy/FlasherBundle.php b/Bridge/Legacy/FlasherBundle.php deleted file mode 100644 index c71c846..0000000 --- a/Bridge/Legacy/FlasherBundle.php +++ /dev/null @@ -1,45 +0,0 @@ - - */ - -namespace Flasher\Symfony\Bridge\Legacy; - -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; -use Symfony\Component\HttpKernel\Bundle\Bundle; - -abstract class FlasherBundle extends Bundle -{ - /** - * {@inheritdoc} - */ - public function build(ContainerBuilder $container) - { - $this->flasherBuild($container); - } - - /** - * {@inheritdoc} - */ - public function getContainerExtension() - { - return $this->getFlasherContainerExtension(); - } - - /** - * @return void - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - protected function flasherBuild(ContainerBuilder $container) - { - } - - /** - * @return ?ExtensionInterface - */ - abstract protected function getFlasherContainerExtension(); -} diff --git a/Bridge/Legacy/Twig/FlasherTwigExtension.php b/Bridge/Legacy/Twig/FlasherTwigExtension.php deleted file mode 100644 index e1bd20e..0000000 --- a/Bridge/Legacy/Twig/FlasherTwigExtension.php +++ /dev/null @@ -1,27 +0,0 @@ - - */ - -namespace Flasher\Symfony\Bridge\Legacy\Twig; - -use Twig\Extension\AbstractExtension; -use Twig\TwigFunction; - -abstract class FlasherTwigExtension extends AbstractExtension -{ - /** - * {@inheritdoc} - */ - public function getFunctions() - { - return $this->getFlasherFunctions(); - } - - /** - * @return TwigFunction[] - */ - abstract protected function getFlasherFunctions(); -} diff --git a/Bridge/Twig/FlasherTwigExtension.php b/Bridge/Twig/FlasherTwigExtension.php deleted file mode 100644 index 7d3e6c0..0000000 --- a/Bridge/Twig/FlasherTwigExtension.php +++ /dev/null @@ -1,27 +0,0 @@ - - */ - -namespace Flasher\Symfony\Bridge\Twig; - -use Flasher\Symfony\Bridge\Bridge; -use Twig\TwigFunction; - -$class = Bridge::isLegacy() - ? 'Flasher\Symfony\Bridge\Legacy\Twig\FlasherTwigExtension' - : 'Flasher\Symfony\Bridge\Typed\Twig\FlasherTwigExtension'; - -class_alias($class, 'Flasher\Symfony\Bridge\Twig\FlasherTwigExtension'); - -if (false) { /** @phpstan-ignore-line */ - abstract class FlasherTwigExtension - { - /** - * @return TwigFunction[] - */ - abstract protected function getFlasherFunctions(); - } -} diff --git a/Bridge/Typed/Command/FlasherCommand.php b/Bridge/Typed/Command/FlasherCommand.php deleted file mode 100644 index 50e99dd..0000000 --- a/Bridge/Typed/Command/FlasherCommand.php +++ /dev/null @@ -1,25 +0,0 @@ - - */ - -namespace Flasher\Symfony\Bridge\Typed\Command; - -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; - -abstract class FlasherCommand extends Command -{ - protected function execute(InputInterface $input, OutputInterface $output): int - { - return $this->flasherExecute($input, $output); - } - - /** - * @return int - */ - abstract protected function flasherExecute(InputInterface $input, OutputInterface $output); -} diff --git a/Bridge/Typed/DependencyInjection/FlasherConfiguration.php b/Bridge/Typed/DependencyInjection/FlasherConfiguration.php deleted file mode 100644 index bca3f45..0000000 --- a/Bridge/Typed/DependencyInjection/FlasherConfiguration.php +++ /dev/null @@ -1,24 +0,0 @@ - - */ - -namespace Flasher\Symfony\Bridge\Typed\DependencyInjection; - -use Symfony\Component\Config\Definition\Builder\TreeBuilder; -use Symfony\Component\Config\Definition\ConfigurationInterface; - -abstract class FlasherConfiguration implements ConfigurationInterface -{ - public function getConfigTreeBuilder(): TreeBuilder - { - return $this->getFlasherConfigTreeBuilder(); - } - - /** - * @return TreeBuilder - */ - abstract public function getFlasherConfigTreeBuilder(); -} diff --git a/Bridge/Typed/DependencyInjection/FlasherExtension.php b/Bridge/Typed/DependencyInjection/FlasherExtension.php deleted file mode 100644 index d0fc1bb..0000000 --- a/Bridge/Typed/DependencyInjection/FlasherExtension.php +++ /dev/null @@ -1,26 +0,0 @@ - - */ - -namespace Flasher\Symfony\Bridge\Typed\DependencyInjection; - -use Symfony\Component\HttpKernel\DependencyInjection\Extension; - -abstract class FlasherExtension extends Extension -{ - /** - * {@inheritdoc} - */ - public function getAlias(): string - { - return $this->getFlasherAlias(); - } - - /** - * @return string - */ - abstract protected function getFlasherAlias(); -} diff --git a/Bridge/Typed/FlasherBundle.php b/Bridge/Typed/FlasherBundle.php deleted file mode 100644 index f259bf9..0000000 --- a/Bridge/Typed/FlasherBundle.php +++ /dev/null @@ -1,47 +0,0 @@ - - */ - -namespace Flasher\Symfony\Bridge\Typed; - -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; -use Symfony\Component\HttpKernel\Bundle\Bundle; - -abstract class FlasherBundle extends Bundle -{ - /** - * {@inheritdoc} - * - * @return void - */ - public function build(ContainerBuilder $container) - { - $this->flasherBuild($container); - } - - /** - * {@inheritdoc} - */ - public function getContainerExtension(): ?ExtensionInterface - { - return $this->getFlasherContainerExtension(); - } - - /** - * @return void - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - protected function flasherBuild(ContainerBuilder $container) - { - } - - /** - * @return ?ExtensionInterface - */ - abstract protected function getFlasherContainerExtension(); -} diff --git a/Bridge/Typed/Twig/FlasherTwigExtension.php b/Bridge/Typed/Twig/FlasherTwigExtension.php deleted file mode 100644 index a71547d..0000000 --- a/Bridge/Typed/Twig/FlasherTwigExtension.php +++ /dev/null @@ -1,26 +0,0 @@ - - */ - -namespace Flasher\Symfony\Bridge\Typed\Twig; - -use Twig\Extension\AbstractExtension; - -abstract class FlasherTwigExtension extends AbstractExtension -{ - /** - * {@inheritdoc} - */ - public function getFunctions(): array - { - return $this->getFlasherFunctions(); - } - - /** - * @return array - */ - abstract protected function getFlasherFunctions(); -} diff --git a/Command/InstallCommand.php b/Command/InstallCommand.php index 65ff4e1..e3e9c00 100644 --- a/Command/InstallCommand.php +++ b/Command/InstallCommand.php @@ -1,40 +1,39 @@ - */ +declare(strict_types=1); namespace Flasher\Symfony\Command; +use Flasher\Prime\Asset\AssetManagerInterface; use Flasher\Prime\Plugin\PluginInterface; -use Flasher\Symfony\Bridge\Bridge; -use Flasher\Symfony\Bridge\Command\FlasherCommand; -use Flasher\Symfony\Support\Bundle; +use Flasher\Symfony\Support\PluginBundleInterface; +use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\DependencyInjection\Container; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; use Symfony\Component\HttpKernel\KernelInterface; -class InstallCommand extends FlasherCommand +final class InstallCommand extends Command { - /** - * @return void - */ - protected function configure() + public function __construct(private readonly AssetManagerInterface $assetManager) + { + parent::__construct(); + } + + protected function configure(): void { $this ->setName('flasher:install') ->setDescription('Installs all PHPFlasher resources to the public and config directories.') - ->setHelp('The command copies PHPFlasher assets to public/vendor/flasher/ directory and config files to the config/packages/ directory without overwriting any existing config files.'); + ->setHelp('The command copies PHPFlasher assets to public/vendor/flasher/ directory and config files to the config/packages/ directory without overwriting any existing config files.') + ->addOption('config', 'c', InputOption::VALUE_NONE, 'Publish all config files to the config/packages/ directory.') + ->addOption('symlink', 's', InputOption::VALUE_NONE, 'Symlink PHPFlasher assets instead of copying them.'); } - /** - * @return int - */ - protected function flasherExecute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $output->writeln(''); $output->writeln(' @@ -51,14 +50,36 @@ protected function flasherExecute(InputInterface $input, OutputInterface $output $output->writeln(' INFO Copying PHPFlasher resources...'); $output->writeln(''); + $application = $this->getApplication(); + if (!$application instanceof Application) { + return self::SUCCESS; + } + + $useSymlinks = (bool) $input->getOption('symlink'); + if ($useSymlinks) { + $output->writeln('Using symlinks to publish assets.'); + } else { + $output->writeln('Copying assets to the public directory.'); + } + + $publishConfig = (bool) $input->getOption('config'); + if ($publishConfig) { + $output->writeln('Publishing configuration files.'); + } + $publicDir = $this->getPublicDir().'/vendor/flasher/'; $configDir = $this->getConfigDir(); - $exitCode = 0; - /** @var KernelInterface $kernel */ - $kernel = $this->getApplication()->getKernel(); + $filesystem = new Filesystem(); + $filesystem->remove($publicDir); + $filesystem->mkdir($publicDir); + + $files = []; + $exitCode = self::SUCCESS; + + $kernel = $application->getKernel(); foreach ($kernel->getBundles() as $bundle) { - if (!$bundle instanceof Bundle) { + if (!$bundle instanceof PluginBundleInterface) { continue; } @@ -66,13 +87,16 @@ protected function flasherExecute(InputInterface $input, OutputInterface $output $configFile = $bundle->getConfigurationFile(); try { - $this->publishAssets($plugin, $publicDir); - $this->publishConfig($plugin, $configDir, $configFile); + $files[] = $this->publishAssets($plugin, $publicDir, $useSymlinks); + + if ($publishConfig) { + $this->publishConfig($plugin, $configDir, $configFile); + } $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) { - $exitCode = 1; + $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())); } @@ -80,46 +104,60 @@ protected function flasherExecute(InputInterface $input, OutputInterface $output $output->writeln(''); - if (0 === $exitCode) { - $output->writeln(' SUCCESS PHPFlasher resources have been successfully installed.'); + if (self::SUCCESS === $exitCode) { + $message = 'PHPFlasher resources have been successfully installed.'; + if ($publishConfig) { + $message .= ' Configuration files have been published.'; + } + if ($useSymlinks) { + $message .= ' Assets were symlinked.'; + } + $output->writeln(" SUCCESS $message"); } else { $output->writeln(' ERROR An error occurred during the installation of PHPFlasher resources.'); } + $this->assetManager->createManifest(array_merge([], ...$files)); + $output->writeln(''); return $exitCode; } /** - * @param string|null $publicDir - * - * @return void + * @return string[] */ - private function publishAssets(PluginInterface $plugin, $publicDir) + private function publishAssets(PluginInterface $plugin, string $publicDir, bool $useSymlinks): array { - if (null === $publicDir) { - return; - } - $originDir = $plugin->getAssetsDir(); if (!is_dir($originDir)) { - return; + return []; } $filesystem = new Filesystem(); - $filesystem->mkdir($originDir, 0777); - $filesystem->mirror($originDir, $publicDir, Finder::create()->ignoreDotFiles(false)->in($originDir)); + $finder = new Finder(); + $finder->files()->in($originDir); + + $files = []; + + foreach ($finder as $file) { + $relativePath = trim(str_replace($originDir, '', $file->getRealPath()), \DIRECTORY_SEPARATOR); + $targetPath = $publicDir.$relativePath; + + if ($useSymlinks) { + $filesystem->symlink($file->getRealPath(), $targetPath); + } else { + $filesystem->copy($file->getRealPath(), $targetPath, true); + } + + $files[] = $targetPath; + } + + return $files; } - /** - * @param string|null $configDir - * @param string $configFile - * - * @return void - */ - private function publishConfig(PluginInterface $plugin, $configDir, $configFile) + private function publishConfig(PluginInterface $plugin, ?string $configDir, string $configFile): void { if (null === $configDir || !file_exists($configFile)) { return; @@ -134,15 +172,14 @@ private function publishConfig(PluginInterface $plugin, $configDir, $configFile) $filesystem->copy($configFile, $target); } - /** - * @return string|null - */ - private function getPublicDir() + private function getPublicDir(): ?string { $projectDir = $this->getProjectDir(); + if (null === $projectDir) { + return null; + } - $publicDir = Bridge::versionCompare('4', '>=') ? '/public' : '/web'; - $publicDir = rtrim($projectDir, '/').$publicDir; + $publicDir = rtrim($projectDir, '/').'/public'; if (is_dir($publicDir)) { return $publicDir; @@ -151,15 +188,15 @@ private function getPublicDir() return $this->getComposerDir('public-dir'); } - /** - * @return string|null - */ - private function getConfigDir() + private function getConfigDir(): ?string { $projectDir = $this->getProjectDir(); - $configDir = Bridge::versionCompare('4', '>=') ? '/config/packages/' : '/config'; - $configDir = rtrim($projectDir, '/').$configDir; + if (null === $projectDir) { + return null; + } + + $configDir = rtrim($projectDir, '/').'/config/packages/'; if (is_dir($configDir)) { return $configDir; @@ -168,34 +205,49 @@ private function getConfigDir() return $this->getComposerDir('config-dir'); } - /** - * @return string - */ - private function getProjectDir() + private function getProjectDir(): ?string { - /** @var Container $container */ - $container = $this->getApplication()->getKernel()->getContainer(); + $kernel = $this->getKernel(); - return $container->hasParameter('kernel.project_dir') - ? $container->getParameter('kernel.project_dir') - : $container->getParameter('kernel.root_dir').'/../'; + if (null === $kernel) { + return null; + } + + $container = $kernel->getContainer(); + + $projectDir = $container->getParameter('kernel.project_dir'); + + return \is_string($projectDir) ? $projectDir : null; } - /** - * @return string|null - */ - private function getComposerDir($dir) + private function getComposerDir(string $dir): ?string { $projectDir = $this->getProjectDir(); + if (null === $projectDir) { + return null; + } + $composerFilePath = $projectDir.'/composer.json'; if (!file_exists($composerFilePath)) { return null; } - $composerConfig = json_decode(file_get_contents($composerFilePath), true); + /** @var array{extra: array{string, string}} $composerConfig */ + $composerConfig = json_decode(file_get_contents($composerFilePath) ?: '', true); + + return $composerConfig['extra'][$dir] ?? null; + } + + private function getKernel(): ?KernelInterface + { + $application = $this->getApplication(); + + if (!$application instanceof Application) { + return null; + } - return isset($composerConfig['extra'][$dir]) ? $composerConfig['extra'][$dir] : null; + return $application->getKernel(); } } diff --git a/Component/FlasherComponent.php b/Component/FlasherComponent.php new file mode 100644 index 0000000..b97d35d --- /dev/null +++ b/Component/FlasherComponent.php @@ -0,0 +1,16 @@ + */ + public array $criteria = []; + + public string $presenter = 'html'; + + /** @var array */ + public array $context = []; +} diff --git a/Container/SymfonyContainer.php b/Container/SymfonyContainer.php deleted file mode 100644 index b46048d..0000000 --- a/Container/SymfonyContainer.php +++ /dev/null @@ -1,30 +0,0 @@ - - */ - -namespace Flasher\Symfony\Container; - -use Flasher\Prime\Container\ContainerInterface; -use Symfony\Component\DependencyInjection\ContainerInterface as BaseSymfonyContainer; - -final class SymfonyContainer implements ContainerInterface -{ - /** @var BaseSymfonyContainer */ - private $container; - - public function __construct(BaseSymfonyContainer $container) - { - $this->container = $container; - } - - /** - * {@inheritDoc} - */ - public function get($id) - { - return $this->container->get($id); - } -} diff --git a/DependencyInjection/Compiler/EventListenerCompilerPass.php b/DependencyInjection/Compiler/EventListenerCompilerPass.php new file mode 100644 index 0000000..3f90aa2 --- /dev/null +++ b/DependencyInjection/Compiler/EventListenerCompilerPass.php @@ -0,0 +1,21 @@ +findDefinition('flasher.event_dispatcher'); + + foreach (array_keys($container->findTaggedServiceIds('flasher.event_listener')) as $id) { + $definition->addMethodCall('addListener', [new Reference($id)]); + } + } +} diff --git a/DependencyInjection/Compiler/EventSubscriberCompilerPass.php b/DependencyInjection/Compiler/EventSubscriberCompilerPass.php deleted file mode 100644 index 1efdfeb..0000000 --- a/DependencyInjection/Compiler/EventSubscriberCompilerPass.php +++ /dev/null @@ -1,34 +0,0 @@ - - */ - -namespace Flasher\Symfony\DependencyInjection\Compiler; - -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; - -/** - * @SuppressWarnings(PHPMD.UnusedLocalVariable) - */ -final class EventSubscriberCompilerPass implements CompilerPassInterface -{ - /** - * @return void - */ - public function process(ContainerBuilder $container) - { - if (!$container->has('flasher.event_dispatcher')) { - return; - } - - $definition = $container->findDefinition('flasher.event_dispatcher'); - - foreach ($container->findTaggedServiceIds('flasher.event_subscriber') as $id => $tags) { - $definition->addMethodCall('addSubscriber', array(new Reference($id))); - } - } -} diff --git a/DependencyInjection/Compiler/FactoryCompilerPass.php b/DependencyInjection/Compiler/FactoryCompilerPass.php deleted file mode 100644 index dd11d1c..0000000 --- a/DependencyInjection/Compiler/FactoryCompilerPass.php +++ /dev/null @@ -1,33 +0,0 @@ - - */ - -namespace Flasher\Symfony\DependencyInjection\Compiler; - -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; - -final class FactoryCompilerPass implements CompilerPassInterface -{ - /** - * @return void - */ - public function process(ContainerBuilder $container) - { - if (!$container->has('flasher')) { - return; - } - - $definition = $container->findDefinition('flasher'); - - foreach ($container->findTaggedServiceIds('flasher.factory') as $id => $tags) { - foreach ($tags as $attributes) { - $definition->addMethodCall('addFactory', array($attributes['alias'], new Reference($id))); - } - } - } -} diff --git a/DependencyInjection/Compiler/FlasherAwareCompilerPass.php b/DependencyInjection/Compiler/FlasherAwareCompilerPass.php deleted file mode 100644 index 278d7b8..0000000 --- a/DependencyInjection/Compiler/FlasherAwareCompilerPass.php +++ /dev/null @@ -1,31 +0,0 @@ - - */ - -namespace Flasher\Symfony\DependencyInjection\Compiler; - -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\ContainerBuilder; - -final class FlasherAwareCompilerPass implements CompilerPassInterface -{ - /** - * @return void - */ - public function process(ContainerBuilder $container) - { - if (!$container->has('flasher')) { - return; - } - - $flasher = $container->findDefinition('flasher'); - - foreach ($container->findTaggedServiceIds('flasher.flasher_aware') as $id => $tags) { - $service = $container->findDefinition($id); - $service->addMethodCall('setFlasher', array($flasher)); - } - } -} diff --git a/DependencyInjection/Compiler/PresenterCompilerPass.php b/DependencyInjection/Compiler/PresenterCompilerPass.php index 915253b..c76c20f 100644 --- a/DependencyInjection/Compiler/PresenterCompilerPass.php +++ b/DependencyInjection/Compiler/PresenterCompilerPass.php @@ -1,32 +1,26 @@ - */ +declare(strict_types=1); namespace Flasher\Symfony\DependencyInjection\Compiler; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; final class PresenterCompilerPass implements CompilerPassInterface { - /** - * @return void - */ - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { - if (!$container->has('flasher.response_manager')) { - return; - } - $definition = $container->findDefinition('flasher.response_manager'); foreach ($container->findTaggedServiceIds('flasher.presenter') as $id => $tags) { foreach ($tags as $attributes) { - $definition->addMethodCall('addPresenter', array($attributes['alias'], new Reference($id))); + $definition->addMethodCall('addPresenter', [ + $attributes['alias'], + new ServiceClosureArgument(new Reference($id)), + ]); } } } diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 1f55200..a5b060d 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -1,139 +1,133 @@ - */ +declare(strict_types=1); namespace Flasher\Symfony\DependencyInjection; use Flasher\Prime\Plugin\FlasherPlugin; -use Flasher\Symfony\Bridge\DependencyInjection\FlasherConfiguration; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\ConfigurationInterface; -final class Configuration extends FlasherConfiguration +final readonly class Configuration implements ConfigurationInterface { - /** - * @return TreeBuilder - */ - public function getFlasherConfigTreeBuilder() + public function __construct(private FlasherPlugin $plugin) { - $plugin = new FlasherPlugin(); + } + + public function getConfigTreeBuilder(): TreeBuilder + { + $treeBuilder = new TreeBuilder($this->plugin->getName()); + $rootNode = $treeBuilder->getRootNode(); + + $this->normalizeConfig($rootNode); - $treeBuilder = new TreeBuilder($plugin->getName()); + $this->addGeneralSection($rootNode); + $this->addFlashBagSection($rootNode); + $this->addPresetsSection($rootNode); + $this->addPluginsSection($rootNode); + + return $treeBuilder; + } - $rootNode = method_exists($treeBuilder, 'getRootNode') - ? $treeBuilder->getRootNode() - : $treeBuilder->root($plugin->getName()); // @phpstan-ignore-line + private function normalizeConfig(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->beforeNormalization() + ->always(fn ($v): array => $this->plugin->normalizeConfig($v)) + ->end(); + } + private function addGeneralSection(ArrayNodeDefinition $rootNode): void + { $rootNode - ->beforeNormalization() - ->always(function ($v) use ($plugin) { - return $plugin->normalizeConfig($v); - }) - ->end() ->children() ->scalarNode('default') + ->isRequired() ->cannotBeEmpty() - ->defaultValue($plugin->getDefault()) + ->defaultValue($this->plugin->getDefault()) + ->end() + ->scalarNode('main_script') + ->defaultValue($this->plugin->getRootScript()) + ->end() + ->booleanNode('translate') + ->defaultTrue() ->end() - ->arrayNode('root_script') - ->prototype('scalar')->end() - ->defaultValue($plugin->getRootScript()) + ->booleanNode('inject_assets') + ->defaultTrue() + ->end() + ->arrayNode('filter') + ->variablePrototype()->end() ->end() ->arrayNode('scripts') - ->prototype('variable')->end() + ->performNoDeepMerging() + ->scalarPrototype()->end() ->end() ->arrayNode('styles') - ->prototype('variable')->end() - ->defaultValue($plugin->getStyles()) + ->performNoDeepMerging() + ->scalarPrototype()->end() ->end() ->arrayNode('options') - ->prototype('scalar')->end() - ->end() - ->booleanNode('use_cdn')->defaultTrue()->end() - ->booleanNode('auto_translate')->defaultTrue()->end() - ->booleanNode('auto_render')->defaultTrue()->end() - ->arrayNode('filter_criteria') - ->prototype('scalar')->end() + ->variablePrototype()->end() ->end() - ->end() - ; - - $this->addThemesSection($rootNode); - $this->addFlashBagSection($rootNode, $plugin); - $this->addPresetsSection($rootNode); - - return $treeBuilder; + ->end(); } - /** - * @return void - */ - private function addThemesSection(ArrayNodeDefinition $rootNode) + private function addFlashBagSection(ArrayNodeDefinition $rootNode): void { - $rootNode // @phpstan-ignore-line + $rootNode ->children() - ->arrayNode('themes') - ->ignoreExtraKeys() - ->prototype('variable')->end() - ->children() - ->scalarNode('view') - ->isRequired() - ->cannotBeEmpty() - ->end() - ->arrayNode('styles')->end() - ->arrayNode('scripts')->end() - ->arrayNode('options')->end() - ->end() + ->variableNode('flash_bag') + ->defaultTrue() ->end() - ->end() - ; + ->end(); } - /** - * @return void - */ - private function addFlashBagSection(ArrayNodeDefinition $rootNode, FlasherPlugin $plugin) + private function addPresetsSection(ArrayNodeDefinition $rootNode): void { - $rootNode // @phpstan-ignore-line + $rootNode + ->fixXmlConfig('preset') ->children() - ->arrayNode('flash_bag') - ->canBeUnset() - ->addDefaultsIfNotSet() - ->children() - ->booleanNode('enabled')->defaultTrue()->end() - ->arrayNode('mapping') - ->prototype('variable')->end() - ->defaultValue($plugin->getFlashBagMapping()) + ->arrayNode('presets') + ->useAttributeAsKey('name') + ->arrayPrototype() + ->children() + ->scalarNode('type')->end() + ->scalarNode('title')->end() + ->scalarNode('message')->end() + ->arrayNode('options') + ->variablePrototype()->end() + ->end() ->end() ->end() ->end() - ->end() - ; + ->end(); } - /** - * @return void - */ - private function addPresetsSection(ArrayNodeDefinition $rootNode) + private function addPluginsSection(ArrayNodeDefinition $rootNode): void { - $rootNode // @phpstan-ignore-line + $rootNode + ->fixXmlConfig('plugin') ->children() - ->arrayNode('presets') - ->prototype('array') - ->children() - ->scalarNode('type')->end() - ->scalarNode('title')->end() - ->scalarNode('message')->end() - ->arrayNode('options') - ->useAttributeAsKey('name') - ->prototype('variable')->end() + ->arrayNode('plugins') + ->useAttributeAsKey('name') + ->arrayPrototype() + ->children() + ->scalarNode('view')->end() + ->arrayNode('styles') + ->performNoDeepMerging() + ->scalarPrototype()->end() + ->end() + ->arrayNode('scripts') + ->performNoDeepMerging() + ->scalarPrototype()->end() + ->end() + ->arrayNode('options') + ->variablePrototype()->end() + ->end() ->end() ->end() ->end() - ->end() - ; + ->end(); } } diff --git a/DependencyInjection/FlasherExtension.php b/DependencyInjection/FlasherExtension.php index c212f0d..7e5b6a0 100644 --- a/DependencyInjection/FlasherExtension.php +++ b/DependencyInjection/FlasherExtension.php @@ -1,235 +1,141 @@ - */ +declare(strict_types=1); namespace Flasher\Symfony\DependencyInjection; -use Flasher\Prime\Config\ConfigInterface; -use Flasher\Symfony\Bridge\Bridge; -use Symfony\Component\Config\FileLocator; +use Flasher\Prime\EventDispatcher\EventListener\EventListenerInterface; +use Flasher\Prime\Plugin\FlasherPlugin; +use Flasher\Prime\Storage\Bag\ArrayBag; +use Flasher\Symfony\Attribute\AsFlasherFactory; +use Flasher\Symfony\Attribute\AsFlasherPresenter; +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Loader; -use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\HttpKernel\DependencyInjection\Extension; - -/** - * @phpstan-import-type ConfigType from ConfigInterface - */ -final class FlasherExtension extends Extension implements CompilerPassInterface -{ - /** - * @phpstan-param ConfigType[] $configs - * - * @return void - */ - public function load(array $configs, ContainerBuilder $container) - { - $loader = new Loader\PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); - $loader->load('services.php'); - - /** @var ConfigType $config */ - $config = $this->processConfiguration(new Configuration(), $configs); - - $this->registerFlasherConfiguration($config, $container); - $this->registerListeners($config, $container); - $this->registerStorageManager($config, $container); - $this->registerHttpExtensions($config, $container); - $this->registerFlasherAutoConfiguration($container); - } - - /** - * @return void - */ - public function process(ContainerBuilder $container) - { - $this->registerFlasherTranslator($container); - $this->registerFlasherTemplateEngine($container); - $this->configureSessionServices($container); - } - - /** - * @phpstan-param ConfigType $config - * - * @return void - */ - private function registerFlasherConfiguration(array $config, ContainerBuilder $container) - { - $flasherConfig = $container->getDefinition('flasher.config'); - $flasherConfig->replaceArgument(0, $config); - - $flasher = $container->getDefinition('flasher'); - $flasher->replaceArgument(0, $config['default']); - - $presetListener = $container->getDefinition('flasher.preset_listener'); - $presetListener->replaceArgument(0, $config['presets']); - } +use Symfony\Component\DependencyInjection\Extension\AbstractExtension; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; - /** - * @phpstan-param ConfigType $config - * - * @return void - */ - private function registerListeners(array $config, ContainerBuilder $container) +final class FlasherExtension extends AbstractExtension implements CompilerPassInterface +{ + public function __construct(private readonly FlasherPlugin $plugin) { - $this->registerSessionListener($config, $container); - $this->registerFlasherListener($config, $container); } - /** - * @return void - */ - private function registerResponseExtension(ContainerBuilder $container) + public function getAlias(): string { - $container->register('flasher.response_extension', 'Flasher\Prime\Http\ResponseExtension') - ->setPublic(false) - ->addArgument(new Reference('flasher')); + return $this->plugin->getName(); } /** - * @param array $mapping - * - * @return void + * @param array $config */ - private function registerRequestExtension(ContainerBuilder $container, array $mapping) + public function getConfiguration(array $config, ContainerBuilder $container): ConfigurationInterface { - $container->register('flasher.request_extension', 'Flasher\Prime\Http\RequestExtension') - ->setPublic(false) - ->addArgument(new Reference('flasher')) - ->addArgument($mapping); + return new Configuration($this->plugin); } /** - * @phpstan-param ConfigType $config - * - * @return void + * @param array{ + * default: string, + * main_script: string, + * inject_assets: bool, + * presets: array, + * flash_bag: array, + * filter: array, + * plugins: array>, + * } $config */ - private function registerSessionListener(array $config, ContainerBuilder $container) + public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void { - if (!$config['flash_bag']['enabled']) { - return; - } + $this->registerFlasherParameters($config, $container, $builder); + $this->registerServicesForAutoconfiguration($builder); - $container->register('flasher.session_listener', 'Flasher\Symfony\EventListener\SessionListener') - ->setPublic(true) - ->addArgument(new Reference('flasher.request_extension')) - ->addTag('kernel.event_listener', array('event' => 'kernel.response')); + $container->import(__DIR__.'/../Resources/config/services.php'); } - /** - * @phpstan-param ConfigType $config - * - * @return void - */ - private function registerFlasherListener(array $config, ContainerBuilder $container) + public function process(ContainerBuilder $container): void { - if (!$config['auto_render']) { - return; - } - - $container->register('flasher.flasher_listener', 'Flasher\Symfony\EventListener\FlasherListener') - ->setPublic(true) - ->addArgument(new Reference('flasher.response_extension')) - ->addTag('kernel.event_listener', array('event' => 'kernel.response', 'priority' => -256)); + $this->registerFlasherTranslator($container); + $this->configureSessionServices($container); + $this->configureFlasherListener($container); } /** - * @phpstan-param ConfigType $config - * - * @return void + * @param array{ + * default: string, + * main_script: string, + * inject_assets: bool, + * presets: array, + * flash_bag: array, + * filter: array, + * plugins: array>, + * } $config */ - private function registerStorageManager(array $config, ContainerBuilder $container) + private function registerFlasherParameters(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void { - $criteria = $config['filter_criteria']; - $storageManager = $container->getDefinition('flasher.storage_manager'); - $storageManager->replaceArgument(2, $criteria); + /** @var string $projectDir */ + $projectDir = $builder->getParameter('kernel.project_dir'); + $publicDir = $projectDir.\DIRECTORY_SEPARATOR.'public'; + $assetsDir = $publicDir.\DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR.'flasher'; + $manifestPath = $assetsDir.\DIRECTORY_SEPARATOR.'manifest.json'; + + $container->parameters() + ->set('flasher', $config) + ->set('flasher.public_dir', $publicDir) + ->set('flasher.assets_dir', $assetsDir) + ->set('flasher.json_manifest_path', $manifestPath) + ->set('flasher.default', $config['default']) + ->set('flasher.main_script', $config['main_script']) + ->set('flasher.inject_assets', $config['inject_assets']) + ->set('flasher.flash_bag', $config['flash_bag']) + ->set('flasher.filter', $config['filter']) + ->set('flasher.presets', $config['presets']) + ->set('flasher.plugins', $config['plugins']) + ; } - /** - * @return void - */ - private function registerHttpExtensions(array $config, ContainerBuilder $container) + private function registerServicesForAutoconfiguration(ContainerBuilder $builder): void { - $mapping = $config['flash_bag']['mapping']; - $this->registerRequestExtension($container, $mapping); - - $this->registerResponseExtension($container); - } + $builder->registerForAutoconfiguration(EventListenerInterface::class) + ->addTag('flasher.event_listener'); - /** - * @return void - */ - private function registerFlasherAutoConfiguration(ContainerBuilder $container) - { - if (!method_exists($container, 'registerForAutoconfiguration')) { - return; - } + $builder->registerAttributeForAutoconfiguration(AsFlasherFactory::class, static function (ChildDefinition $definition, AsFlasherFactory $attribute): void { + $definition->addTag('flasher.factory', get_object_vars($attribute)); + }); - $container - ->registerForAutoconfiguration('Flasher\Prime\Aware\FlasherAwareInterface') - ->addTag('flasher.flasher_aware'); + $builder->registerAttributeForAutoconfiguration(AsFlasherPresenter::class, static function (ChildDefinition $definition, AsFlasherPresenter $attribute): void { + $definition->addTag('flasher.presenter', get_object_vars($attribute)); + }); } - /** - * @return void - */ - private function registerFlasherTranslator(ContainerBuilder $container) + private function registerFlasherTranslator(ContainerBuilder $container): void { - $config = $container->getDefinition('flasher.config')->getArgument(0); - - $translationListener = $container->getDefinition('flasher.translation_listener'); - $translationListener->replaceArgument(1, $config['auto_translate']); // @phpstan-ignore-line - if ($container->has('translator')) { return; } $container->removeDefinition('flasher.translator'); - $translationListener->replaceArgument(0, null); } - /** - * @return void - */ - private function registerFlasherTemplateEngine(ContainerBuilder $container) + private function configureSessionServices(ContainerBuilder $container): void { - if ($container->has('twig')) { - return; + if (!$container->has('session.factory') || false === $container->getParameter('flasher.flash_bag')) { + $container->removeDefinition('flasher.session_listener'); } - $container->removeDefinition('flasher.template_engine'); - - $listener = $container->getDefinition('flasher.resource_manager'); - $listener->replaceArgument(1, null); - } - - /** - * @return void - */ - private function configureSessionServices(ContainerBuilder $container) - { - if ($this->isSessionEnabled($container)) { - return; + if (!$container->has('session.factory')) { + $container->removeDefinition('flasher.storage_bag'); + $container->register('flasher.storage_bag', ArrayBag::class); } - - $container->removeDefinition('flasher.storage_bag'); - $container->removeDefinition('flasher.session_listener'); - - $container->register('flasher.storage_bag', 'Flasher\Prime\Storage\Bag\ArrayBag'); } - /** - * @return bool - */ - private function isSessionEnabled(ContainerBuilder $container) + private function configureFlasherListener(ContainerBuilder $container): void { - if (Bridge::versionCompare('5.3', '>=')) { - return $container->has('session.factory'); + if ($container->getParameter('flasher.inject_assets')) { + return; } - return $container->has('session'); + $container->removeDefinition('flasher.flasher_listener'); } } diff --git a/EventListener/FlasherListener.php b/EventListener/FlasherListener.php index ec1dedf..1aab514 100644 --- a/EventListener/FlasherListener.php +++ b/EventListener/FlasherListener.php @@ -1,39 +1,33 @@ - */ +declare(strict_types=1); namespace Flasher\Symfony\EventListener; -use Flasher\Prime\Http\ResponseExtension; +use Flasher\Prime\Http\ResponseExtensionInterface; use Flasher\Symfony\Http\Request; use Flasher\Symfony\Http\Response; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\ResponseEvent; -final class FlasherListener +final readonly class FlasherListener implements EventSubscriberInterface { - /** - * @var ResponseExtension - */ - private $responseExtension; - - public function __construct(ResponseExtension $responseExtension) + public function __construct(private ResponseExtensionInterface $responseExtension) { - $this->responseExtension = $responseExtension; } - /** - * @param ResponseEvent $event - * - * @return void - */ - public function onKernelResponse($event) + public function onKernelResponse(ResponseEvent $event): void { $request = new Request($event->getRequest()); $response = new Response($event->getResponse()); $this->responseExtension->render($request, $response); } + + public static function getSubscribedEvents(): array + { + return [ + ResponseEvent::class => ['onKernelResponse', -256], + ]; + } } diff --git a/EventListener/SessionListener.php b/EventListener/SessionListener.php index da0b425..7ad2e64 100644 --- a/EventListener/SessionListener.php +++ b/EventListener/SessionListener.php @@ -1,39 +1,33 @@ - */ +declare(strict_types=1); namespace Flasher\Symfony\EventListener; -use Flasher\Prime\Http\RequestExtension; +use Flasher\Prime\Http\RequestExtensionInterface; use Flasher\Symfony\Http\Request; use Flasher\Symfony\Http\Response; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\ResponseEvent; -final class SessionListener +final readonly class SessionListener implements EventSubscriberInterface { - /** - * @var RequestExtension - */ - private $requestExtension; - - public function __construct(RequestExtension $requestExtension) + public function __construct(private RequestExtensionInterface $requestExtension) { - $this->requestExtension = $requestExtension; } - /** - * @param ResponseEvent $event - * - * @return void - */ - public function onKernelResponse($event) + public function onKernelResponse(ResponseEvent $event): void { $request = new Request($event->getRequest()); $response = new Response($event->getResponse()); $this->requestExtension->flash($request, $response); } + + public static function getSubscribedEvents(): array + { + return [ + ResponseEvent::class => ['onKernelResponse', 0], + ]; + } } diff --git a/Factory/NotificationFactoryLocator.php b/Factory/NotificationFactoryLocator.php new file mode 100644 index 0000000..41d22b8 --- /dev/null +++ b/Factory/NotificationFactoryLocator.php @@ -0,0 +1,29 @@ + $serviceLocator + */ + public function __construct(private ServiceLocator $serviceLocator) + { + } + + public function has(string $id): bool + { + return $this->serviceLocator->has($id); + } + + public function get(string $id): NotificationFactoryInterface + { + return $this->serviceLocator->get($id); + } +} diff --git a/FlasherBundle.php b/FlasherBundle.php new file mode 100644 index 0000000..51d5d4f --- /dev/null +++ b/FlasherBundle.php @@ -0,0 +1,40 @@ +container instanceof ContainerInterface) { + FlasherContainer::from($this->container); + } + } + + public function build(ContainerBuilder $container): void + { + $container->addCompilerPass(new EventListenerCompilerPass()); + $container->addCompilerPass(new PresenterCompilerPass()); + } + + public function getContainerExtension(): ExtensionInterface + { + return new FlasherExtension($this->createPlugin()); + } + + public function createPlugin(): FlasherPlugin + { + return new FlasherPlugin(); + } +} diff --git a/FlasherSymfonyBundle.php b/FlasherSymfonyBundle.php deleted file mode 100644 index 45c76d4..0000000 --- a/FlasherSymfonyBundle.php +++ /dev/null @@ -1,57 +0,0 @@ - - */ - -namespace Flasher\Symfony; - -use Flasher\Prime\Container\FlasherContainer; -use Flasher\Prime\Plugin\FlasherPlugin; -use Flasher\Symfony\Container\SymfonyContainer; -use Flasher\Symfony\DependencyInjection\Compiler\EventSubscriberCompilerPass; -use Flasher\Symfony\DependencyInjection\Compiler\FactoryCompilerPass; -use Flasher\Symfony\DependencyInjection\Compiler\FlasherAwareCompilerPass; -use Flasher\Symfony\DependencyInjection\Compiler\PresenterCompilerPass; -use Flasher\Symfony\DependencyInjection\FlasherExtension; -use Flasher\Symfony\Support\Bundle; -use Symfony\Component\DependencyInjection\ContainerBuilder; - -class FlasherSymfonyBundle extends Bundle // Symfony\Component\HttpKernel\Bundle\Bundle -{ - /** - * {@inheritdoc} - */ - public function boot() - { - FlasherContainer::init(new SymfonyContainer($this->container)); - } - - /** - * {@inheritDoc} - */ - public function createPlugin() - { - return new FlasherPlugin(); - } - - /** - * {@inheritdoc} - */ - protected function flasherBuild(ContainerBuilder $container) - { - $container->addCompilerPass(new FactoryCompilerPass()); - $container->addCompilerPass(new EventSubscriberCompilerPass()); - $container->addCompilerPass(new PresenterCompilerPass()); - $container->addCompilerPass(new FlasherAwareCompilerPass()); - } - - /** - * {@inheritdoc} - */ - protected function getFlasherContainerExtension() - { - return new FlasherExtension(); - } -} diff --git a/Http/Request.php b/Http/Request.php index 5b7af35..f2aa879 100644 --- a/Http/Request.php +++ b/Http/Request.php @@ -1,90 +1,88 @@ - */ +declare(strict_types=1); namespace Flasher\Symfony\Http; use Flasher\Prime\Http\RequestInterface; +use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; use Symfony\Component\HttpFoundation\Request as SymfonyRequest; -use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\FlashBagAwareSessionInterface; +use Symfony\Component\HttpFoundation\Session\SessionInterface; -final class Request implements RequestInterface +final readonly class Request implements RequestInterface { - /** - * @var SymfonyRequest - */ - private $request; - - public function __construct(SymfonyRequest $request) + public function __construct(private SymfonyRequest $request) { - $this->request = $request; } - /** - * {@inheritDoc} - */ - public function isXmlHttpRequest() + public function isXmlHttpRequest(): bool { return $this->request->isXmlHttpRequest(); } - /** - * {@inheritDoc} - */ - public function isHtmlRequestFormat() + public function isHtmlRequestFormat(): bool { return 'html' === $this->request->getRequestFormat(); } - /** - * {@inheritDoc} - */ - public function hasSession() + public function hasSession(): bool { return $this->request->hasSession(); } - /** - * {@inheritDoc} - */ - public function hasType($type) + public function isSessionStarted(): bool + { + $session = $this->getSession(); + + return $session?->isStarted() ?: false; + } + + public function hasType(string $type): bool { - if (!$this->hasSession()) { + if (!$this->hasSession() || !$this->isSessionStarted()) { return false; } - $session = $this->request->getSession(); - if (!$session->isStarted()) { + $session = $this->getSession(); + if (!$session instanceof FlashBagAwareSessionInterface) { return false; } - /** @var Session $session */ - $session = $this->request->getSession(); - $flashBag = $session->getFlashBag(); - - return $flashBag->has($type); + return $session->getFlashBag()->has($type); } - /** - * {@inheritDoc} - */ - public function getType($type) + public function getType(string $type): string|array { - /** @var Session $session */ - $session = $this->request->getSession(); - $flashBag = $session->getFlashBag(); + $session = $this->getSession(); + if (!$session instanceof FlashBagAwareSessionInterface) { + return []; + } - return $flashBag->get($type); + return $session->getFlashBag()->get($type); } - /** - * {@inheritDoc} - */ - public function forgetType($type) + public function forgetType(string $type): void { $this->getType($type); } + + private function getSession(): ?SessionInterface + { + try { + return $this->request->getSession(); + } catch (SessionNotFoundException) { + return null; + } + } + + public function hasHeader(string $key): bool + { + return $this->request->headers->has($key); + } + + public function getHeader(string $key): ?string + { + return $this->request->headers->get($key); + } } diff --git a/Http/Response.php b/Http/Response.php index 4ba33b2..edbf399 100644 --- a/Http/Response.php +++ b/Http/Response.php @@ -1,9 +1,6 @@ - */ +declare(strict_types=1); namespace Flasher\Symfony\Http; @@ -11,38 +8,23 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response as SymfonyResponse; -final class Response implements ResponseInterface +final readonly class Response implements ResponseInterface { - /** - * @var SymfonyResponse - */ - private $response; - - public function __construct(SymfonyResponse $response) + public function __construct(private SymfonyResponse $response) { - $this->response = $response; } - /** - * {@inheritDoc} - */ - public function isRedirection() + public function isRedirection(): bool { return $this->response->isRedirection(); } - /** - * {@inheritDoc} - */ - public function isJson() + public function isJson(): bool { return $this->response instanceof JsonResponse; } - /** - * {@inheritDoc} - */ - public function isHtml() + public function isHtml(): bool { $contentType = $this->response->headers->get('Content-Type'); @@ -53,10 +35,7 @@ public function isHtml() return false !== stripos($contentType, 'html'); } - /** - * {@inheritDoc} - */ - public function isAttachment() + public function isAttachment(): bool { $contentDisposition = $this->response->headers->get('Content-Disposition', ''); @@ -67,21 +46,38 @@ public function isAttachment() return false !== stripos($contentDisposition, 'attachment;'); } - /** - * {@inheritDoc} - */ - public function getContent() + public function isSuccessful(): bool { - $content = $this->response->getContent(); + return $this->response->isSuccessful(); + } - return \is_string($content) ? $content : ''; + public function getContent(): string + { + return $this->response->getContent() ?: ''; } - /** - * {@inheritDoc} - */ - public function setContent($content) + public function setContent(string $content): void { $this->response->setContent($content); } + + public function hasHeader(string $key): bool + { + return $this->response->headers->has($key); + } + + public function getHeader(string $key): ?string + { + return $this->response->headers->get($key); + } + + public function setHeader(string $key, array|string|null $values): void + { + $this->response->headers->set($key, $values); + } + + public function removeHeader(string $key): void + { + $this->response->headers->remove($key); + } } diff --git a/LICENSE b/LICENSE index 8e94bc1..cf3a76d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 PHPFlasher +Copyright (c) 2024 PHPFlasher Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 70a05eb..cd352ed 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Shining stars of our community: - + @@ -64,11 +64,11 @@ You can reach out with questions, bug reports, or feature requests on any of the - [Github Issues](https://github.com/php-flasher/php-flasher/issues) - [Github](https://github.com/yoeunes) - [Twitter](https://twitter.com/yoeunes) -- [Linkedin](https://www.linkedin.com/in/younes-khoubza/) -- [Email me directly](mailto:younes.khoubza@gmail.com) +- [Linkedin](https://www.linkedin.com/in/younes--ennaji//) +- [Email me directly](mailto:younes.ennaji.pro@gmail.com) ## License PHPFlasher is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). -

Made with ❤️ by Younes KHOUBZA

+

Made with ❤️ by Younes ENNAJI

diff --git a/Resources/config/config.yaml b/Resources/config/config.yaml index b0d1f93..c2494f6 100644 --- a/Resources/config/config.yaml +++ b/Resources/config/config.yaml @@ -1,173 +1,28 @@ flasher: - # -------------------------------------------------------------------------- - # Default PHPFlasher library - # -------------------------------------------------------------------------- - # This option controls the default library that will be used by PHPFlasher - # to display notifications in your Symfony application. PHPFlasher supports - # several libraries, including "flasher", "toastr", "noty", "notyf", - # "sweetalert" and "pnotify". - # - # The "flasher" library is used by default. If you want to use a different - # library, you will need to install it using composer. For example, to use - # the "toastr" library, run the following command: - # composer require php-flasher/flasher-toastr-symfony - # - # Here is a list of the supported libraries and the corresponding composer - # commands to install them: - # - # "toastr" : composer require php-flasher/flasher-toastr-symfony - # "noty" : composer require php-flasher/flasher-noty-symfony - # "notyf" : composer require php-flasher/flasher-notyf-symfony - # "sweetalert" : composer require php-flasher/flasher-sweetalert-symfony - # "pnotify" : composer require php-flasher/flasher-pnotify-symfony - # + # Default notification library (e.g., 'flasher', 'toastr', 'noty', etc.) default: flasher - # -------------------------------------------------------------------------- - # Main PHPFlasher javascript file - # -------------------------------------------------------------------------- - # This option specifies the location of the main javascript file that is - # required by PHPFlasher to display notifications in your Symfony application. - # - # By default, PHPFlasher uses a CDN to serve the latest version of the library. - # However, you can also choose to download the library locally or install it - # using npm. - # - # To use the local version of the library, run the following command: - # php bin/console flasher:install - # - # This will copy the necessary assets to your application's public folder. - # You can then specify the local path to the javascript file in the 'local' - # field of this option. - # - root_script: - cdn: 'https://cdn.jsdelivr.net/npm/@flasher/flasher@1.3.2/dist/flasher.min.js' - local: '/vendor/flasher/flasher.min.js' + # Path to the main JavaScript file of PHPFlasher + main_script: '/vendor/flasher/flasher.min.js' - # -------------------------------------------------------------------------- - # PHPFlasher Stylesheet - # -------------------------------------------------------------------------- - # This option specifies the location of the stylesheet file that is - # required by PHPFlasher to style the notifications in your Symfony application. - # - # By default, PHPFlasher uses a CDN to serve the latest version of the stylesheet. - # However, you can also choose to download the stylesheet locally or include it - # from your assets. - # - # To use the local version of the stylesheet, make sure you have the necessary - # assets in your application's public folder. Then specify the local path to - # the stylesheet file in the 'local' field of this option. - # + # Path to the stylesheets for PHPFlasher notifications styles: - cdn: 'https://cdn.jsdelivr.net/npm/@flasher/flasher@1.3.2/dist/flasher.min.css' - local: '/vendor/flasher/flasher.min.css' + - '/vendor/flasher/flasher.min.css' - # -------------------------------------------------------------------------- - # Whether to use CDN for PHPFlasher assets or not - # -------------------------------------------------------------------------- - # This option controls whether PHPFlasher should use CDN links or local assets - # for its javascript and CSS files. By default, PHPFlasher uses CDN links - # to serve the latest version of the library. However, you can also choose - # to use local assets by setting this option to 'false'. - # - # If you decide to use local assets, don't forget to publish the necessary - # files to your application's public folder by running the following command: - # php bin/console flasher:install - # - # This will copy the necessary assets to your application's public folder. - # - use_cdn: true + # Enable translation of PHPFlasher messages using Symfony's service + translate: true - # -------------------------------------------------------------------------- - # Translate PHPFlasher messages - # -------------------------------------------------------------------------- - # This option controls whether PHPFlasher should pass its messages to the Symfony's - # translation service for localization. - # - # By default, this option is set to 'true', which means that PHPFlasher will - # attempt to translate its messages using the translation service. - # - # If you don't want PHPFlasher to use the Symfony's translation service, you can - # set this option to 'false'. In this case, PHPFlasher will use the messages - # as-is, without attempting to translate them. - # - auto_translate: true - - # -------------------------------------------------------------------------- - # Inject PHPFlasher in Response - # -------------------------------------------------------------------------- - # This option controls whether PHPFlasher should automatically inject its - # javascript and CSS files into the HTML response of your Symfony application. - # - # By default, this option is set to 'true', which means that PHPFlasher will - # listen to the response of your application and automatically insert its - # scripts and stylesheets into the HTML before the closing `` tag. - # - # If you don't want PHPFlasher to automatically inject its scripts and stylesheets - # into the response, you can set this option to 'false'. In this case, you will - # need to manually include the necessary files in your application's layout. - # - auto_render: true + # Automatically inject PHPFlasher assets in HTML response + inject_assets: true + # Map Symfony session keys to PHPFlasher notification types flash_bag: - # ----------------------------------------------------------------------- - # Enable flash bag - # ----------------------------------------------------------------------- - # This option controls whether PHPFlasher should automatically convert - # Symfony's flash messages to PHPFlasher notifications. This feature is - # useful when you want to migrate from a legacy system or another - # library that uses similar conventions for flash messages. - # - # When this option is set to 'true', PHPFlasher will check for flash - # messages in the session and convert them to notifications using the - # mapping specified in the 'mapping' option. When this option is set - # to 'false', PHPFlasher will ignore flash messages in the session. - # - enabled: true - - - # ----------------------------------------------------------------------- - # Flash bag type mapping - # ----------------------------------------------------------------------- - # This option allows you to map or convert session keys to PHPFlasher - # notification types. On the left side are the PHPFlasher types. - # On the right side are the Symfony session keys that you want to - # convert to PHPFlasher types. - # - # For example, if you want to convert Symfony's 'danger' flash - # messages to PHPFlasher's 'error' notifications, you can add - # the following entry to the mapping: - # error: ['danger'], - # - mapping: - success: ['success'] - error: ['error', 'danger'] - warning: ['warning', 'alarm'] - info: ['info', 'notice', 'alert'] - + success: ['success'] + error: ['error', 'danger'] + warning: ['warning', 'alarm'] + info: ['info', 'notice', 'alert'] - # ----------------------------------------------------------------------- - # Global Filter Criteria - # ----------------------------------------------------------------------- - # This option allows you to filter the notifications that are displayed - # in your Symfony application. By default, all notifications are displayed, - # but you can use this option to limit the number of notifications or - # filter them by type. - # - # For example, to limit the number of notifications to 5, you can set - # the 'limit' field to 5: - # limit: 5 - # - # To filter the notifications by type, you can specify an array of - # types that you want to display. For example, to only display - # error notifications, you can set the 'types' field to ['error']: - # types: ['error'], - # - # You can also combine multiple criteria by specifying multiple fields. - # For example, to display up to 5 error notifications, you can set - # the 'limit' and 'types' fields like this: - # limit: 5, - # types: ['error'], - # + # Criteria to filter displayed notifications (limit, types) filter_criteria: + # Limit number of displayed notifications limit: 5 diff --git a/Resources/config/services.php b/Resources/config/services.php index f228c7b..16ab30e 100644 --- a/Resources/config/services.php +++ b/Resources/config/services.php @@ -1,90 +1,149 @@ - */ - -use Flasher\Symfony\Bridge\Bridge; -use Symfony\Component\DependencyInjection\Reference; - -if (!isset($container)) { - return; -} - -$container->register('flasher.config', 'Flasher\Prime\Config\Config') - ->setPublic(false) - ->addArgument(array()); - -$storage = Bridge::versionCompare('5.3', '>=') - ? new Reference('request_stack') - : new Reference('session'); - -$container->register('flasher.storage_bag', 'Flasher\Symfony\Storage\SessionBag') - ->setPublic(false) - ->addArgument($storage); - -$container->register('flasher.storage', 'Flasher\Prime\Storage\StorageBag') - ->setPublic(false) - ->addArgument(new Reference('flasher.storage_bag')); - -$container->register('flasher.event_dispatcher', 'Flasher\Prime\EventDispatcher\EventDispatcher') - ->setPublic(false); - -$container->register('flasher.storage_manager', 'Flasher\Prime\Storage\StorageManager') - ->setPublic(false) - ->addArgument(new Reference('flasher.storage')) - ->addArgument(new Reference('flasher.event_dispatcher')) - ->addArgument(array()); - -$container->register('flasher.twig.extension', 'Flasher\Symfony\Twig\FlasherTwigExtension') - ->setPublic(false) - ->addTag('twig.extension', array()); - -$container->register('flasher.template_engine', 'Flasher\Symfony\Template\TwigTemplateEngine') - ->setPublic(false) - ->addArgument(new Reference('twig')); - -$container->register('flasher.resource_manager', 'Flasher\Prime\Response\Resource\ResourceManager') - ->setPublic(false) - ->addArgument(new Reference('flasher.config')) - ->addArgument(new Reference('flasher.template_engine')); - -$container->register('flasher.response_manager', 'Flasher\Prime\Response\ResponseManager') - ->setPublic(false) - ->addArgument(new Reference('flasher.resource_manager')) - ->addArgument(new Reference('flasher.storage_manager')) - ->addArgument(new Reference('flasher.event_dispatcher')); - -$container->register('flasher', 'Flasher\Prime\Flasher') - ->setPublic(true) - ->addArgument(null) - ->addArgument(new Reference('flasher.response_manager')) - ->addArgument(new Reference('flasher.storage_manager')); - -$container->register('flasher.notification_factory', 'Flasher\Prime\Factory\NotificationFactory') - ->setPublic(false) - ->addArgument(new Reference('flasher.storage_manager')); - -$container->register('flasher.translator', 'Flasher\Symfony\Translation\Translator') - ->setPublic(false) - ->addArgument(new Reference('translator')); - -$container->register('flasher.translation_listener', 'Flasher\Prime\EventDispatcher\EventListener\TranslationListener') - ->setPublic(false) - ->addArgument(new Reference('flasher.translator')) - ->addArgument(true) - ->addTag('flasher.event_subscriber'); - -$container->register('flasher.preset_listener', 'Flasher\Prime\EventDispatcher\EventListener\PresetListener') - ->setPublic(false) - ->addArgument(array()) - ->addTag('flasher.event_subscriber'); - -$container->register('flasher.install_command', 'Flasher\Symfony\Command\InstallCommand') - ->addTag('console.command'); - -if (Bridge::canLoadAliases()) { - $container->setAlias('Flasher\Prime\Flasher', 'flasher'); - $container->setAlias('Flasher\Prime\FlasherInterface', 'flasher'); -} +declare(strict_types=1); + +use Flasher\Prime\Asset\AssetManager; +use Flasher\Prime\EventDispatcher\EventDispatcher; +use Flasher\Prime\EventDispatcher\EventListener\ApplyPresetListener; +use Flasher\Prime\EventDispatcher\EventListener\NotificationLoggerListener; +use Flasher\Prime\EventDispatcher\EventListener\TranslationListener; +use Flasher\Prime\Factory\NotificationFactory; +use Flasher\Prime\Flasher; +use Flasher\Prime\FlasherInterface; +use Flasher\Prime\Http\Csp\ContentSecurityPolicyHandler; +use Flasher\Prime\Http\Csp\NonceGenerator; +use Flasher\Prime\Http\RequestExtension; +use Flasher\Prime\Http\ResponseExtension; +use Flasher\Prime\Response\Resource\ResourceManager; +use Flasher\Prime\Response\ResponseManager; +use Flasher\Prime\Storage\Filter\FilterFactory; +use Flasher\Prime\Storage\Storage; +use Flasher\Prime\Storage\StorageManager; +use Flasher\Symfony\Command\InstallCommand; +use Flasher\Symfony\Component\FlasherComponent; +use Flasher\Symfony\EventListener\FlasherListener; +use Flasher\Symfony\EventListener\SessionListener; +use Flasher\Symfony\Factory\NotificationFactoryLocator; +use Flasher\Symfony\Storage\SessionBag; +use Flasher\Symfony\Template\TwigTemplateEngine; +use Flasher\Symfony\Translation\Translator; +use Flasher\Symfony\Twig\FlasherTwigExtension; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + +use function Symfony\Component\DependencyInjection\Loader\Configurator\inline_service; +use function Symfony\Component\DependencyInjection\Loader\Configurator\param; +use function Symfony\Component\DependencyInjection\Loader\Configurator\service; +use function Symfony\Component\DependencyInjection\Loader\Configurator\tagged_locator; + +return static function (ContainerConfigurator $container): void { + $container->services() + ->set('flasher', Flasher::class) + ->public() + ->args([ + param('flasher.default'), + inline_service(NotificationFactoryLocator::class) + ->args([tagged_locator('flasher.factory', indexAttribute: 'alias')]), + service('flasher.response_manager'), + service('flasher.storage_manager'), + ]) + ->alias(FlasherInterface::class, 'flasher') + + ->set('flasher.flasher_listener', FlasherListener::class) + ->args([ + inline_service(ResponseExtension::class) + ->args([ + service('flasher'), + service('flasher.csp_handler'), + ]), + ]) + ->tag('kernel.event_subscriber') + + ->set('flasher.twig_extension', FlasherTwigExtension::class) + ->args([service('flasher')]) + ->tag('twig.extension') + + ->set('flasher.session_listener', SessionListener::class) + ->args([ + inline_service(RequestExtension::class) + ->args([ + service('flasher'), + param('flasher.flash_bag'), + ]), + ]) + ->tag('kernel.event_subscriber') + + ->set('flasher.notification_logger_listener', NotificationLoggerListener::class) + ->tag('flasher.event_dispatcher') + ->tag('kernel.reset', ['method' => 'reset']) + + ->set('flasher.translation_listener', TranslationListener::class) + ->args([service('flasher.translator')->nullOnInvalid()]) + ->tag('kernel.event_listener') + + ->set('flasher.preset_listener', ApplyPresetListener::class) + ->args([param('flasher.presets')]) + ->tag('kernel.event_listener') + + ->set('flasher.install_command', InstallCommand::class) + ->args([service('flasher.asset_manager')]) + ->tag('console.command') + + ->set('flasher.flasher_component', FlasherComponent::class) + ->tag('twig.component', [ + 'key' => 'flasher', + 'template' => '@Flasher/components/flasher.html.twig', + 'attributesVar' => 'attributes', + ]) + + ->set('flasher.notification_factory', NotificationFactory::class) + ->args([service('flasher.storage_manager')]) + + ->set('flasher.storage', Storage::class) + ->args([service('flasher.storage_bag')]) + + ->set('flasher.storage_bag', SessionBag::class) + ->args([service('request_stack')]) + + ->set('flasher.event_dispatcher', EventDispatcher::class) + + ->set('flasher.filter_factory', FilterFactory::class) + + ->set('flasher.storage_manager', StorageManager::class) + ->args([ + service('flasher.storage'), + service('flasher.event_dispatcher'), + service('flasher.filter_factory'), + param('flasher.filter'), + ]) + + ->set('flasher.template_engine', TwigTemplateEngine::class) + ->args([service('twig')->nullOnInvalid()]) + + ->set('flasher.resource_manager', ResourceManager::class) + ->args([ + service('flasher.template_engine'), + service('flasher.asset_manager'), + param('flasher.main_script'), + param('flasher.plugins'), + ]) + + ->set('flasher.response_manager', ResponseManager::class) + ->args([ + service('flasher.resource_manager'), + service('flasher.storage_manager'), + service('flasher.event_dispatcher'), + ]) + + ->set('flasher.translator', Translator::class) + ->args([service('translator')->nullOnInvalid()]) + + ->set('flasher.csp_handler', ContentSecurityPolicyHandler::class) + ->args([inline_service(NonceGenerator::class)]) + + ->set('flasher.asset_manager', AssetManager::class) + ->args([ + param('flasher.public_dir'), + param('flasher.json_manifest_path'), + ]) + ; +}; diff --git a/Resources/translations/flasher.ar.php b/Resources/translations/flasher.ar.php index 52e2752..084fd9e 100644 --- a/Resources/translations/flasher.ar.php +++ b/Resources/translations/flasher.ar.php @@ -1,10 +1,5 @@ - */ +declare(strict_types=1); -use Flasher\Prime\Translation\Messages; - -return Messages::$ar; +return Flasher\Prime\Translation\Messages::get('ar'); diff --git a/Resources/translations/flasher.de.php b/Resources/translations/flasher.de.php new file mode 100644 index 0000000..57908c7 --- /dev/null +++ b/Resources/translations/flasher.de.php @@ -0,0 +1,5 @@ + - */ +declare(strict_types=1); -use Flasher\Prime\Translation\Messages; - -return Messages::$en; +return Flasher\Prime\Translation\Messages::get('en'); diff --git a/Resources/translations/flasher.es.php b/Resources/translations/flasher.es.php new file mode 100644 index 0000000..f7fe97e --- /dev/null +++ b/Resources/translations/flasher.es.php @@ -0,0 +1,5 @@ + - */ +declare(strict_types=1); -use Flasher\Prime\Translation\Messages; - -return Messages::$fr; +return Flasher\Prime\Translation\Messages::get('fr'); diff --git a/Resources/translations/flasher.pt.php b/Resources/translations/flasher.pt.php new file mode 100644 index 0000000..688f1ef --- /dev/null +++ b/Resources/translations/flasher.pt.php @@ -0,0 +1,5 @@ + + +
+ +
+ diff --git a/Resources/views/components/flasher.html.twig b/Resources/views/components/flasher.html.twig new file mode 100644 index 0000000..360776e --- /dev/null +++ b/Resources/views/components/flasher.html.twig @@ -0,0 +1,3 @@ +
+ {{ flasher_render() }} +
diff --git a/Resources/views/tailwindcss.html.twig b/Resources/views/tailwindcss.html.twig new file mode 100644 index 0000000..3074b17 --- /dev/null +++ b/Resources/views/tailwindcss.html.twig @@ -0,0 +1,52 @@ +{% if 'success' == envelope.type %} + {% set title = 'Success' %} + {% set text_color = 'text-green-600' %} + {% set ring_color = 'ring-green-300' %} + {% set background_color = 'bg-green-600' %} + {% set progress_background_color = 'bg-green-100' %} + {% set border_color = 'border-green-600' %} + {% set icon = '' %} +{% elseif 'error' == envelope.type %} + {% set title = 'Error' %} + {% set text_color = 'text-red-600' %} + {% set ring_color = 'ring-red-300' %} + {% set background_color = 'bg-red-600' %} + {% set progress_background_color = 'bg-red-100' %} + {% set border_color = 'border-red-600' %} + {% set icon = '' %} +{% elseif 'warning' == envelope.type %} + {% set title = 'Warning' %} + {% set text_color = 'text-yellow-600' %} + {% set ring_color = 'ring-yellow-300' %} + {% set background_color = 'bg-yellow-600' %} + {% set progress_background_color = 'bg-yellow-100' %} + {% set border_color = 'border-yellow-600' %} + {% set icon = '' %} +{% else %} + {% set title = 'Info' %} + {% set text_color = 'text-blue-600' %} + {% set ring_color = 'ring-blue-300' %} + {% set background_color = 'bg-blue-600' %} + {% set progress_background_color = 'bg-blue-100' %} + {% set border_color = 'border-blue-600' %} + {% set icon = '' %} +{% endif %} + +
+
+
+ {{ icon | raw }} +
+
+

+ {{ title | trans }} +

+

+ {{ envelope.message }} +

+
+
+
+ +
+
diff --git a/Resources/views/tailwindcss_bg.html.twig b/Resources/views/tailwindcss_bg.html.twig new file mode 100644 index 0000000..702e4d8 --- /dev/null +++ b/Resources/views/tailwindcss_bg.html.twig @@ -0,0 +1,48 @@ +{% if 'success' == envelope.type %} + {% set title = 'Success' %} + {% set text_color = 'text-green-700' %} + {% set background_color = 'bg-green-50' %} + {% set progress_background_color = 'bg-green-200' %} + {% set border_color = 'border-green-600' %} + {% set icon = '' %} +{% elseif 'error' == envelope.type %} + {% set title = 'Error' %} + {% set text_color = 'text-red-700' %} + {% set background_color = 'bg-red-50' %} + {% set progress_background_color = 'bg-red-200' %} + {% set border_color = 'border-red-600' %} + {% set icon = '' %} +{% elseif 'warning' == envelope.type %} + {% set title = 'Warning' %} + {% set text_color = 'text-yellow-700' %} + {% set background_color = 'bg-yellow-50' %} + {% set progress_background_color = 'bg-yellow-200' %} + {% set border_color = 'border-yellow-600' %} + {% set icon = '' %} +{% else %} + {% set title = 'Info' %} + {% set text_color = 'text-blue-700' %} + {% set background_color = 'bg-blue-50' %} + {% set progress_background_color = 'bg-blue-200' %} + {% set border_color = 'border-blue-600' %} + {% set icon = '' %} +{% endif %} + +
+
+
+ {{ icon | raw }} +
+
+

+ {{ title | trans }} +

+

+ {{ envelope.message }} +

+
+
+
+ +
+
diff --git a/Resources/views/tailwindcss_r.html.twig b/Resources/views/tailwindcss_r.html.twig new file mode 100644 index 0000000..4227a28 --- /dev/null +++ b/Resources/views/tailwindcss_r.html.twig @@ -0,0 +1,53 @@ +{% if 'success' == envelope.type %} + {% set title = 'Success' %} + {% set text_color = 'text-green-600' %} + {% set ring_color = 'ring-green-300' %} + {% set background_color = 'bg-green-600' %} + {% set progress_background_color = 'bg-green-100' %} + {% set border_color = 'border-green-600' %} + {% set icon = '' %} +{% elseif 'error' == envelope.type %} + {% set title = 'Error' %} + {% set text_color = 'text-red-600' %} + {% set ring_color = 'ring-red-300' %} + {% set background_color = 'bg-red-600' %} + {% set progress_background_color = 'bg-red-100' %} + {% set border_color = 'border-red-600' %} + {% set icon = '' %} +{% elseif 'warning' == envelope.type %} + {% set title = 'Warning' %} + {% set text_color = 'text-yellow-600' %} + {% set ring_color = 'ring-yellow-300' %} + {% set background_color = 'bg-yellow-600' %} + {% set progress_background_color = 'bg-yellow-100' %} + {% set border_color = 'border-yellow-600' %} + {% set icon = '' %} +{% else %} + {% set title = 'Info' %} + {% set text_color = 'text-blue-600' %} + {% set ring_color = 'ring-blue-300' %} + {% set background_color = 'bg-blue-600' %} + {% set progress_background_color = 'bg-blue-100' %} + {% set border_color = 'border-blue-600' %} + {% set icon = '' %} +{% endif %} + + +
+
+
+ {{ icon | raw }} +
+
+

+ {{ title | trans }} +

+

+ {{ envelope.message }} +

+
+
+
+ +
+
diff --git a/Storage/FallbackSession.php b/Storage/FallbackSession.php index 96e8482..9651f12 100644 --- a/Storage/FallbackSession.php +++ b/Storage/FallbackSession.php @@ -1,31 +1,23 @@ */ + private static array $storage = []; - /** - * @param string $name - * @param mixed $default - * - * @return mixed - */ - public function get($name, $default = null) + public function get(string $name, mixed $default = null): mixed { - return array_key_exists($name, self::$storage) - ? self::$storage[$name] - : $default; + return \array_key_exists($name, self::$storage) ? self::$storage[$name] : $default; } - /** - * @param string $name - * @param mixed $value - * - * @return void - */ - public function set($name, $value) + public function set(string $name, mixed $value): void { self::$storage[$name] = $value; } diff --git a/Storage/FallbackSessionInterface.php b/Storage/FallbackSessionInterface.php new file mode 100644 index 0000000..f5908f0 --- /dev/null +++ b/Storage/FallbackSessionInterface.php @@ -0,0 +1,29 @@ + - */ +declare(strict_types=1); namespace Flasher\Symfony\Storage; +use Flasher\Prime\Notification\Envelope; use Flasher\Prime\Storage\Bag\BagInterface; use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\HttpFoundation\Session as LegacySession; use Symfony\Component\HttpFoundation\Session\SessionInterface; -final class SessionBag implements BagInterface +final readonly class SessionBag implements BagInterface { - const ENVELOPES_NAMESPACE = 'flasher::envelopes'; + public const ENVELOPES_NAMESPACE = 'flasher::envelopes'; - /** - * @var RequestStack|SessionInterface - */ - private $session; + private FallbackSessionInterface $fallbackSession; - /** - * @var FallbackSession - */ - private $fallbackSession; - - /** - * @param RequestStack|SessionInterface $session - */ - public function __construct($session) + public function __construct(private RequestStack $requestStack, ?FallbackSessionInterface $fallbackSession = null) { - $this->session = $session; - $this->fallbackSession = new FallbackSession(); + $this->fallbackSession = $fallbackSession ?: new FallbackSession(); } - /** - * {@inheritdoc} - */ - public function get() + public function get(): array { - return $this->session()->get(self::ENVELOPES_NAMESPACE, array()); // @phpstan-ignore-line + $session = $this->getSession(); + + /** @var Envelope[] $envelopes */ + $envelopes = $session->get(self::ENVELOPES_NAMESPACE, []); + + return $envelopes; } - /** - * {@inheritdoc} - */ - public function set(array $envelopes) + public function set(array $envelopes): void { - $this->session()->set(self::ENVELOPES_NAMESPACE, $envelopes); + $session = $this->getSession(); + + $session->set(self::ENVELOPES_NAMESPACE, $envelopes); } - /** - * @return SessionInterface - */ - private function session() + private function getSession(): SessionInterface|FallbackSessionInterface { - if ($this->session instanceof SessionInterface || $this->session instanceof LegacySession) { // @phpstan-ignore-line - return $this->session; // @phpstan-ignore-line - } - try { - if (method_exists($this->session, 'getSession')) { - $session = $this->session->getSession(); - } else { - $session = $this->session->getCurrentRequest()->getSession(); - } - - $isStateless = $this->session->getCurrentRequest()->attributes->has('_stateless'); + $request = $this->requestStack->getCurrentRequest(); - if (null !== $session && !$isStateless) { - return $this->session = $session; + if ($request && !$request->attributes->get('_stateless', false)) { + return $this->requestStack->getSession(); } - - return $this->fallbackSession; - } catch (SessionNotFoundException $e) { - return $this->fallbackSession; + } catch (SessionNotFoundException) { } + + return $this->fallbackSession; } } diff --git a/Support/Bundle.php b/Support/Bundle.php deleted file mode 100644 index 8de3215..0000000 --- a/Support/Bundle.php +++ /dev/null @@ -1,39 +0,0 @@ - - */ - -namespace Flasher\Symfony\Support; - -use Flasher\Prime\Plugin\PluginInterface; -use Flasher\Symfony\Bridge\FlasherBundle; - -abstract class Bundle extends FlasherBundle -{ - /** - * @return PluginInterface - */ - abstract public function createPlugin(); - - public function getConfigurationFile() - { - return rtrim($this->getResourcesDir(), '/').'/config/config.yaml'; - } - - protected function getFlasherContainerExtension() - { - return new Extension($this->createPlugin()); - } - - /** - * @return string - */ - protected function getResourcesDir() - { - $r = new \ReflectionClass($this); - - return pathinfo($r->getFileName() ?: '', PATHINFO_DIRNAME).'/Resources/'; - } -} diff --git a/Support/Configuration.php b/Support/Configuration.php deleted file mode 100644 index 39702e7..0000000 --- a/Support/Configuration.php +++ /dev/null @@ -1,60 +0,0 @@ - - */ - -namespace Flasher\Symfony\Support; - -use Flasher\Prime\Plugin\PluginInterface; -use Flasher\Symfony\Bridge\DependencyInjection\FlasherConfiguration; -use Symfony\Component\Config\Definition\Builder\TreeBuilder; - -class Configuration extends FlasherConfiguration -{ - /** - * @var PluginInterface - */ - private $plugin; - - public function __construct(PluginInterface $plugin) - { - $this->plugin = $plugin; - } - - public function getFlasherConfigTreeBuilder() - { - $treeBuilder = new TreeBuilder($this->plugin->getName()); - - $rootNode = method_exists($treeBuilder, 'getRootNode') - ? $treeBuilder->getRootNode() - : $treeBuilder->root($this->plugin->getName()); // @phpstan-ignore-line - - $plugin = $this->plugin; - $rootNode - ->beforeNormalization() - ->always(function ($v) use ($plugin) { - return $plugin->normalizeConfig($v); - }) - ->end() - ->children() - ->arrayNode('scripts') - ->prototype('variable')->end() - ->defaultValue($this->plugin->getScripts()) - ->end() - ->arrayNode('styles') - ->prototype('variable')->end() - ->defaultValue($this->plugin->getStyles()) - ->end() - ->arrayNode('options') - ->prototype('variable')->end() - ->ignoreExtraKeys(false) - ->defaultValue($this->plugin->getOptions()) - ->end() - ->end() - ; - - return $treeBuilder; - } -} diff --git a/Support/Extension.php b/Support/Extension.php deleted file mode 100644 index 25a47e4..0000000 --- a/Support/Extension.php +++ /dev/null @@ -1,116 +0,0 @@ - - */ - -namespace Flasher\Symfony\Support; - -use Flasher\Prime\Plugin\PluginInterface; -use Flasher\Symfony\Bridge\Bridge; -use Flasher\Symfony\Bridge\DependencyInjection\FlasherExtension; -use Symfony\Component\Config\Definition\ConfigurationInterface; -use Symfony\Component\DependencyInjection\ChildDefinition; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\DefinitionDecorator; - -final class Extension extends FlasherExtension implements CompilerPassInterface -{ - /** - * @var PluginInterface - */ - private $plugin; - - public function __construct(PluginInterface $plugin) - { - $this->plugin = $plugin; - } - - /** - * {@inheritdoc} - * - * @param array> $configs - * - * @return void - */ - public function load(array $configs, ContainerBuilder $container) - { - /** @var ChildDefinition $definition */ - $definition = class_exists('Symfony\Component\DependencyInjection\ChildDefinition') - ? new ChildDefinition('flasher.notification_factory') - : new DefinitionDecorator('flasher.notification_factory'); // @phpstan-ignore-line - - $definition - ->setClass($this->plugin->getFactory()) - ->setPublic(true) - ->addTag('flasher.factory', array('alias' => $this->plugin->getAlias())); - - $identifier = $this->plugin->getServiceID(); - $container->setDefinition($identifier, $definition); - - if (Bridge::canLoadAliases()) { - $container->setAlias($this->plugin->getFactory(), $identifier); - } - } - - /** - * {@inheritdoc} - */ - public function getFlasherAlias() - { - return $this->plugin->getName(); - } - - /** - * Returns extension configuration. - * - * @param array> $config - * - * @return ConfigurationInterface|null - */ - public function getConfiguration(array $config, ContainerBuilder $container) - { - return new Configuration($this->plugin); - } - - /** - * {@inheritdoc} - * - * @return void - */ - public function process(ContainerBuilder $container) - { - $configs = $this->processConfiguration( - new Configuration($this->plugin), - $container->getExtensionConfig($this->plugin->getName()) - ); - - $this->processResourceConfiguration($configs, $container); - } - - /** - * @param array $configs - * - * @return void - */ - protected function processResourceConfiguration(array $configs, ContainerBuilder $container) - { - if (!$container->has('flasher.resource_manager')) { - return; - } - - $definition = $container->getDefinition('flasher.resource_manager'); - $handler = $this->plugin->getAlias(); - - $scripts = isset($configs['scripts']) ? $configs['scripts'] : array(); - $definition->addMethodCall('addScripts', array($handler, $scripts)); - - $styles = isset($configs['styles']) ? $configs['styles'] : array(); - $definition->addMethodCall('addStyles', array($handler, $styles)); - - $options = isset($configs['options']) ? $configs['options'] : array(); - $definition->addMethodCall('addOptions', array($handler, $options)); - } -} diff --git a/Support/PluginBundle.php b/Support/PluginBundle.php new file mode 100644 index 0000000..a6447bb --- /dev/null +++ b/Support/PluginBundle.php @@ -0,0 +1,75 @@ + $config + */ + public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void + { + if ($this instanceof FlasherBundle) { + return; + } + + $plugin = $this->createPlugin(); + $identifier = $plugin->getServiceId(); + + $container->services() + ->set($identifier, $plugin->getFactory()) + ->parent('flasher.notification_factory') + ->tag('flasher.factory', ['alias' => $plugin->getAlias()]) + ->public() + ; + + foreach ((array) $plugin->getServiceAliases() as $alias) { + $builder->setAlias($alias, $identifier); + } + } + + public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void + { + if ($this instanceof FlasherBundle) { + return; + } + + $plugin = $this->createPlugin(); + + $builder->prependExtensionConfig('flasher', [ + 'plugins' => [ + $plugin->getAlias() => [ + 'scripts' => (array) $plugin->getScripts(), + 'styles' => (array) $plugin->getStyles(), + 'options' => $plugin->getOptions(), + ], + ], + ]); + } + + public function getConfigurationFile(): string + { + return rtrim($this->getPath(), '/').'/Resources/config/config.yaml'; + } + + public function getPath(): string + { + if (!isset($this->path)) { + $reflected = new \ReflectionObject($this); + // assume the modern directory structure by default + $this->path = \dirname($reflected->getFileName() ?: ''); + } + + return $this->path; + } +} diff --git a/Support/PluginBundleInterface.php b/Support/PluginBundleInterface.php new file mode 100644 index 0000000..b65d8f4 --- /dev/null +++ b/Support/PluginBundleInterface.php @@ -0,0 +1,14 @@ +plugin->getName(); + } + + public function load(array $configs, ContainerBuilder $container): void + { + $definition = new ChildDefinition('flasher.notification_factory'); + $definition + ->setClass($this->plugin->getFactory()) + ->setPublic(true) + ->addTag('flasher.factory', ['alias' => $this->plugin->getAlias()]); + + $identifier = $this->plugin->getServiceId(); + $container->setDefinition($identifier, $definition); + + foreach ((array) $this->plugin->getServiceAliases() as $alias) { + $container->setAlias($alias, $identifier); + } + } + + public function prepend(ContainerBuilder $container): void + { + $container->prependExtensionConfig('flasher', [ + 'plugins' => [ + $this->plugin->getAlias() => [ + 'scripts' => (array) $this->plugin->getScripts(), + 'styles' => (array) $this->plugin->getStyles(), + 'options' => $this->plugin->getOptions(), + ], + ], + ]); + } +} diff --git a/Template/TwigTemplateEngine.php b/Template/TwigTemplateEngine.php index 7500580..04fae24 100644 --- a/Template/TwigTemplateEngine.php +++ b/Template/TwigTemplateEngine.php @@ -1,29 +1,24 @@ - */ +declare(strict_types=1); namespace Flasher\Symfony\Template; use Flasher\Prime\Template\TemplateEngineInterface; use Twig\Environment; -final class TwigTemplateEngine implements TemplateEngineInterface +final readonly class TwigTemplateEngine implements TemplateEngineInterface { - /** - * @var Environment - */ - private $engine; - - public function __construct(Environment $engine) + public function __construct(private ?Environment $twig = null) { - $this->engine = $engine; } - public function render($name, array $context = array()) + public function render(string $name, array $context = []): string { - return $this->engine->render($name, $context); + if (null === $this->twig) { + throw new \LogicException('The TwigBundle is not registered in your application. Try running "composer require symfony/twig-bundle".'); + } + + return $this->twig->render($name, $context); } } diff --git a/Translation/Translator.php b/Translation/Translator.php index 013b554..73a52ff 100644 --- a/Translation/Translator.php +++ b/Translation/Translator.php @@ -1,48 +1,28 @@ - */ +declare(strict_types=1); namespace Flasher\Symfony\Translation; -use Flasher\Prime\Stamp\TranslationStamp; use Flasher\Prime\Translation\TranslatorInterface; use Symfony\Component\Translation\TranslatorBagInterface; use Symfony\Contracts\Translation\TranslatorInterface as SymfonyTranslatorInterface; -final class Translator implements TranslatorInterface +final readonly class Translator implements TranslatorInterface { - /** - * @var SymfonyTranslatorInterface - */ - private $translator; - - /** - * @param SymfonyTranslatorInterface $translator - */ - public function __construct($translator) + public function __construct(private SymfonyTranslatorInterface $translator) { - $this->translator = $translator; } - /** - * {@inheritdoc} - */ - public function translate($id, $parameters = array(), $locale = null) + public function translate(string $id, array $parameters = [], ?string $locale = null): string { - $order = TranslationStamp::parametersOrder($parameters, $locale); - $parameters = $this->addPrefixedParams($order['parameters']); - $locale = $order['locale']; - if (!$this->translator instanceof TranslatorBagInterface) { return $this->translator->trans($id, $parameters, 'flasher', $locale); } $catalogue = $this->translator->getCatalogue($locale); - foreach (array('flasher', 'messages') as $domain) { + foreach (['flasher', 'messages'] as $domain) { if ($catalogue->has($id, $domain)) { return $this->translator->trans($id, $parameters, $domain, $locale); } @@ -51,27 +31,12 @@ public function translate($id, $parameters = array(), $locale = null) return $id; } - /** - * {@inheritDoc} - */ - public function getLocale() - { - return $this->translator->getLocale(); - } - - /** - * @param array $parameters - * - * @return array - */ - private function addPrefixedParams(array $parameters) + public function getLocale(): string { - foreach ($parameters as $key => $value) { - if (0 !== strpos($key, ':')) { - $parameters[':'.$key] = $value; - } + if (method_exists($this->translator, 'getLocale')) { + return $this->translator->getLocale(); } - return $parameters; + return class_exists(\Locale::class) ? \Locale::getDefault() : 'en'; } } diff --git a/Twig/FlasherTwigExtension.php b/Twig/FlasherTwigExtension.php index ca098a4..af6af17 100644 --- a/Twig/FlasherTwigExtension.php +++ b/Twig/FlasherTwigExtension.php @@ -1,32 +1,35 @@ - */ +declare(strict_types=1); namespace Flasher\Symfony\Twig; -use Flasher\Symfony\Bridge\Twig\FlasherTwigExtension as BaseFlasherTwigExtension; +use Flasher\Prime\FlasherInterface; +use Twig\Extension\AbstractExtension; use Twig\TwigFunction; -final class FlasherTwigExtension extends BaseFlasherTwigExtension +final class FlasherTwigExtension extends AbstractExtension { - /** - * @return TwigFunction[] - */ - public function getFlasherFunctions() + public function __construct(private readonly FlasherInterface $flasher) + { + } + + public function getFunctions(): array { - return array( - new TwigFunction('flasher_render', array($this, 'render')), - ); + return [ + new TwigFunction('flasher_render', $this->render(...), ['is_safe' => ['html']]), + ]; } /** - * @return string + * Renders the flash notifications based on the specified criteria, presenter, and context. + * + * @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 */ - public function render() + public function render(array $criteria = [], string $presenter = 'html', array $context = []): mixed { - return ''; + return $this->flasher->render($presenter, $criteria, $context); } } diff --git a/composer.json b/composer.json index 1e05002..cd49f52 100644 --- a/composer.json +++ b/composer.json @@ -1,54 +1,48 @@ { "name": "php-flasher/flasher-symfony", - "description": "PHPFlasher - A powerful & easy-to-use package for adding flash messages to Laravel or Symfony projects. Provides feedback to users, improves engagement & enhances user experience. Intuitive design for beginners & experienced developers. A reliable, flexible solution.", - "license": "MIT", "type": "symfony-bundle", + "license": "MIT", + "homepage": "https://php-flasher.io", + "description": "PHPFlasher Symfony Bundle: Supercharge your Symfony projects with this robust flash message package. Enhance user engagement with intuitive, easy-to-integrate flash messaging. Tailored for developers, PHPFlasher provides a powerful solution for managing user feedback in Symfony applications.", "keywords": [ - "php-flasher", "flash-messages", - "notification-system", - "user-feedback", - "toastr", - "sweetalert", - "pnotify", - "noty", - "notyf", - "desktop-notifications", - "php", - "laravel", - "symfony", - "javascript", - "yoeunes", - "framework-agnostic", - "phpstorm-auto-complete", - "custom-adapter", - "user-experience", - "rtl", - "dark-mode" + "php-notification-system", + "laravel-notification", + "symfony-notification", + "user-feedback-tools", + "web-application-notifications", + "php-user-interface", + "customizable-alerts-php", + "interactive-web-notifications", + "php-messaging-library", + "user-engagement-php" ], + "support": { + "issues": "https://github.com/php-flasher/php-flasher/issues", + "source": "https://github.com/php-flasher/php-flasher" + }, "authors": [ { - "name": "Younes KHOUBZA", - "email": "younes.khoubza@gmail.com", - "homepage": "https://www.linkedin.com/in/younes-khoubza", + "name": "Younes ENNAJI", + "email": "younes.ennaji.pro@gmail.com", + "homepage": "https://www.linkedin.com/in/younes--ennaji/", "role": "Developer" } ], - "homepage": "https://php-flasher.io", + "minimum-stability": "dev", + "prefer-stable": true, "require": { - "php": ">=5.3", - "php-flasher/flasher": "^1.15.14", - "symfony/config": "^2.0 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", - "symfony/console": "^2.0 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", - "symfony/dependency-injection": "^2.0 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", - "symfony/http-kernel": "^2.0 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" + "php": ">=8.2", + "php-flasher/flasher": "^2.0", + "symfony/config": "^7.0", + "symfony/console": "^7.0", + "symfony/dependency-injection": "^7.0", + "symfony/http-kernel": "^7.0" }, "suggest": { "symfony/translation": "To translate flash messages, title and presets", - "symfony/twig-bundle": "To create custom themes using twig templates" + "symfony/ux-twig-component": "To utilize and interact with flash messages components in Twig templates" }, - "minimum-stability": "stable", - "prefer-stable": true, "autoload": { "psr-4": { "Flasher\\Symfony\\": "" From 0f77ade6aea338d7574a0f379ed31f8345d7972a Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Tue, 9 Apr 2024 13:33:37 +0000 Subject: [PATCH 02/26] chore: bump flasher dependency to v2.x-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index cd49f52..6e9d45b 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "prefer-stable": true, "require": { "php": ">=8.2", - "php-flasher/flasher": "^2.0", + "php-flasher/flasher": "2.x-dev", "symfony/config": "^7.0", "symfony/console": "^7.0", "symfony/dependency-injection": "^7.0", From 1c295f508d7d04bea4da091d06a936739a4c67fd Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Tue, 9 Apr 2024 13:39:24 +0000 Subject: [PATCH 03/26] chore: bump flasher dependency to v2.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 6e9d45b..cd49f52 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "prefer-stable": true, "require": { "php": ">=8.2", - "php-flasher/flasher": "2.x-dev", + "php-flasher/flasher": "^2.0", "symfony/config": "^7.0", "symfony/console": "^7.0", "symfony/dependency-injection": "^7.0", From 206fb8906237c071847aa72b8f6a69c61003283d Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Tue, 9 Apr 2024 13:49:25 +0000 Subject: [PATCH 04/26] chore: allow symfony bundles to be detected by symfony flex --- FlasherBundle.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlasherBundle.php b/FlasherBundle.php index 51d5d4f..d98b811 100644 --- a/FlasherBundle.php +++ b/FlasherBundle.php @@ -13,7 +13,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; -final class FlasherBundle extends Support\PluginBundle +final class FlasherBundle extends Support\PluginBundle // Symfony\Component\HttpKernel\Bundle\Bundle { public function boot(): void { From 319faa1c5ceda0efd2882fdb840f828d3fd29a38 Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Tue, 9 Apr 2024 14:20:55 +0000 Subject: [PATCH 05/26] chore: follow symfony best practices for bundle names --- FlasherBundle.php => FlasherSymfonyBundle.php | 2 +- Support/PluginBundle.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename FlasherBundle.php => FlasherSymfonyBundle.php (91%) diff --git a/FlasherBundle.php b/FlasherSymfonyBundle.php similarity index 91% rename from FlasherBundle.php rename to FlasherSymfonyBundle.php index d98b811..24351a1 100644 --- a/FlasherBundle.php +++ b/FlasherSymfonyBundle.php @@ -13,7 +13,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; -final class FlasherBundle extends Support\PluginBundle // Symfony\Component\HttpKernel\Bundle\Bundle +final class FlasherSymfonyBundle extends Support\PluginBundle // Symfony\Component\HttpKernel\Bundle\Bundle { public function boot(): void { diff --git a/Support/PluginBundle.php b/Support/PluginBundle.php index a6447bb..bbcc10d 100644 --- a/Support/PluginBundle.php +++ b/Support/PluginBundle.php @@ -5,7 +5,7 @@ namespace Flasher\Symfony\Support; use Flasher\Prime\Plugin\PluginInterface; -use Flasher\Symfony\FlasherBundle; +use Flasher\Symfony\FlasherSymfonyBundle; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symfony\Component\HttpKernel\Bundle\AbstractBundle; @@ -19,7 +19,7 @@ abstract public function createPlugin(): PluginInterface; */ public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void { - if ($this instanceof FlasherBundle) { + if ($this instanceof FlasherSymfonyBundle) { return; } @@ -40,7 +40,7 @@ public function loadExtension(array $config, ContainerConfigurator $container, C public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void { - if ($this instanceof FlasherBundle) { + if ($this instanceof FlasherSymfonyBundle) { return; } From de910af929ee3c1de8b94af841c52f4948ba60ad Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Thu, 11 Apr 2024 17:25:32 +0000 Subject: [PATCH 06/26] composer: update packages description and keywords --- composer.json | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/composer.json b/composer.json index cd49f52..79f24ec 100644 --- a/composer.json +++ b/composer.json @@ -3,20 +3,8 @@ "type": "symfony-bundle", "license": "MIT", "homepage": "https://php-flasher.io", - "description": "PHPFlasher Symfony Bundle: Supercharge your Symfony projects with this robust flash message package. Enhance user engagement with intuitive, easy-to-integrate flash messaging. Tailored for developers, PHPFlasher provides a powerful solution for managing user feedback in Symfony applications.", - "keywords": [ - "flash-messages", - "php-notification-system", - "laravel-notification", - "symfony-notification", - "user-feedback-tools", - "web-application-notifications", - "php-user-interface", - "customizable-alerts-php", - "interactive-web-notifications", - "php-messaging-library", - "user-engagement-php" - ], + "description": "Integrate flash notifications into Symfony projects effortlessly with PHPFlasher. Improve user experience and application feedback loops easily.", + "keywords": ["symfony", "php", "flash-notifications", "phpflasher", "user-feedback", "open-source"], "support": { "issues": "https://github.com/php-flasher/php-flasher/issues", "source": "https://github.com/php-flasher/php-flasher" From eb81f7611453dda56e660d7de939435f35409a3f Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Sun, 5 May 2024 17:52:48 +0100 Subject: [PATCH 07/26] chore: update the doc and improve phpstan annotations --- Resources/config/services.php | 5 ++-- Support/PluginExtension.php | 52 ----------------------------------- 2 files changed, 3 insertions(+), 54 deletions(-) delete mode 100644 Support/PluginExtension.php diff --git a/Resources/config/services.php b/Resources/config/services.php index 16ab30e..a806fef 100644 --- a/Resources/config/services.php +++ b/Resources/config/services.php @@ -78,11 +78,11 @@ ->set('flasher.translation_listener', TranslationListener::class) ->args([service('flasher.translator')->nullOnInvalid()]) - ->tag('kernel.event_listener') + ->tag('flasher.event_listener') ->set('flasher.preset_listener', ApplyPresetListener::class) ->args([param('flasher.presets')]) - ->tag('kernel.event_listener') + ->tag('flasher.event_listener') ->set('flasher.install_command', InstallCommand::class) ->args([service('flasher.asset_manager')]) @@ -96,6 +96,7 @@ ]) ->set('flasher.notification_factory', NotificationFactory::class) + ->abstract() ->args([service('flasher.storage_manager')]) ->set('flasher.storage', Storage::class) diff --git a/Support/PluginExtension.php b/Support/PluginExtension.php deleted file mode 100644 index 5c07dbf..0000000 --- a/Support/PluginExtension.php +++ /dev/null @@ -1,52 +0,0 @@ -plugin->getName(); - } - - public function load(array $configs, ContainerBuilder $container): void - { - $definition = new ChildDefinition('flasher.notification_factory'); - $definition - ->setClass($this->plugin->getFactory()) - ->setPublic(true) - ->addTag('flasher.factory', ['alias' => $this->plugin->getAlias()]); - - $identifier = $this->plugin->getServiceId(); - $container->setDefinition($identifier, $definition); - - foreach ((array) $this->plugin->getServiceAliases() as $alias) { - $container->setAlias($alias, $identifier); - } - } - - public function prepend(ContainerBuilder $container): void - { - $container->prependExtensionConfig('flasher', [ - 'plugins' => [ - $this->plugin->getAlias() => [ - 'scripts' => (array) $this->plugin->getScripts(), - 'styles' => (array) $this->plugin->getStyles(), - 'options' => $this->plugin->getOptions(), - ], - ], - ]); - } -} From ef1299cc7df45d23ea95739505ddedcf0bf665ab Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Sun, 5 May 2024 19:56:13 +0100 Subject: [PATCH 08/26] chore: update composer.json keywords section --- composer.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 79f24ec..b9598e9 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,14 @@ "license": "MIT", "homepage": "https://php-flasher.io", "description": "Integrate flash notifications into Symfony projects effortlessly with PHPFlasher. Improve user experience and application feedback loops easily.", - "keywords": ["symfony", "php", "flash-notifications", "phpflasher", "user-feedback", "open-source"], + "keywords": [ + "symfony", + "php", + "flash-notifications", + "phpflasher", + "user-feedback", + "open-source" + ], "support": { "issues": "https://github.com/php-flasher/php-flasher/issues", "source": "https://github.com/php-flasher/php-flasher" From 279b44c1741545a72e293a949ea67492f3230efe Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Mon, 13 May 2024 10:21:18 +0100 Subject: [PATCH 09/26] chore: fix phpstan bleeding edge errors --- Http/Request.php | 5 ++++- Http/Response.php | 2 +- Translation/Translator.php | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Http/Request.php b/Http/Request.php index f2aa879..9346dcd 100644 --- a/Http/Request.php +++ b/Http/Request.php @@ -52,7 +52,10 @@ public function hasType(string $type): bool return $session->getFlashBag()->has($type); } - public function getType(string $type): string|array + /** + * @return string[] + */ + public function getType(string $type): array { $session = $this->getSession(); if (!$session instanceof FlashBagAwareSessionInterface) { diff --git a/Http/Response.php b/Http/Response.php index edbf399..890b9c5 100644 --- a/Http/Response.php +++ b/Http/Response.php @@ -39,7 +39,7 @@ public function isAttachment(): bool { $contentDisposition = $this->response->headers->get('Content-Disposition', ''); - if (!\is_string($contentDisposition)) { + if (!$contentDisposition) { return false; } diff --git a/Translation/Translator.php b/Translation/Translator.php index 73a52ff..fa076cd 100644 --- a/Translation/Translator.php +++ b/Translation/Translator.php @@ -33,7 +33,7 @@ public function translate(string $id, array $parameters = [], ?string $locale = public function getLocale(): string { - if (method_exists($this->translator, 'getLocale')) { + if (method_exists($this->translator, 'getLocale')) { // @phpstan-ignore-line return $this->translator->getLocale(); } From 7318adcc57b8bab219e153eeeca2f4936ef037e5 Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Thu, 23 May 2024 10:51:41 +0100 Subject: [PATCH 10/26] chore: bump flasher dependency to v2.0.1 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index b9598e9..13fdb9d 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "prefer-stable": true, "require": { "php": ">=8.2", - "php-flasher/flasher": "^2.0", + "php-flasher/flasher": "^2.0.1", "symfony/config": "^7.0", "symfony/console": "^7.0", "symfony/dependency-injection": "^7.0", From 55e579e369a3cda008e7d500b402f431e339ef0b Mon Sep 17 00:00:00 2001 From: Ahmed Gamal Date: Sat, 25 May 2024 21:24:39 +0300 Subject: [PATCH 11/26] chore: Symfony config options update --- Resources/config/config.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Resources/config/config.yaml b/Resources/config/config.yaml index c2494f6..32c395a 100644 --- a/Resources/config/config.yaml +++ b/Resources/config/config.yaml @@ -15,6 +15,12 @@ flasher: # Automatically inject PHPFlasher assets in HTML response inject_assets: true + # Global options + options: + # timeout in milliseconds + timeout: 5000 + position: 'top-right' + # Map Symfony session keys to PHPFlasher notification types flash_bag: success: ['success'] From 0befce396fdea3926cc5c127e009ff95ed2330d0 Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Mon, 27 May 2024 00:40:47 +0100 Subject: [PATCH 12/26] chore: remove pr auto_closer github action --- .github/workflows/auto_closer.yaml | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 .github/workflows/auto_closer.yaml diff --git a/.github/workflows/auto_closer.yaml b/.github/workflows/auto_closer.yaml deleted file mode 100644 index ba4fb61..0000000 --- a/.github/workflows/auto_closer.yaml +++ /dev/null @@ -1,23 +0,0 @@ -name: Auto Closer PR - -on: - pull_request_target: - types: [ opened ] - -jobs: - run: - name: 🤖 PR Auto-Closure - runs-on: ubuntu-latest - steps: - - uses: superbrothers/close-pull-request@v3 - with: - comment: | - Hi there 👋, - - First off, thanks for your effort! 🎉 Unfortunately, this repository is read-only because it's split from our primary monorepo repository. - - 🙏 We kindly ask if you could direct your valuable contribution to our main repository at https://github.com/php-flasher/php-flasher. - - Once you've moved your contribution there, we'll review it and provide feedback. 🕵️‍♂️ - - Thanks again for your understanding and cooperation. We really appreciate it! 🙌 From 6a438030381bc391103ff24ed6c7b906e44a660a Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Thu, 19 Sep 2024 06:42:10 +0100 Subject: [PATCH 13/26] chore: run php-cs-fixer --- Command/InstallCommand.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Command/InstallCommand.php b/Command/InstallCommand.php index e3e9c00..27a29e2 100644 --- a/Command/InstallCommand.php +++ b/Command/InstallCommand.php @@ -93,12 +93,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->publishConfig($plugin, $configDir, $configFile); } - $status = sprintf('%s', '\\' === \DIRECTORY_SEPARATOR ? 'OK' : "\xE2\x9C\x94" /* HEAVY CHECK MARK (U+2714) */); - $output->writeln(sprintf(' %s %s', $status, $plugin->getAlias())); + $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) { $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())); + $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())); } } From 96186ea18c0191a6ac6edaa0cfbdd895445974d9 Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Sat, 21 Sep 2024 19:14:36 +0100 Subject: [PATCH 14/26] add palestine banner support to nested repositories --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index cd352ed..47aa95a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ + +

From 29dd7df0cbde4f33d497f3e291bbeffbed3bb88c Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Sat, 5 Oct 2024 13:34:23 +0100 Subject: [PATCH 15/26] chore: add Symfony Profiler integration for PHPFlasher --- DependencyInjection/Configuration.php | 7 + DependencyInjection/FlasherExtension.php | 3 + EventListener/FlasherListener.php | 2 +- Http/Request.php | 5 + Profiler/FlasherDataCollector.php | 138 +++++++++++++ Resources/config/services.php | 13 +- .../profiler/_notifications_table.html.twig | 30 +++ Resources/views/profiler/flasher.html.twig | 184 ++++++++++++++++++ Resources/views/profiler/flasher.svg | 8 + composer.json | 2 +- 10 files changed, 388 insertions(+), 4 deletions(-) create mode 100644 Profiler/FlasherDataCollector.php create mode 100644 Resources/views/profiler/_notifications_table.html.twig create mode 100644 Resources/views/profiler/flasher.html.twig create mode 100644 Resources/views/profiler/flasher.svg diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index a5b060d..029cc52 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -56,6 +56,13 @@ private function addGeneralSection(ArrayNodeDefinition $rootNode): void ->booleanNode('inject_assets') ->defaultTrue() ->end() + ->arrayNode('excluded_paths') + ->defaultValue([ + '/^\/_profiler/', + '/^\/_fragment/', + ]) + ->scalarPrototype()->end() + ->end() ->arrayNode('filter') ->variablePrototype()->end() ->end() diff --git a/DependencyInjection/FlasherExtension.php b/DependencyInjection/FlasherExtension.php index 7e5b6a0..11169ac 100644 --- a/DependencyInjection/FlasherExtension.php +++ b/DependencyInjection/FlasherExtension.php @@ -40,6 +40,7 @@ public function getConfiguration(array $config, ContainerBuilder $container): Co * default: string, * main_script: string, * inject_assets: bool, + * excluded_paths: list, * presets: array, * flash_bag: array, * filter: array, @@ -66,6 +67,7 @@ public function process(ContainerBuilder $container): void * default: string, * main_script: string, * inject_assets: bool, + * excluded_paths: list, * presets: array, * flash_bag: array, * filter: array, @@ -88,6 +90,7 @@ private function registerFlasherParameters(array $config, ContainerConfigurator ->set('flasher.default', $config['default']) ->set('flasher.main_script', $config['main_script']) ->set('flasher.inject_assets', $config['inject_assets']) + ->set('flasher.excluded_paths', $config['excluded_paths']) ->set('flasher.flash_bag', $config['flash_bag']) ->set('flasher.filter', $config['filter']) ->set('flasher.presets', $config['presets']) diff --git a/EventListener/FlasherListener.php b/EventListener/FlasherListener.php index 1aab514..1fcfec9 100644 --- a/EventListener/FlasherListener.php +++ b/EventListener/FlasherListener.php @@ -27,7 +27,7 @@ public function onKernelResponse(ResponseEvent $event): void public static function getSubscribedEvents(): array { return [ - ResponseEvent::class => ['onKernelResponse', -256], + ResponseEvent::class => ['onKernelResponse', -20], ]; } } diff --git a/Http/Request.php b/Http/Request.php index 9346dcd..fbb1f4d 100644 --- a/Http/Request.php +++ b/Http/Request.php @@ -16,6 +16,11 @@ public function __construct(private SymfonyRequest $request) { } + public function getUri(): string + { + return $this->request->getRequestUri(); + } + public function isXmlHttpRequest(): bool { return $this->request->isXmlHttpRequest(); diff --git a/Profiler/FlasherDataCollector.php b/Profiler/FlasherDataCollector.php new file mode 100644 index 0000000..ba8dbe1 --- /dev/null +++ b/Profiler/FlasherDataCollector.php @@ -0,0 +1,138 @@ +, + * metadata: array, + * } + * @phpstan-type ConfigShare array{ + * default: string, + * main_script: string, + * inject_assets: bool, + * excluded_paths: list, + * presets: array, + * flash_bag: array, + * filter: array{limit?: int|null}, + * plugins: array>, + * } + * @phpstan-type DataShape array{ + * displayed_envelopes: NotificationShape[], + * dispatched_envelopes: NotificationShape[], + * config: array, + * versions: array{ + * php_flasher: string, + * php: string, + * symfony: string + * } + * } + * + * @property DataShape|Data $data + * + * @internal + */ +#[\AllowDynamicProperties] +final class FlasherDataCollector extends AbstractDataCollector implements LateDataCollectorInterface +{ + /** + * @phpstan-param ConfigShare $config + */ + public function __construct( + private readonly NotificationLoggerListener $logger, + private readonly array $config, + ) { + } + + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void + { + // No action needed here since we're collecting data in lateCollect + } + + public function lateCollect(): void + { + $this->data = [ + 'displayed_envelopes' => array_map(fn (Envelope $envelope) => $envelope->toArray(), $this->logger->getDisplayedEnvelopes()->getEnvelopes()), + 'dispatched_envelopes' => array_map(fn (Envelope $envelope) => $envelope->toArray(), $this->logger->getDispatchedEnvelopes()->getEnvelopes()), + 'config' => $this->config, + 'versions' => [ + 'php_flasher' => Flasher::VERSION, + 'php' => \PHP_VERSION, + 'symfony' => Kernel::VERSION, + ], + ]; + + $this->data = $this->cloneVar($this->data); + } + + /** + * @return DataShape|Data + */ + public function getData(): array|Data + { + return $this->data; + } + + public function getName(): string + { + return 'flasher'; + } + + public function reset(): void + { + $this->logger->reset(); + parent::reset(); + } + + /** + * @return NotificationShape[]|Data + */ + public function getDisplayedEnvelopes(): array|Data + { + return $this->data['displayed_envelopes'] ?? []; + } + + /** + * @return NotificationShape[]|Data + */ + public function getDispatchedEnvelopes(): array|Data + { + return $this->data['dispatched_envelopes'] ?? []; + } + + /** + * @phpstan-return ConfigShare|Data + */ + public function getConfig(): array|Data + { + return $this->data['config'] ?? []; + } + + /** + * @return array{php_flasher: string, php: string, symfony: string}|Data + */ + public function getVersions(): array|Data + { + return $this->data['versions'] ?? []; + } + + public static function getTemplate(): string + { + return '@FlasherSymfony/profiler/flasher.html.twig'; + } +} diff --git a/Resources/config/services.php b/Resources/config/services.php index a806fef..d1403bc 100644 --- a/Resources/config/services.php +++ b/Resources/config/services.php @@ -24,6 +24,7 @@ use Flasher\Symfony\EventListener\FlasherListener; use Flasher\Symfony\EventListener\SessionListener; use Flasher\Symfony\Factory\NotificationFactoryLocator; +use Flasher\Symfony\Profiler\FlasherDataCollector; use Flasher\Symfony\Storage\SessionBag; use Flasher\Symfony\Template\TwigTemplateEngine; use Flasher\Symfony\Translation\Translator; @@ -54,6 +55,7 @@ ->args([ service('flasher'), service('flasher.csp_handler'), + param('flasher.excluded_paths'), ]), ]) ->tag('kernel.event_subscriber') @@ -73,7 +75,7 @@ ->tag('kernel.event_subscriber') ->set('flasher.notification_logger_listener', NotificationLoggerListener::class) - ->tag('flasher.event_dispatcher') + ->tag('flasher.event_listener') ->tag('kernel.reset', ['method' => 'reset']) ->set('flasher.translation_listener', TranslationListener::class) @@ -91,7 +93,7 @@ ->set('flasher.flasher_component', FlasherComponent::class) ->tag('twig.component', [ 'key' => 'flasher', - 'template' => '@Flasher/components/flasher.html.twig', + 'template' => '@FlasherSymfony/components/flasher.html.twig', 'attributesVar' => 'attributes', ]) @@ -146,5 +148,12 @@ param('flasher.public_dir'), param('flasher.json_manifest_path'), ]) + + ->set('flasher.data_collector', FlasherDataCollector::class) + ->args([ + service('flasher.notification_logger_listener'), + param('flasher'), + ]) + ->tag('data_collector', ['id' => 'flasher', 'template' => '@FlasherSymfony/profiler/flasher.html.twig', 'priority' => 0]) ; }; diff --git a/Resources/views/profiler/_notifications_table.html.twig b/Resources/views/profiler/_notifications_table.html.twig new file mode 100644 index 0000000..8f0cc42 --- /dev/null +++ b/Resources/views/profiler/_notifications_table.html.twig @@ -0,0 +1,30 @@ +

Younes KHOUBZA
Younes KHOUBZA

💻 📖 🚧
Younes ENNAJI
Younes ENNAJI

💻 📖 🚧
Salma Mourad
Salma Mourad

💵
Nashwan Abdullah
Nashwan Abdullah

💵
Arvid de Jong
Arvid de Jong

💵
+ + + + + + + + + + + + {% for envelope in envelopes %} + + + + + + + + + {% endfor %} + +
#PluginTypeTitleMessageOptions
{{ loop.index }}{{ envelope.metadata.plugin }}{{ envelope.type }}{{ envelope.title }}{{ envelope.message }} + {% if envelope.options is not empty %} + {{ profiler_dump(envelope.options) }} + {% else %} + No Options + {% endif %} +
diff --git a/Resources/views/profiler/flasher.html.twig b/Resources/views/profiler/flasher.html.twig new file mode 100644 index 0000000..1157f9d --- /dev/null +++ b/Resources/views/profiler/flasher.html.twig @@ -0,0 +1,184 @@ +{% extends '@WebProfiler/Profiler/layout.html.twig' %} + +{% macro logo() %} + + PHPFlasher + +{% endmacro %} + +{% block toolbar %} + {% import _self as macros %} + + {% set displayedEnvelopes = collector.displayedEnvelopes %} + {% set dispatchedEnvelopes = collector.dispatchedEnvelopes %} + + {% set totalDispatched = dispatchedEnvelopes|length %} + {% set totalDisplayed = displayedEnvelopes|length %} + + {% if totalDisplayed > 0 %} + {# Initialize type counts #} + {% set typeCounts = {} %} + {% for envelope in displayedEnvelopes %} + {% set type = envelope.type|default('info')|lower %} + {% set typeCounts = typeCounts | merge({ (type): (typeCounts[type]|default(0) + 1) }) %} + {% endfor %} + + {% set icon %} + {{ macros.logo() }} + + {{ source('@FlasherSymfony/profiler/flasher.svg') }} + + {% if totalDisplayed == totalDispatched %} + {{ totalDisplayed }} + {% else %} + {{ totalDisplayed }}/{{ totalDispatched }} + {% endif %} + + {% endset %} + + {% set text %} +
+ Notifications Displayed + {{ totalDisplayed }} +
+ + {% if totalDispatched != totalDisplayed %} +
+ Notifications Dispatched: + {{ totalDispatched }} +
+ {% endif %} + + {% if totalDisplayed > totalDispatched %} +
+ Note: Some notifications are from previous requests. +
+ {% endif %} + + {% for type, count in typeCounts %} +
+ {{ type|capitalize }} + {{ count }} +
+ {% endfor %} + {% endset %} + + {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url }) }} + {% endif %} +{% endblock %} + +{% block menu %} + {% import _self as macros %} + + {% set totalDisplayed = collector.displayedEnvelopes|length %} + {% set totalDispatched = collector.dispatchedEnvelopes|length %} + + + {{ source('@FlasherSymfony/profiler/flasher.svg') }} + {{ macros.logo() }} + {% if totalDisplayed > 0 %} + + {% if totalDisplayed == totalDispatched %} + {{ totalDisplayed }} + {% else %} + {{ totalDisplayed }}/{{ totalDispatched }} + {% endif %} + + {% endif %} + +{% endblock %} + +{% block panel %} + {% set displayedEnvelopes = collector.displayedEnvelopes %} + {% set dispatchedEnvelopes = collector.dispatchedEnvelopes %} + {% set totalNotifications = dispatchedEnvelopes|length %} + {% set displayedNotifications = displayedEnvelopes|length %} + {% set config = collector.config %} + {% set versions = collector.versions %} + +

PHPFlasher Notifications

+ + {% if totalNotifications == 0 %} +
+

No notifications have been dispatched.

+
+ {% else %} +
+
+

Notifications {{ displayedNotifications }}/{{ totalNotifications }}

+
+ {% if displayedNotifications > totalNotifications %} +
+

The number of displayed notifications is greater than the number of dispatched notifications. This may happen if notifications are stored in the session from previous requests.

+
+ {% endif %} + +

Displayed Notifications

+ {{ include('@FlasherSymfony/profiler/_notifications_table.html.twig', { 'envelopes': displayedEnvelopes }) }} + + {% if totalNotifications > displayedNotifications %} +

Remaining Notifications

+ {% set remainingNotifications = dispatchedEnvelopes|slice(displayedNotifications) %} + {{ include('@FlasherSymfony/profiler/_notifications_table.html.twig', { 'envelopes': remainingNotifications }) }} + {% endif %} +
+
+ +
+

Debug

+
+

Version Information

+
    +
  • PHPFlasher Version: {{ versions.php_flasher }}
  • +
  • PHP Version: {{ versions.php }}
  • +
  • Symfony Version: {{ versions.symfony }}
  • +
+ +

Configuration

+ {{ profiler_dump(config, maxDepth=10) }} +
+
+
+ {% endif %} +{% endblock %} + +{% block head %} + {{ parent() }} + +{% endblock %} diff --git a/Resources/views/profiler/flasher.svg b/Resources/views/profiler/flasher.svg new file mode 100644 index 0000000..291f1fb --- /dev/null +++ b/Resources/views/profiler/flasher.svg @@ -0,0 +1,8 @@ + + + + + diff --git a/composer.json b/composer.json index 13fdb9d..bf0d74a 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "prefer-stable": true, "require": { "php": ">=8.2", - "php-flasher/flasher": "^2.0.1", + "php-flasher/flasher": "^2.1.0", "symfony/config": "^7.0", "symfony/console": "^7.0", "symfony/dependency-injection": "^7.0", From 913604dd45dc9a951f6691d6927b3cfdb39626f5 Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Sun, 6 Oct 2024 14:29:45 +0100 Subject: [PATCH 16/26] chore: improve configuration descriptions and add examples --- DependencyInjection/Configuration.php | 36 +++++++++++++++++---- Resources/config/config.yaml | 46 +++++++++++++++++---------- 2 files changed, 60 insertions(+), 22 deletions(-) diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 029cc52..16f1d4c 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -43,20 +43,25 @@ private function addGeneralSection(ArrayNodeDefinition $rootNode): void $rootNode ->children() ->scalarNode('default') + ->info('Default notification library (e.g., "flasher", "toastr", "noty", "notyf", "sweetalert")') ->isRequired() ->cannotBeEmpty() ->defaultValue($this->plugin->getDefault()) ->end() ->scalarNode('main_script') + ->info('Path to the main PHPFlasher JavaScript file') ->defaultValue($this->plugin->getRootScript()) ->end() - ->booleanNode('translate') + ->booleanNode('inject_assets') + ->info('Automatically inject assets into HTML pages') ->defaultTrue() ->end() - ->booleanNode('inject_assets') + ->booleanNode('translate') + ->info('Enable message translation') ->defaultTrue() ->end() ->arrayNode('excluded_paths') + ->info('URL patterns to exclude from asset injection and flash_bag conversion') ->defaultValue([ '/^\/_profiler/', '/^\/_fragment/', @@ -64,17 +69,21 @@ private function addGeneralSection(ArrayNodeDefinition $rootNode): void ->scalarPrototype()->end() ->end() ->arrayNode('filter') + ->info('Criteria to filter notifications') ->variablePrototype()->end() ->end() ->arrayNode('scripts') + ->info('Additional JavaScript files') ->performNoDeepMerging() ->scalarPrototype()->end() ->end() ->arrayNode('styles') + ->info('CSS files to style notifications') ->performNoDeepMerging() ->scalarPrototype()->end() ->end() ->arrayNode('options') + ->info('Global notification options') ->variablePrototype()->end() ->end() ->end(); @@ -85,6 +94,7 @@ private function addFlashBagSection(ArrayNodeDefinition $rootNode): void $rootNode ->children() ->variableNode('flash_bag') + ->info('Map Symfony flash messages to notification types') ->defaultTrue() ->end() ->end(); @@ -96,13 +106,21 @@ private function addPresetsSection(ArrayNodeDefinition $rootNode): void ->fixXmlConfig('preset') ->children() ->arrayNode('presets') + ->info('Notification presets (optional)') ->useAttributeAsKey('name') ->arrayPrototype() ->children() - ->scalarNode('type')->end() - ->scalarNode('title')->end() - ->scalarNode('message')->end() + ->scalarNode('type') + ->info('Notification type (e.g., "success", "error")') + ->end() + ->scalarNode('title') + ->info('Default title') + ->end() + ->scalarNode('message') + ->info('Default message') + ->end() ->arrayNode('options') + ->info('Additional options') ->variablePrototype()->end() ->end() ->end() @@ -117,19 +135,25 @@ private function addPluginsSection(ArrayNodeDefinition $rootNode): void ->fixXmlConfig('plugin') ->children() ->arrayNode('plugins') + ->info('Additional plugins') ->useAttributeAsKey('name') ->arrayPrototype() ->children() - ->scalarNode('view')->end() + ->scalarNode('view') + ->info('Custom twig view template') + ->end() ->arrayNode('styles') + ->info('CSS files for the plugin') ->performNoDeepMerging() ->scalarPrototype()->end() ->end() ->arrayNode('scripts') + ->info('JavaScript files for the plugin') ->performNoDeepMerging() ->scalarPrototype()->end() ->end() ->arrayNode('options') + ->info('Plugin-specific options') ->variablePrototype()->end() ->end() ->end() diff --git a/Resources/config/config.yaml b/Resources/config/config.yaml index 32c395a..1f472bc 100644 --- a/Resources/config/config.yaml +++ b/Resources/config/config.yaml @@ -1,34 +1,48 @@ flasher: - # Default notification library (e.g., 'flasher', 'toastr', 'noty', etc.) + # Default notification library (e.g., 'flasher', 'toastr', 'noty', 'notyf', 'sweetalert') default: flasher - # Path to the main JavaScript file of PHPFlasher + # Path to the main PHPFlasher JavaScript file main_script: '/vendor/flasher/flasher.min.js' - # Path to the stylesheets for PHPFlasher notifications + # List of CSS files to style your notifications styles: - '/vendor/flasher/flasher.min.css' - # Enable translation of PHPFlasher messages using Symfony's service - translate: true + # Set global options for all notifications (optional) + # options: + # # Time in milliseconds before the notification disappears + # timeout: 5000 + # # Where the notification appears on the screen + # position: 'top-right' - # Automatically inject PHPFlasher assets in HTML response + # Automatically inject JavaScript and CSS assets into your HTML pages inject_assets: true - # Global options - options: - # timeout in milliseconds - timeout: 5000 - position: 'top-right' + # Enable message translation using Symfony's translation service + translate: true + + # URL patterns to exclude from asset injection and flash_bag conversion + excluded_paths: + - '/^\/_profiler/' + - '/^\/_fragment/' - # Map Symfony session keys to PHPFlasher notification types + # Map Symfony flash message keys to notification types flash_bag: success: ['success'] error: ['error', 'danger'] warning: ['warning', 'alarm'] info: ['info', 'notice', 'alert'] - # Criteria to filter displayed notifications (limit, types) - filter_criteria: - # Limit number of displayed notifications - limit: 5 + # Set criteria to filter which notifications are displayed (optional) + # filter: + # # Maximum number of notifications to show at once + # limit: 5 + + # Define notification presets to simplify notification creation (optional) + # presets: + # # Example preset: + # entity_saved: + # type: 'success' + # title: 'Entity saved' + # message: 'Entity saved successfully' From 01905e67aa37310736a6d3db102e8e6fefa1f106 Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Sun, 20 Oct 2024 15:11:19 +0100 Subject: [PATCH 17/26] chore: bump flasher dependency to v2.1.1 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index bf0d74a..11064f2 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "prefer-stable": true, "require": { "php": ">=8.2", - "php-flasher/flasher": "^2.1.0", + "php-flasher/flasher": "^2.1.1", "symfony/config": "^7.0", "symfony/console": "^7.0", "symfony/dependency-injection": "^7.0", From 841e668823abdb29122d634ec5bb0c2718ebe1d4 Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Sun, 27 Oct 2024 15:29:49 +0100 Subject: [PATCH 18/26] docs: update symfony README.md file --- README.md | 346 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 330 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 47aa95a..c613a52 100644 --- a/README.md +++ b/README.md @@ -6,26 +6,340 @@

- - PHPFlasher Logo + + PHPFlasher Logo

-## About PHPFlasher +

+ Author Badge + Source Code Badge + GitHub Release Badge + License Badge + Packagist Downloads Badge + GitHub Stars Badge + Supported PHP Version Badge +

+ +## Table of Contents + +- [About PHPFlasher Symfony Adapter](#about-phpflasher-symfony-adapter) +- [Features](#features) +- [Supported Versions](#supported-versions) +- [Installation](#installation) + - [Core Package](#core-package) + - [Adapters](#adapters) +- [Configuration](#configuration) + - [Configuration File](#configuration-file) + - [Configuration Options](#configuration-options) +- [Quick Start](#quick-start) +- [Usage Examples](#usage-examples) +- [Adapters Overview](#adapters-overview) +- [Official Documentation](#official-documentation) +- [Contributors and Sponsors](#contributors-and-sponsors) +- [Contact](#contact) +- [License](#license) + +## About PHPFlasher Symfony Adapter + +**PHPFlasher Symfony Adapter** is an open-source package that seamlessly integrates PHPFlasher’s robust flash messaging capabilities into your **Symfony** applications. It streamlines the process of adding flash messages, offering an intuitive API to enhance user experience with minimal configuration. + +With PHPFlasher Symfony Adapter, you can effortlessly display success, error, warning, and informational messages to your users, ensuring clear communication of application states and actions. + +## Features + +- **Seamless Symfony Integration**: Designed specifically for Symfony, ensuring compatibility and ease of use. +- **Multiple Notification Libraries**: Supports various frontend libraries like Toastr, Noty, SweetAlert, and Notyf. +- **Flexible Configuration**: Customize the appearance and behavior of flash messages to fit your application's needs. +- **Intuitive API**: Simple methods to create and manage flash messages without boilerplate code. +- **Extensible**: Easily add or create new adapters for different frontend libraries. + +## Supported Versions + +| PHPFlasher Symfony Adapter Version | PHP Version | Symfony Version | +|------------------------------------|-------------|-----------------| +| **v2.x** | ≥ 8.2 | ≥ 7.0 | +| **v1.x** | ≥ 5.3 | ≥ 2.0 | + +> **Note:** Ensure your project meets the PHP and Symfony version requirements for the PHPFlasher Symfony Adapter version you intend to use. For older PHP or Symfony versions, refer to [PHPFlasher v1.x](https://github.com/php-flasher/flasher-symfony/tree/1.x). + +## Installation + +### Core Package + +Install the PHPFlasher Symfony Adapter via Composer: + +```bash +composer require php-flasher/flasher-symfony +``` + +After installation, set up the necessary assets: + +```shell +php bin/console flasher:install +``` + +> **Note:** PHPFlasher automatically injects the necessary JavaScript and CSS assets into your Blade templates. No additional steps are required for asset injection. + +### Adapters + +PHPFlasher provides various adapters for different notification libraries. Below is an overview of available adapters for Symfony: + +- [flasher-toastr-symfony](https://github.com/php-flasher/flasher-toastr-symfony) - Symfony Adapter +- [flasher-noty-symfony](https://github.com/php-flasher/flasher-noty-symfony) - Symfony Adapter +- [flasher-notyf-symfony](https://github.com/php-flasher/flasher-notyf-symfony) - Symfony Adapter +- [flasher-sweetalert-symfony](https://github.com/php-flasher/flasher-sweetalert-symfony) - Symfony Adapter + +For detailed installation and usage instructions for each adapter, refer to their respective `README.md`. + +## Configuration + +After installing the PHPFlasher Symfony Adapter, you can configure it by publishing the configuration file or by modifying it directly. + +### Configuration File + +If you need to customize the default settings, publish the configuration file using the following command: + +```bash +php bin/console flasher:install --config +``` + +This will create a file at `config/packages/flasher.yaml` with the following content: + +```yaml +flasher: + # Default notification library (e.g., 'flasher', 'toastr', 'noty', 'notyf', 'sweetalert') + default: flasher + + # Path to the main PHPFlasher JavaScript file + main_script: '/vendor/flasher/flasher.min.js' + + # List of CSS files to style your notifications + styles: + - '/vendor/flasher/flasher.min.css' + + # Set global options for all notifications (optional) + # options: + # # Time in milliseconds before the notification disappears + # timeout: 5000 + # # Where the notification appears on the screen + # position: 'top-right' + + # Automatically inject JavaScript and CSS assets into your HTML pages + inject_assets: true + + # Enable message translation using Symfony's translation service + translate: true + + # URL patterns to exclude from asset injection and flash_bag conversion + excluded_paths: + - '/^\/_profiler/' + - '/^\/_fragment/' + + # Map Symfony flash message keys to notification types + flash_bag: + success: ['success'] + error: ['error', 'danger'] + warning: ['warning', 'alarm'] + info: ['info', 'notice', 'alert'] + + # Set criteria to filter which notifications are displayed (optional) + # filter: + # # Maximum number of notifications to show at once + # limit: 5 + + # Define notification presets to simplify notification creation (optional) + # presets: + # # Example preset: + # entity_saved: + # type: 'success' + # title: 'Entity saved' + # message: 'Entity saved successfully' +``` + +### Configuration Options + +| **Option** | **Description** | +|------------------|---------------------------------------------------------------------------------------------------------------------------| +| `default` | **String**: The default notification library to use (e.g., `'flasher'`, `'toastr'`, `'noty'`, `'notyf'`, `'sweetalert'`). | +| `main_script` | **String**: Path to the main PHPFlasher JavaScript file. | +| `styles` | **Array**: List of CSS files to style your notifications. | +| `options` | **Array** (Optional): Global options for all notifications (e.g., `'timeout'`, `'position'`). | +| `inject_assets` | **Boolean**: Whether to automatically inject JavaScript and CSS assets into your HTML pages. | +| `translate` | **Boolean**: Enable message translation using Symfony’s translation service. | +| `excluded_paths` | **Array**: URL patterns to exclude from asset injection and flash_bag conversion. | +| `flash_bag` | **Array**: Map Symfony flash message keys to notification types. | +| `filter` | **Array** (Optional): Criteria to filter which notifications are displayed (e.g., `'limit'`). | +| `presets` | **Array** (Optional): Define notification presets to simplify notification creation. | + +## Quick Start + +To display a notification message, you can either use the `flash()` helper function or obtain an instance of `flasher` from the service container. Then, before returning a view or redirecting, call the desired method (`success()`, `error()`, etc.) and pass in the message to be displayed. + +### Using the `flash()` Helper + +```php +redirectToRoute('book_list'); + } +} +``` + +### Using the `flasher` Service + +```php +flasher = $flasher; + } + + public function register(): RedirectResponse + { + // Your logic here + + $this->flasher->success('Your changes have been saved!'); + + // ... redirect or render the view + return $this->redirectToRoute('home'); + } + + public function update(): RedirectResponse + { + // Your logic here + + $this->flasher->error('An error occurred while updating.'); + + return $this->redirectToRoute('update_page'); + } +} +``` + +## Usage Examples + +### Success Message + +```php +flash()->success('Operation completed successfully!'); +``` + +### Error Message + +```php +flash()->error('An error occurred.'); +``` + +### Info Message + +```php +flash()->info('This is an informational message.'); +``` + +### Warning Message + +```php +flash()->warning('This is a warning message.'); +``` + +### Passing Options + +```php +flash()->success('Custom message with options.', ['timeout' => 3000, 'position' => 'bottom-left']); +``` + +### Using presets + +Define a preset in your `config/packages/flasher.yaml`: + +```yaml +flasher: + # ... other configurations + + presets: + entity_saved: + type: 'success' + title: 'Entity Saved' + message: 'The entity has been saved successfully.' + entity_deleted: + type: 'warning' + title: 'Entity Deleted' + message: 'The entity has been deleted.' +``` + +Use the preset in your controller: + +```php +preset('entity_saved'); + + return $this->redirectToRoute('books.index'); + } + + public function delete(): RedirectResponse + { + // Your deletion logic + + flash()->preset('entity_deleted'); + + return $this->redirectToRoute('books.index'); + } +} +``` -PHPFlasher is a powerful and easy-to-use package that allows you to quickly and easily add flash messages to your Laravel or Symfony projects. -Whether you need to alert users of a successful form submission, an error, or any other important information, flash messages are a simple and effective solution for providing feedback to your users. +## Adapters Overview -With PHPFlasher, you can easily record and store messages within the session, making it simple to retrieve and display them on the current or next page. -This improves user engagement and enhances the overall user experience on your website or application. +PHPFlasher supports various adapters to integrate seamlessly with different frontend libraries. Below is an overview of available adapters for Symfony: -Whether you're a beginner or an experienced developer, PHPFlasher's intuitive and straightforward design makes it easy to integrate into your projects. -So, if you're looking for a reliable, flexible and easy to use flash messages solution, PHPFlasher is the perfect choice. +| Adapter Repository | Description | +|-----------------------------------------------------------------------------------------|--------------------------------| +| [flasher-symfony](https://github.com/php-flasher/flasher-symfony) | Symfony framework adapter | +| [flasher-toastr-symfony](https://github.com/php-flasher/flasher-toastr-symfony) | Toastr adapter for Symfony | +| [flasher-noty-symfony](https://github.com/php-flasher/flasher-noty-symfony) | Noty adapter for Symfony | +| [flasher-notyf-symfony](https://github.com/php-flasher/flasher-notyf-symfony) | Notyf adapter for Symfony | +| [flasher-sweetalert-symfony](https://github.com/php-flasher/flasher-sweetalert-symfony) | SweetAlert adapter for Symfony | +> **Note:** Each adapter has its own repository. For detailed installation and usage instructions, please refer to the [Official Documentation](https://php-flasher.io). ## Official Documentation -Documentation for PHPFlasher can be found on the [https://php-flasher.io](https://php-flasher.io). +Comprehensive documentation for PHPFlasher is available at [https://php-flasher.io](https://php-flasher.io). Here you will find detailed guides, API references, and advanced usage examples to help you get the most out of PHPFlasher. ## Contributors and sponsors @@ -42,7 +356,7 @@ Shining stars of our community: - + @@ -52,7 +366,7 @@ Shining stars of our community: - +
Younes ENNAJI
Younes ENNAJI

💻 📖 🚧
Younes ENNAJI
Younes ENNAJI

💻 📖 🚧
Salma Mourad
Salma Mourad

💵
Nashwan Abdullah
Nashwan Abdullah

💵
Arvid de Jong
Arvid de Jong

💵
Lucas Maciel
Lucas Maciel

🎨
Antoni Siek
Antoni Siek

💻
Ahmed Gamal
Ahmed Gamal

💻 📖
@@ -64,17 +378,17 @@ Shining stars of our community: ## Contact -PHPFlasher is being actively developed by yoeunes. +PHPFlasher is being actively developed by yoeunes. You can reach out with questions, bug reports, or feature requests on any of the following: -- [Github Issues](https://github.com/php-flasher/php-flasher/issues) +- [Github Issues](https://github.com/php-flasher/php-flasher/issues) - [Github](https://github.com/yoeunes) - [Twitter](https://twitter.com/yoeunes) -- [Linkedin](https://www.linkedin.com/in/younes--ennaji//) +- [Linkedin](https://www.linkedin.com/in/younes--ennaji/) - [Email me directly](mailto:younes.ennaji.pro@gmail.com) ## License PHPFlasher is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). -

Made with ❤️ by Younes ENNAJI

+

Made with ❤️ by Younes ENNAJI

From 0cb35332d5ca299a7431dd906bc58360bc1394e1 Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Sun, 27 Oct 2024 16:04:49 +0100 Subject: [PATCH 19/26] Add PR template and auto-close PR on subtree split repositories --- .github/PULL_REQUEST_TEMPLATE.md | 8 ++++++++ .github/workflows/close-pull-request.yml | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/close-pull-request.yml diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..821ddf5 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,8 @@ +Please do not submit any Pull Requests here. They will be closed. +--- + +Please submit your PR here instead: +https://github.com/php-flasher/php-flasher + +This repository is what we call a "subtree split": a read-only subset of that main repository. +We're looking forward to your PR there! diff --git a/.github/workflows/close-pull-request.yml b/.github/workflows/close-pull-request.yml new file mode 100644 index 0000000..6e2076f --- /dev/null +++ b/.github/workflows/close-pull-request.yml @@ -0,0 +1,20 @@ +name: Close Pull Request + +on: + pull_request_target: + types: [opened] + +jobs: + run: + runs-on: ubuntu-latest + steps: + - uses: superbrothers/close-pull-request@v3 + with: + comment: | + Thanks for your Pull Request! We love contributions. + + However, you should instead open your PR on the main repository: + https://github.com/php-flasher/php-flasher + + This repository is what we call a "subtree split": a read-only subset of that main repository. + We're looking forward to your PR there! From ff3180f315a8ee5a9802cf401036dd1e33738e64 Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Sat, 18 Jan 2025 11:26:31 +0100 Subject: [PATCH 20/26] chore: bump flasher dependency to v2.1.2 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 11064f2..e3c8cdf 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "prefer-stable": true, "require": { "php": ">=8.2", - "php-flasher/flasher": "^2.1.1", + "php-flasher/flasher": "^2.1.2", "symfony/config": "^7.0", "symfony/console": "^7.0", "symfony/dependency-injection": "^7.0", From 8427282862025d59f5b3db5ab9d1d9ab25cef9cd Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Tue, 18 Feb 2025 13:58:18 +0100 Subject: [PATCH 21/26] chore: bump flasher dependency to v2.1.4 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e3c8cdf..128c88f 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "prefer-stable": true, "require": { "php": ">=8.2", - "php-flasher/flasher": "^2.1.2", + "php-flasher/flasher": "^2.1.4", "symfony/config": "^7.0", "symfony/console": "^7.0", "symfony/dependency-injection": "^7.0", From 5011997516a245f8a1d466bd9b002b8ac26a0746 Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Fri, 21 Feb 2025 18:52:39 +0100 Subject: [PATCH 22/26] 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 23/26] 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 24/26] 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 { From e9b08a3e4ac315775550eec3f20f8d8fd942ee21 Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Thu, 27 Mar 2025 21:13:03 +0000 Subject: [PATCH 25/26] add themes to symfony configuration --- DependencyInjection/Configuration.php | 31 ++++++++++++++++++++++++ DependencyInjection/FlasherExtension.php | 30 ++++++++++++++++++++++- Resources/config/services.php | 2 +- 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 16f1d4c..c6436eb 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -26,6 +26,7 @@ public function getConfigTreeBuilder(): TreeBuilder $this->addFlashBagSection($rootNode); $this->addPresetsSection($rootNode); $this->addPluginsSection($rootNode); + $this->addThemesSection($rootNode); return $treeBuilder; } @@ -161,4 +162,34 @@ private function addPluginsSection(ArrayNodeDefinition $rootNode): void ->end() ->end(); } + + private function addThemesSection(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->fixXmlConfig('theme') + ->children() + ->arrayNode('themes') + ->info('Additional themes') + ->useAttributeAsKey('name') + ->arrayPrototype() + ->children() + ->arrayNode('styles') + ->info('CSS files for the theme') + ->performNoDeepMerging() + ->scalarPrototype()->end() + ->end() + ->arrayNode('scripts') + ->info('JavaScript files for the theme') + ->performNoDeepMerging() + ->scalarPrototype()->end() + ->end() + ->arrayNode('options') + ->info('Theme-specific options') + ->variablePrototype()->end() + ->end() + ->end() + ->end() + ->end() + ->end(); + } } diff --git a/DependencyInjection/FlasherExtension.php b/DependencyInjection/FlasherExtension.php index 11169ac..0e759fe 100644 --- a/DependencyInjection/FlasherExtension.php +++ b/DependencyInjection/FlasherExtension.php @@ -45,6 +45,7 @@ public function getConfiguration(array $config, ContainerBuilder $container): Co * flash_bag: array, * filter: array, * plugins: array>, + * themes: array>, * } $config */ public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void @@ -72,6 +73,7 @@ public function process(ContainerBuilder $container): void * flash_bag: array, * filter: array, * plugins: array>, + * themes: array>, * } $config */ private function registerFlasherParameters(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void @@ -95,7 +97,8 @@ private function registerFlasherParameters(array $config, ContainerConfigurator ->set('flasher.filter', $config['filter']) ->set('flasher.presets', $config['presets']) ->set('flasher.plugins', $config['plugins']) - ; + ->set('flasher.themes', $config['themes']) + ->set('flasher.resources', $this->getFlasherResources($config)); } private function registerServicesForAutoconfiguration(ContainerBuilder $builder): void @@ -141,4 +144,29 @@ private function configureFlasherListener(ContainerBuilder $container): void $container->removeDefinition('flasher.flasher_listener'); } + + /** + * Convert the Flasher configuration into a format that can be used by the ResourceManager. + * + * @param array{ + * plugins: array>, + * themes: array>, + * } $config + * + * @return array> + */ + private function getFlasherResources(array $config): array + { + $resources = []; + + foreach ($config['plugins'] as $name => $options) { + $resources[$name] = $options; + } + + foreach ($config['themes'] as $name => $options) { + $resources['theme.'.$name] = $options; + } + + return $resources; + } } diff --git a/Resources/config/services.php b/Resources/config/services.php index d1403bc..d458a9c 100644 --- a/Resources/config/services.php +++ b/Resources/config/services.php @@ -127,7 +127,7 @@ service('flasher.template_engine'), service('flasher.asset_manager'), param('flasher.main_script'), - param('flasher.plugins'), + param('flasher.resources'), ]) ->set('flasher.response_manager', ResponseManager::class) From 321269b58706a977916dc5058321c450e57e911a Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Mon, 10 Nov 2025 15:20:21 +0100 Subject: [PATCH 26/26] Updates Flasher to v2.2.0 Updates the flasher dependencies in composer.json files and package.json files to version 2.2.0. Updates the Flasher version constant in the Prime Flasher class. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index b8f20db..131fa52 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "prefer-stable": true, "require": { "php": ">=8.2", - "php-flasher/flasher": "^2.1.6", + "php-flasher/flasher": "^2.2.0", "symfony/config": "^7.0", "symfony/console": "^7.0", "symfony/dependency-injection": "^7.0",