├── .github ├── FUNDING.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── close-pull-request.yml ├── .phpstorm.meta.php ├── Attribute ├── AsFlasherFactory.php └── AsFlasherPresenter.php ├── Command └── InstallCommand.php ├── Component └── FlasherComponent.php ├── DependencyInjection ├── Compiler │ ├── EventListenerCompilerPass.php │ └── PresenterCompilerPass.php ├── Configuration.php └── FlasherExtension.php ├── EventListener ├── FlasherListener.php └── SessionListener.php ├── Factory └── NotificationFactoryLocator.php ├── FlasherSymfonyBundle.php ├── Http ├── Request.php └── Response.php ├── LICENSE ├── Profiler └── FlasherDataCollector.php ├── README.md ├── Resources ├── config │ ├── config.yaml │ └── services.php ├── translations │ ├── flasher.ar.php │ ├── flasher.de.php │ ├── flasher.en.php │ ├── flasher.es.php │ ├── flasher.fr.php │ ├── flasher.pt.php │ ├── flasher.ru.php │ └── flasher.zh.php └── views │ ├── bootstrap.html.twig │ ├── components │ └── flasher.html.twig │ ├── profiler │ ├── _notifications_table.html.twig │ ├── flasher.html.twig │ └── flasher.svg │ ├── tailwindcss.html.twig │ ├── tailwindcss_bg.html.twig │ └── tailwindcss_r.html.twig ├── Storage ├── FallbackSession.php ├── FallbackSessionInterface.php └── SessionBag.php ├── Support ├── PluginBundle.php └── PluginBundleInterface.php ├── Template └── TwigTemplateEngine.php ├── Translation └── Translator.php ├── Twig └── FlasherTwigExtension.php └── composer.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: yoeunes 2 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Please do not submit any Pull Requests here. They will be closed. 2 | --- 3 | 4 | Please submit your PR here instead: 5 | https://github.com/php-flasher/php-flasher 6 | 7 | This repository is what we call a "subtree split": a read-only subset of that main repository. 8 | We're looking forward to your PR there! 9 | -------------------------------------------------------------------------------- /.github/workflows/close-pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Close Pull Request 2 | 3 | on: 4 | pull_request_target: 5 | types: [opened] 6 | 7 | jobs: 8 | run: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: superbrothers/close-pull-request@v3 12 | with: 13 | comment: | 14 | Thanks for your Pull Request! We love contributions. 15 | 16 | However, you should instead open your PR on the main repository: 17 | https://github.com/php-flasher/php-flasher 18 | 19 | This repository is what we call a "subtree split": a read-only subset of that main repository. 20 | We're looking forward to your PR there! 21 | -------------------------------------------------------------------------------- /.phpstorm.meta.php: -------------------------------------------------------------------------------- 1 | setName('flasher:install') 50 | ->setDescription('Installs all PHPFlasher resources to the public and config directories.') 51 | ->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.') 52 | ->addOption('config', 'c', InputOption::VALUE_NONE, 'Publish all config files to the config/packages/ directory.') 53 | ->addOption('symlink', 's', InputOption::VALUE_NONE, 'Symlink PHPFlasher assets instead of copying them.'); 54 | } 55 | 56 | /** 57 | * Execute the command to install PHPFlasher resources. 58 | * 59 | * This method processes each registered bundle that implements PluginBundleInterface, 60 | * installing its assets and configuration files as requested. 61 | * 62 | * @param InputInterface $input The input interface 63 | * @param OutputInterface $output The output interface 64 | * 65 | * @return int Command status code (SUCCESS or FAILURE) 66 | */ 67 | protected function execute(InputInterface $input, OutputInterface $output): int 68 | { 69 | // Display PHPFlasher banner and info message 70 | $output->writeln(''); 71 | $output->writeln(' 72 | ██████╗ ██╗ ██╗██████╗ ███████╗██╗ █████╗ ███████╗██╗ ██╗███████╗██████╗ 73 | ██╔══██╗██║ ██║██╔══██╗██╔════╝██║ ██╔══██╗██╔════╝██║ ██║██╔════╝██╔══██╗ 74 | ██████╔╝███████║██████╔╝█████╗ ██║ ███████║███████╗███████║█████╗ ██████╔╝ 75 | ██╔═══╝ ██╔══██║██╔═══╝ ██╔══╝ ██║ ██╔══██║╚════██║██╔══██║██╔══╝ ██╔══██╗ 76 | ██║ ██║ ██║██║ ██║ ███████╗██║ ██║███████║██║ ██║███████╗██║ ██║ 77 | ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ 78 | '); 79 | $output->writeln(''); 80 | 81 | $output->writeln(''); 82 | $output->writeln(' INFO Copying PHPFlasher resources...'); 83 | $output->writeln(''); 84 | 85 | // Get application and validate it's a Symfony application 86 | $application = $this->getApplication(); 87 | if (!$application instanceof Application) { 88 | return self::SUCCESS; 89 | } 90 | 91 | // Process command options 92 | $useSymlinks = (bool) $input->getOption('symlink'); 93 | if ($useSymlinks) { 94 | $output->writeln('Using symlinks to publish assets.'); 95 | } else { 96 | $output->writeln('Copying assets to the public directory.'); 97 | } 98 | 99 | $publishConfig = (bool) $input->getOption('config'); 100 | if ($publishConfig) { 101 | $output->writeln('Publishing configuration files.'); 102 | } 103 | 104 | // Prepare directories 105 | $publicDir = $this->getPublicDir().'/vendor/flasher/'; 106 | $configDir = $this->getConfigDir(); 107 | 108 | $filesystem = new Filesystem(); 109 | $filesystem->remove($publicDir); 110 | $filesystem->mkdir($publicDir); 111 | 112 | // Process each plugin bundle 113 | $files = []; 114 | $exitCode = self::SUCCESS; 115 | 116 | $kernel = $application->getKernel(); 117 | foreach ($kernel->getBundles() as $bundle) { 118 | if (!$bundle instanceof PluginBundleInterface) { 119 | continue; 120 | } 121 | 122 | $plugin = $bundle->createPlugin(); 123 | $configFile = $bundle->getConfigurationFile(); 124 | 125 | try { 126 | // Install assets and config 127 | $files[] = $this->publishAssets($plugin, $publicDir, $useSymlinks); 128 | 129 | if ($publishConfig) { 130 | $this->publishConfig($plugin, $configDir, $configFile); 131 | } 132 | 133 | // Report success 134 | $status = \sprintf('%s', '\\' === \DIRECTORY_SEPARATOR ? 'OK' : "\xE2\x9C\x94" /* HEAVY CHECK MARK (U+2714) */); 135 | $output->writeln(\sprintf(' %s %s', $status, $plugin->getAlias())); 136 | } catch (\Exception $e) { 137 | // Report failure 138 | $exitCode = self::FAILURE; 139 | $status = \sprintf('%s', '\\' === \DIRECTORY_SEPARATOR ? 'ERROR' : "\xE2\x9C\x98" /* HEAVY BALLOT X (U+2718) */); 140 | $output->writeln(\sprintf(' %s %s %s', $status, $plugin->getAlias(), $e->getMessage())); 141 | } 142 | } 143 | 144 | $output->writeln(''); 145 | 146 | // Display final status message 147 | if (self::SUCCESS === $exitCode) { 148 | $message = 'PHPFlasher resources have been successfully installed.'; 149 | if ($publishConfig) { 150 | $message .= ' Configuration files have been published.'; 151 | } 152 | if ($useSymlinks) { 153 | $message .= ' Assets were symlinked.'; 154 | } 155 | $output->writeln(" SUCCESS $message"); 156 | } else { 157 | $output->writeln(' ERROR An error occurred during the installation of PHPFlasher resources.'); 158 | } 159 | 160 | // Create asset manifest 161 | $this->assetManager->createManifest(array_merge([], ...$files)); 162 | 163 | $output->writeln(''); 164 | 165 | return $exitCode; 166 | } 167 | 168 | /** 169 | * Publishes assets from the plugin's assets directory to the public directory. 170 | * 171 | * This method copies or symlinks asset files from the plugin's assets directory 172 | * to the public directory for web access. 173 | * 174 | * @param PluginInterface $plugin The plugin containing assets 175 | * @param string $publicDir Target directory for assets 176 | * @param bool $useSymlinks Whether to symlink files instead of copying 177 | * 178 | * @return string[] List of target paths for generated manifest 179 | */ 180 | private function publishAssets(PluginInterface $plugin, string $publicDir, bool $useSymlinks): array 181 | { 182 | $originDir = $plugin->getAssetsDir(); 183 | 184 | if (!is_dir($originDir)) { 185 | return []; 186 | } 187 | 188 | $filesystem = new Filesystem(); 189 | $finder = new Finder(); 190 | $finder->files()->in($originDir); 191 | 192 | $files = []; 193 | 194 | foreach ($finder as $file) { 195 | $relativePath = trim(str_replace($originDir, '', $file->getRealPath()), \DIRECTORY_SEPARATOR); 196 | $targetPath = $publicDir.$relativePath; 197 | 198 | if ($useSymlinks) { 199 | $filesystem->symlink($file->getRealPath(), $targetPath); 200 | } else { 201 | $filesystem->copy($file->getRealPath(), $targetPath, true); 202 | } 203 | 204 | $files[] = $targetPath; 205 | } 206 | 207 | return $files; 208 | } 209 | 210 | /** 211 | * Publishes configuration files to the application's config directory. 212 | * 213 | * This method copies plugin configuration files to the Symfony config directory, 214 | * but only if the target file doesn't already exist (to avoid overwriting user customizations). 215 | * 216 | * @param PluginInterface $plugin The plugin containing configuration 217 | * @param string|null $configDir Target config directory 218 | * @param string $configFile Source configuration file path 219 | */ 220 | private function publishConfig(PluginInterface $plugin, ?string $configDir, string $configFile): void 221 | { 222 | if (null === $configDir || !file_exists($configFile)) { 223 | return; 224 | } 225 | 226 | $target = $configDir.$plugin->getName().'.yaml'; 227 | if (file_exists($target)) { 228 | return; 229 | } 230 | 231 | $filesystem = new Filesystem(); 232 | $filesystem->copy($configFile, $target); 233 | } 234 | 235 | /** 236 | * Gets the path to the public directory. 237 | * 238 | * This method tries to locate the public directory using multiple strategies: 239 | * 1. First, it looks for a standard 'public' directory in the project 240 | * 2. If not found, it falls back to the composer.json configuration 241 | * 242 | * @return string|null Path to the public directory or null if not found 243 | */ 244 | private function getPublicDir(): ?string 245 | { 246 | $projectDir = $this->getProjectDir(); 247 | if (null === $projectDir) { 248 | return null; 249 | } 250 | 251 | $publicDir = rtrim($projectDir, '/').'/public'; 252 | 253 | if (is_dir($publicDir)) { 254 | return $publicDir; 255 | } 256 | 257 | return $this->getComposerDir('public-dir'); 258 | } 259 | 260 | /** 261 | * Gets the path to the config directory. 262 | * 263 | * This method tries to locate the config/packages directory using multiple strategies: 264 | * 1. First, it looks for a standard 'config/packages' directory in the project 265 | * 2. If not found, it falls back to the composer.json configuration 266 | * 267 | * @return string|null Path to the config directory or null if not found 268 | */ 269 | private function getConfigDir(): ?string 270 | { 271 | $projectDir = $this->getProjectDir(); 272 | 273 | if (null === $projectDir) { 274 | return null; 275 | } 276 | 277 | $configDir = rtrim($projectDir, '/').'/config/packages/'; 278 | 279 | if (is_dir($configDir)) { 280 | return $configDir; 281 | } 282 | 283 | return $this->getComposerDir('config-dir'); 284 | } 285 | 286 | /** 287 | * Gets the project root directory from the kernel. 288 | * 289 | * @return string|null The project directory path or null if not available 290 | */ 291 | private function getProjectDir(): ?string 292 | { 293 | $kernel = $this->getKernel(); 294 | 295 | if (null === $kernel) { 296 | return null; 297 | } 298 | 299 | $container = $kernel->getContainer(); 300 | 301 | $projectDir = $container->getParameter('kernel.project_dir'); 302 | 303 | return \is_string($projectDir) ? $projectDir : null; 304 | } 305 | 306 | /** 307 | * Gets a directory path from composer.json extra configuration. 308 | * 309 | * @param string $dir The directory key to look for in composer.json extra section 310 | * 311 | * @return string|null The directory path or null if not found 312 | */ 313 | private function getComposerDir(string $dir): ?string 314 | { 315 | $projectDir = $this->getProjectDir(); 316 | 317 | if (null === $projectDir) { 318 | return null; 319 | } 320 | 321 | $composerFilePath = $projectDir.'/composer.json'; 322 | 323 | if (!file_exists($composerFilePath)) { 324 | return null; 325 | } 326 | 327 | /** @var array{extra: array{string, string}} $composerConfig */ 328 | $composerConfig = json_decode(file_get_contents($composerFilePath) ?: '', true); 329 | 330 | return $composerConfig['extra'][$dir] ?? null; 331 | } 332 | 333 | /** 334 | * Gets the kernel instance from the application. 335 | * 336 | * @return KernelInterface|null The Symfony kernel or null if not available 337 | */ 338 | private function getKernel(): ?KernelInterface 339 | { 340 | $application = $this->getApplication(); 341 | 342 | if (!$application instanceof Application) { 343 | return null; 344 | } 345 | 346 | return $application->getKernel(); 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /Component/FlasherComponent.php: -------------------------------------------------------------------------------- 1 | 24 | * ``` 25 | */ 26 | final class FlasherComponent 27 | { 28 | /** 29 | * Filtering criteria for notifications. 30 | * 31 | * @var array 32 | */ 33 | public array $criteria = []; 34 | 35 | /** 36 | * Presentation format (e.g., 'html', 'json'). 37 | */ 38 | public string $presenter = 'html'; 39 | 40 | /** 41 | * Additional context for rendering. 42 | * 43 | * @var array 44 | */ 45 | public array $context = []; 46 | } 47 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/EventListenerCompilerPass.php: -------------------------------------------------------------------------------- 1 | findDefinition('flasher.event_dispatcher'); 33 | 34 | foreach (array_keys($container->findTaggedServiceIds('flasher.event_listener')) as $id) { 35 | $definition->addMethodCall('addListener', [new Reference($id)]); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/PresenterCompilerPass.php: -------------------------------------------------------------------------------- 1 | findDefinition('flasher.response_manager'); 34 | 35 | foreach ($container->findTaggedServiceIds('flasher.presenter') as $id => $tags) { 36 | foreach ($tags as $attributes) { 37 | $definition->addMethodCall('addPresenter', [ 38 | $attributes['alias'], 39 | new ServiceClosureArgument(new Reference($id)), 40 | ]); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | plugin->getName()); 21 | $rootNode = $treeBuilder->getRootNode(); 22 | 23 | $this->normalizeConfig($rootNode); 24 | 25 | $this->addGeneralSection($rootNode); 26 | $this->addFlashBagSection($rootNode); 27 | $this->addPresetsSection($rootNode); 28 | $this->addPluginsSection($rootNode); 29 | 30 | return $treeBuilder; 31 | } 32 | 33 | private function normalizeConfig(ArrayNodeDefinition $rootNode): void 34 | { 35 | $rootNode 36 | ->beforeNormalization() 37 | ->always(fn ($v): array => $this->plugin->normalizeConfig($v)) 38 | ->end(); 39 | } 40 | 41 | private function addGeneralSection(ArrayNodeDefinition $rootNode): void 42 | { 43 | $rootNode 44 | ->children() 45 | ->scalarNode('default') 46 | ->info('Default notification library (e.g., "flasher", "toastr", "noty", "notyf", "sweetalert")') 47 | ->isRequired() 48 | ->cannotBeEmpty() 49 | ->defaultValue($this->plugin->getDefault()) 50 | ->end() 51 | ->scalarNode('main_script') 52 | ->info('Path to the main PHPFlasher JavaScript file') 53 | ->defaultValue($this->plugin->getRootScript()) 54 | ->end() 55 | ->booleanNode('inject_assets') 56 | ->info('Automatically inject assets into HTML pages') 57 | ->defaultTrue() 58 | ->end() 59 | ->booleanNode('translate') 60 | ->info('Enable message translation') 61 | ->defaultTrue() 62 | ->end() 63 | ->arrayNode('excluded_paths') 64 | ->info('URL patterns to exclude from asset injection and flash_bag conversion') 65 | ->defaultValue([ 66 | '/^\/_profiler/', 67 | '/^\/_fragment/', 68 | ]) 69 | ->scalarPrototype()->end() 70 | ->end() 71 | ->arrayNode('filter') 72 | ->info('Criteria to filter notifications') 73 | ->variablePrototype()->end() 74 | ->end() 75 | ->arrayNode('scripts') 76 | ->info('Additional JavaScript files') 77 | ->performNoDeepMerging() 78 | ->scalarPrototype()->end() 79 | ->end() 80 | ->arrayNode('styles') 81 | ->info('CSS files to style notifications') 82 | ->performNoDeepMerging() 83 | ->scalarPrototype()->end() 84 | ->end() 85 | ->arrayNode('options') 86 | ->info('Global notification options') 87 | ->variablePrototype()->end() 88 | ->end() 89 | ->end(); 90 | } 91 | 92 | private function addFlashBagSection(ArrayNodeDefinition $rootNode): void 93 | { 94 | $rootNode 95 | ->children() 96 | ->variableNode('flash_bag') 97 | ->info('Map Symfony flash messages to notification types') 98 | ->defaultTrue() 99 | ->end() 100 | ->end(); 101 | } 102 | 103 | private function addPresetsSection(ArrayNodeDefinition $rootNode): void 104 | { 105 | $rootNode 106 | ->fixXmlConfig('preset') 107 | ->children() 108 | ->arrayNode('presets') 109 | ->info('Notification presets (optional)') 110 | ->useAttributeAsKey('name') 111 | ->arrayPrototype() 112 | ->children() 113 | ->scalarNode('type') 114 | ->info('Notification type (e.g., "success", "error")') 115 | ->end() 116 | ->scalarNode('title') 117 | ->info('Default title') 118 | ->end() 119 | ->scalarNode('message') 120 | ->info('Default message') 121 | ->end() 122 | ->arrayNode('options') 123 | ->info('Additional options') 124 | ->variablePrototype()->end() 125 | ->end() 126 | ->end() 127 | ->end() 128 | ->end() 129 | ->end(); 130 | } 131 | 132 | private function addPluginsSection(ArrayNodeDefinition $rootNode): void 133 | { 134 | $rootNode 135 | ->fixXmlConfig('plugin') 136 | ->children() 137 | ->arrayNode('plugins') 138 | ->info('Additional plugins') 139 | ->useAttributeAsKey('name') 140 | ->arrayPrototype() 141 | ->children() 142 | ->scalarNode('view') 143 | ->info('Custom twig view template') 144 | ->end() 145 | ->arrayNode('styles') 146 | ->info('CSS files for the plugin') 147 | ->performNoDeepMerging() 148 | ->scalarPrototype()->end() 149 | ->end() 150 | ->arrayNode('scripts') 151 | ->info('JavaScript files for the plugin') 152 | ->performNoDeepMerging() 153 | ->scalarPrototype()->end() 154 | ->end() 155 | ->arrayNode('options') 156 | ->info('Plugin-specific options') 157 | ->variablePrototype()->end() 158 | ->end() 159 | ->end() 160 | ->end() 161 | ->end() 162 | ->end(); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /DependencyInjection/FlasherExtension.php: -------------------------------------------------------------------------------- 1 | plugin->getName(); 28 | } 29 | 30 | /** 31 | * @param array $config 32 | */ 33 | public function getConfiguration(array $config, ContainerBuilder $container): ConfigurationInterface 34 | { 35 | return new Configuration($this->plugin); 36 | } 37 | 38 | /** 39 | * @param array{ 40 | * default: string, 41 | * main_script: string, 42 | * inject_assets: bool, 43 | * excluded_paths: list, 44 | * presets: array, 45 | * flash_bag: array, 46 | * filter: array, 47 | * plugins: array>, 48 | * } $config 49 | */ 50 | public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void 51 | { 52 | $this->registerFlasherParameters($config, $container, $builder); 53 | $this->registerServicesForAutoconfiguration($builder); 54 | 55 | $container->import(__DIR__.'/../Resources/config/services.php'); 56 | } 57 | 58 | public function process(ContainerBuilder $container): void 59 | { 60 | $this->registerFlasherTranslator($container); 61 | $this->configureSessionServices($container); 62 | $this->configureFlasherListener($container); 63 | } 64 | 65 | /** 66 | * @param array{ 67 | * default: string, 68 | * main_script: string, 69 | * inject_assets: bool, 70 | * excluded_paths: list, 71 | * presets: array, 72 | * flash_bag: array, 73 | * filter: array, 74 | * plugins: array>, 75 | * } $config 76 | */ 77 | private function registerFlasherParameters(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void 78 | { 79 | /** @var string $projectDir */ 80 | $projectDir = $builder->getParameter('kernel.project_dir'); 81 | $publicDir = $projectDir.\DIRECTORY_SEPARATOR.'public'; 82 | $assetsDir = $publicDir.\DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR.'flasher'; 83 | $manifestPath = $assetsDir.\DIRECTORY_SEPARATOR.'manifest.json'; 84 | 85 | $container->parameters() 86 | ->set('flasher', $config) 87 | ->set('flasher.public_dir', $publicDir) 88 | ->set('flasher.assets_dir', $assetsDir) 89 | ->set('flasher.json_manifest_path', $manifestPath) 90 | ->set('flasher.default', $config['default']) 91 | ->set('flasher.main_script', $config['main_script']) 92 | ->set('flasher.inject_assets', $config['inject_assets']) 93 | ->set('flasher.excluded_paths', $config['excluded_paths']) 94 | ->set('flasher.flash_bag', $config['flash_bag']) 95 | ->set('flasher.filter', $config['filter']) 96 | ->set('flasher.presets', $config['presets']) 97 | ->set('flasher.plugins', $config['plugins']) 98 | ; 99 | } 100 | 101 | private function registerServicesForAutoconfiguration(ContainerBuilder $builder): void 102 | { 103 | $builder->registerForAutoconfiguration(EventListenerInterface::class) 104 | ->addTag('flasher.event_listener'); 105 | 106 | $builder->registerAttributeForAutoconfiguration(AsFlasherFactory::class, static function (ChildDefinition $definition, AsFlasherFactory $attribute): void { 107 | $definition->addTag('flasher.factory', get_object_vars($attribute)); 108 | }); 109 | 110 | $builder->registerAttributeForAutoconfiguration(AsFlasherPresenter::class, static function (ChildDefinition $definition, AsFlasherPresenter $attribute): void { 111 | $definition->addTag('flasher.presenter', get_object_vars($attribute)); 112 | }); 113 | } 114 | 115 | private function registerFlasherTranslator(ContainerBuilder $container): void 116 | { 117 | if ($container->has('translator')) { 118 | return; 119 | } 120 | 121 | $container->removeDefinition('flasher.translator'); 122 | } 123 | 124 | private function configureSessionServices(ContainerBuilder $container): void 125 | { 126 | if (!$container->has('session.factory') || false === $container->getParameter('flasher.flash_bag')) { 127 | $container->removeDefinition('flasher.session_listener'); 128 | } 129 | 130 | if (!$container->has('session.factory')) { 131 | $container->removeDefinition('flasher.storage_bag'); 132 | $container->register('flasher.storage_bag', ArrayBag::class); 133 | } 134 | } 135 | 136 | private function configureFlasherListener(ContainerBuilder $container): void 137 | { 138 | if ($container->getParameter('flasher.inject_assets')) { 139 | return; 140 | } 141 | 142 | $container->removeDefinition('flasher.flasher_listener'); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /EventListener/FlasherListener.php: -------------------------------------------------------------------------------- 1 | getRequest()); 47 | $response = new Response($event->getResponse()); 48 | 49 | $this->responseExtension->render($request, $response); 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | * 55 | * Returns events this subscriber listens to and their corresponding handlers. 56 | * The low priority (-20) ensures this runs after most other response listeners. 57 | * 58 | * @return array> The events and handlers 59 | */ 60 | public static function getSubscribedEvents(): array 61 | { 62 | return [ 63 | ResponseEvent::class => ['onKernelResponse', -20], 64 | ]; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /EventListener/SessionListener.php: -------------------------------------------------------------------------------- 1 | getRequest()); 47 | $response = new Response($event->getResponse()); 48 | 49 | $this->requestExtension->flash($request, $response); 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | * 55 | * Returns events this subscriber listens to and their corresponding handlers. 56 | * 57 | * @return array> The events and handlers 58 | */ 59 | public static function getSubscribedEvents(): array 60 | { 61 | return [ 62 | ResponseEvent::class => ['onKernelResponse', 0], 63 | ]; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Factory/NotificationFactoryLocator.php: -------------------------------------------------------------------------------- 1 | $serviceLocator Symfony's service locator 28 | */ 29 | public function __construct(private ServiceLocator $serviceLocator) 30 | { 31 | } 32 | 33 | /** 34 | * {@inheritdoc} 35 | * 36 | * Checks if a notification factory with the given ID exists. 37 | * 38 | * @param string $id The factory identifier 39 | * 40 | * @return bool True if the factory exists, false otherwise 41 | */ 42 | public function has(string $id): bool 43 | { 44 | return $this->serviceLocator->has($id); 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | * 50 | * Gets a notification factory by ID. 51 | * 52 | * @param string $id The factory identifier 53 | * 54 | * @return NotificationFactoryInterface The notification factory 55 | */ 56 | public function get(string $id): NotificationFactoryInterface 57 | { 58 | return $this->serviceLocator->get($id); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /FlasherSymfonyBundle.php: -------------------------------------------------------------------------------- 1 | container instanceof ContainerInterface) { 38 | FlasherContainer::from($this->container); 39 | } 40 | } 41 | 42 | /** 43 | * Register compiler passes with the container. 44 | * 45 | * @param ContainerBuilder $container The container builder 46 | */ 47 | public function build(ContainerBuilder $container): void 48 | { 49 | $container->addCompilerPass(new EventListenerCompilerPass()); 50 | $container->addCompilerPass(new PresenterCompilerPass()); 51 | } 52 | 53 | /** 54 | * Get the container extension for this bundle. 55 | * 56 | * @return ExtensionInterface The bundle extension 57 | */ 58 | public function getContainerExtension(): ExtensionInterface 59 | { 60 | return new FlasherExtension($this->createPlugin()); 61 | } 62 | 63 | /** 64 | * Create the core PHPFlasher plugin. 65 | * 66 | * @return FlasherPlugin The core PHPFlasher plugin 67 | */ 68 | public function createPlugin(): FlasherPlugin 69 | { 70 | return new FlasherPlugin(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Http/Request.php: -------------------------------------------------------------------------------- 1 | request->getRequestUri(); 39 | } 40 | 41 | public function isXmlHttpRequest(): bool 42 | { 43 | return $this->request->isXmlHttpRequest(); 44 | } 45 | 46 | public function isHtmlRequestFormat(): bool 47 | { 48 | return 'html' === $this->request->getRequestFormat(); 49 | } 50 | 51 | public function hasSession(): bool 52 | { 53 | return $this->request->hasSession(); 54 | } 55 | 56 | public function isSessionStarted(): bool 57 | { 58 | $session = $this->getSession(); 59 | 60 | return $session?->isStarted() ?: false; 61 | } 62 | 63 | public function hasType(string $type): bool 64 | { 65 | if (!$this->hasSession() || !$this->isSessionStarted()) { 66 | return false; 67 | } 68 | 69 | $session = $this->getSession(); 70 | if (!$session instanceof FlashBagAwareSessionInterface) { 71 | return false; 72 | } 73 | 74 | return $session->getFlashBag()->has($type); 75 | } 76 | 77 | /** 78 | * @return string[] 79 | */ 80 | public function getType(string $type): array 81 | { 82 | $session = $this->getSession(); 83 | if (!$session instanceof FlashBagAwareSessionInterface) { 84 | return []; 85 | } 86 | 87 | return $session->getFlashBag()->get($type); 88 | } 89 | 90 | public function forgetType(string $type): void 91 | { 92 | $this->getType($type); 93 | } 94 | 95 | /** 96 | * Gets the session from the request, with graceful handling of missing sessions. 97 | * 98 | * @return SessionInterface|null The session or null if not available 99 | */ 100 | private function getSession(): ?SessionInterface 101 | { 102 | try { 103 | return $this->request->getSession(); 104 | } catch (SessionNotFoundException) { 105 | return null; 106 | } 107 | } 108 | 109 | public function hasHeader(string $key): bool 110 | { 111 | return $this->request->headers->has($key); 112 | } 113 | 114 | public function getHeader(string $key): ?string 115 | { 116 | return $this->request->headers->get($key); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Http/Response.php: -------------------------------------------------------------------------------- 1 | response->isRedirection(); 37 | } 38 | 39 | public function isJson(): bool 40 | { 41 | return $this->response instanceof JsonResponse; 42 | } 43 | 44 | public function isHtml(): bool 45 | { 46 | $contentType = $this->response->headers->get('Content-Type'); 47 | 48 | if (!\is_string($contentType)) { 49 | return false; 50 | } 51 | 52 | return false !== stripos($contentType, 'html'); 53 | } 54 | 55 | public function isAttachment(): bool 56 | { 57 | $contentDisposition = $this->response->headers->get('Content-Disposition', ''); 58 | 59 | if (!$contentDisposition) { 60 | return false; 61 | } 62 | 63 | return false !== stripos($contentDisposition, 'attachment;'); 64 | } 65 | 66 | public function isSuccessful(): bool 67 | { 68 | return $this->response->isSuccessful(); 69 | } 70 | 71 | public function getContent(): string 72 | { 73 | return $this->response->getContent() ?: ''; 74 | } 75 | 76 | public function setContent(string $content): void 77 | { 78 | $this->response->setContent($content); 79 | } 80 | 81 | public function hasHeader(string $key): bool 82 | { 83 | return $this->response->headers->has($key); 84 | } 85 | 86 | public function getHeader(string $key): ?string 87 | { 88 | return $this->response->headers->get($key); 89 | } 90 | 91 | public function setHeader(string $key, array|string|null $values): void 92 | { 93 | $this->response->headers->set($key, $values); 94 | } 95 | 96 | public function removeHeader(string $key): void 97 | { 98 | $this->response->headers->remove($key); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 PHPFlasher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Profiler/FlasherDataCollector.php: -------------------------------------------------------------------------------- 1 | , 34 | * metadata: array, 35 | * } 36 | * @phpstan-type ConfigShare array{ 37 | * default: string, 38 | * main_script: string, 39 | * inject_assets: bool, 40 | * excluded_paths: list, 41 | * presets: array, 42 | * flash_bag: array, 43 | * filter: array{limit?: int|null}, 44 | * plugins: array>, 45 | * } 46 | * @phpstan-type DataShape array{ 47 | * displayed_envelopes: NotificationShape[], 48 | * dispatched_envelopes: NotificationShape[], 49 | * config: array, 50 | * versions: array{ 51 | * php_flasher: string, 52 | * php: string, 53 | * symfony: string 54 | * } 55 | * } 56 | * 57 | * @property DataShape|Data $data 58 | * 59 | * @internal 60 | */ 61 | #[\AllowDynamicProperties] 62 | final class FlasherDataCollector extends AbstractDataCollector implements LateDataCollectorInterface 63 | { 64 | /** 65 | * Creates a new FlasherDataCollector instance. 66 | * 67 | * @param NotificationLoggerListener $logger The notification logger for accessing dispatched notifications 68 | * @param array $config The PHPFlasher configuration 69 | * 70 | * @phpstan-param ConfigShare $config 71 | */ 72 | public function __construct( 73 | private readonly NotificationLoggerListener $logger, 74 | private readonly array $config, 75 | ) { 76 | } 77 | 78 | /** 79 | * Initial data collection - called during request processing. 80 | * 81 | * This implementation doesn't collect data here, deferring to lateCollect. 82 | * 83 | * @param Request $request The request object 84 | * @param Response $response The response object 85 | * @param \Throwable|null $exception Any exception that occurred 86 | */ 87 | public function collect(Request $request, Response $response, ?\Throwable $exception = null): void 88 | { 89 | // No action needed here since we're collecting data in lateCollect 90 | } 91 | 92 | /** 93 | * Late data collection - called after response is sent. 94 | * 95 | * Collects information about notifications, configuration, and versions. 96 | */ 97 | public function lateCollect(): void 98 | { 99 | $this->data = [ 100 | 'displayed_envelopes' => array_map(fn (Envelope $envelope) => $envelope->toArray(), $this->logger->getDisplayedEnvelopes()->getEnvelopes()), 101 | 'dispatched_envelopes' => array_map(fn (Envelope $envelope) => $envelope->toArray(), $this->logger->getDispatchedEnvelopes()->getEnvelopes()), 102 | 'config' => $this->config, 103 | 'versions' => [ 104 | 'php_flasher' => Flasher::VERSION, 105 | 'php' => \PHP_VERSION, 106 | 'symfony' => Kernel::VERSION, 107 | ], 108 | ]; 109 | 110 | $this->data = $this->cloneVar($this->data); 111 | } 112 | 113 | /** 114 | * Gets the collector data. 115 | * 116 | * @return DataShape|Data 117 | */ 118 | public function getData(): array|Data 119 | { 120 | return $this->data; 121 | } 122 | 123 | /** 124 | * Gets the collector name for the profiler panel. 125 | * 126 | * @return string The collector name 127 | */ 128 | public function getName(): string 129 | { 130 | return 'flasher'; 131 | } 132 | 133 | /** 134 | * Resets the collector between requests when using kernel.reset. 135 | */ 136 | public function reset(): void 137 | { 138 | $this->logger->reset(); 139 | parent::reset(); 140 | } 141 | 142 | /** 143 | * Gets the displayed notification envelopes. 144 | * 145 | * @return NotificationShape[]|Data 146 | */ 147 | public function getDisplayedEnvelopes(): array|Data 148 | { 149 | return $this->data['displayed_envelopes'] ?? []; 150 | } 151 | 152 | /** 153 | * Gets the dispatched notification envelopes. 154 | * 155 | * @return NotificationShape[]|Data 156 | */ 157 | public function getDispatchedEnvelopes(): array|Data 158 | { 159 | return $this->data['dispatched_envelopes'] ?? []; 160 | } 161 | 162 | /** 163 | * Gets the PHPFlasher configuration. 164 | * 165 | * @phpstan-return ConfigShare|Data 166 | */ 167 | public function getConfig(): array|Data 168 | { 169 | return $this->data['config'] ?? []; 170 | } 171 | 172 | /** 173 | * Gets version information. 174 | * 175 | * @return array{php_flasher: string, php: string, symfony: string}|Data 176 | */ 177 | public function getVersions(): array|Data 178 | { 179 | return $this->data['versions'] ?? []; 180 | } 181 | 182 | /** 183 | * Gets the template path for the profiler panel. 184 | * 185 | * @return string The template path 186 | */ 187 | public static function getTemplate(): string 188 | { 189 | return '@FlasherSymfony/profiler/flasher.html.twig'; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | Help Palestine 4 | 5 |
6 | 7 |

8 | 9 | 10 | PHPFlasher Logo 11 | 12 |

13 | 14 |

15 | Author Badge 16 | Source Code Badge 17 | GitHub Release Badge 18 | License Badge 19 | Packagist Downloads Badge 20 | GitHub Stars Badge 21 | Supported PHP Version Badge 22 |

23 | 24 | ## Table of Contents 25 | 26 | - [About PHPFlasher Symfony Adapter](#about-phpflasher-symfony-adapter) 27 | - [Features](#features) 28 | - [Supported Versions](#supported-versions) 29 | - [Installation](#installation) 30 | - [Core Package](#core-package) 31 | - [Adapters](#adapters) 32 | - [Configuration](#configuration) 33 | - [Configuration File](#configuration-file) 34 | - [Configuration Options](#configuration-options) 35 | - [Quick Start](#quick-start) 36 | - [Usage Examples](#usage-examples) 37 | - [Adapters Overview](#adapters-overview) 38 | - [Official Documentation](#official-documentation) 39 | - [Contributors and Sponsors](#contributors-and-sponsors) 40 | - [Contact](#contact) 41 | - [License](#license) 42 | 43 | ## About PHPFlasher Symfony Adapter 44 | 45 | **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. 46 | 47 | 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. 48 | 49 | ## Features 50 | 51 | - **Seamless Symfony Integration**: Designed specifically for Symfony, ensuring compatibility and ease of use. 52 | - **Multiple Notification Libraries**: Supports various frontend libraries like Toastr, Noty, SweetAlert, and Notyf. 53 | - **Flexible Configuration**: Customize the appearance and behavior of flash messages to fit your application's needs. 54 | - **Intuitive API**: Simple methods to create and manage flash messages without boilerplate code. 55 | - **Extensible**: Easily add or create new adapters for different frontend libraries. 56 | 57 | ## Supported Versions 58 | 59 | | PHPFlasher Symfony Adapter Version | PHP Version | Symfony Version | 60 | |------------------------------------|-------------|-----------------| 61 | | **v2.x** | ≥ 8.2 | ≥ 7.0 | 62 | | **v1.x** | ≥ 5.3 | ≥ 2.0 | 63 | 64 | > **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). 65 | 66 | ## Installation 67 | 68 | ### Core Package 69 | 70 | Install the PHPFlasher Symfony Adapter via Composer: 71 | 72 | ```bash 73 | composer require php-flasher/flasher-symfony 74 | ``` 75 | 76 | After installation, set up the necessary assets: 77 | 78 | ```shell 79 | php bin/console flasher:install 80 | ``` 81 | 82 | > **Note:** PHPFlasher automatically injects the necessary JavaScript and CSS assets into your Blade templates. No additional steps are required for asset injection. 83 | 84 | ### Adapters 85 | 86 | PHPFlasher provides various adapters for different notification libraries. Below is an overview of available adapters for Symfony: 87 | 88 | - [flasher-toastr-symfony](https://github.com/php-flasher/flasher-toastr-symfony) - Symfony Adapter 89 | - [flasher-noty-symfony](https://github.com/php-flasher/flasher-noty-symfony) - Symfony Adapter 90 | - [flasher-notyf-symfony](https://github.com/php-flasher/flasher-notyf-symfony) - Symfony Adapter 91 | - [flasher-sweetalert-symfony](https://github.com/php-flasher/flasher-sweetalert-symfony) - Symfony Adapter 92 | 93 | For detailed installation and usage instructions for each adapter, refer to their respective `README.md`. 94 | 95 | ## Configuration 96 | 97 | After installing the PHPFlasher Symfony Adapter, you can configure it by publishing the configuration file or by modifying it directly. 98 | 99 | ### Configuration File 100 | 101 | If you need to customize the default settings, publish the configuration file using the following command: 102 | 103 | ```bash 104 | php bin/console flasher:install --config 105 | ``` 106 | 107 | This will create a file at `config/packages/flasher.yaml` with the following content: 108 | 109 | ```yaml 110 | flasher: 111 | # Default notification library (e.g., 'flasher', 'toastr', 'noty', 'notyf', 'sweetalert') 112 | default: flasher 113 | 114 | # Path to the main PHPFlasher JavaScript file 115 | main_script: '/vendor/flasher/flasher.min.js' 116 | 117 | # List of CSS files to style your notifications 118 | styles: 119 | - '/vendor/flasher/flasher.min.css' 120 | 121 | # Set global options for all notifications (optional) 122 | # options: 123 | # # Time in milliseconds before the notification disappears 124 | # timeout: 5000 125 | # # Where the notification appears on the screen 126 | # position: 'top-right' 127 | 128 | # Automatically inject JavaScript and CSS assets into your HTML pages 129 | inject_assets: true 130 | 131 | # Enable message translation using Symfony's translation service 132 | translate: true 133 | 134 | # URL patterns to exclude from asset injection and flash_bag conversion 135 | excluded_paths: 136 | - '/^\/_profiler/' 137 | - '/^\/_fragment/' 138 | 139 | # Map Symfony flash message keys to notification types 140 | flash_bag: 141 | success: ['success'] 142 | error: ['error', 'danger'] 143 | warning: ['warning', 'alarm'] 144 | info: ['info', 'notice', 'alert'] 145 | 146 | # Set criteria to filter which notifications are displayed (optional) 147 | # filter: 148 | # # Maximum number of notifications to show at once 149 | # limit: 5 150 | 151 | # Define notification presets to simplify notification creation (optional) 152 | # presets: 153 | # # Example preset: 154 | # entity_saved: 155 | # type: 'success' 156 | # title: 'Entity saved' 157 | # message: 'Entity saved successfully' 158 | ``` 159 | 160 | ### Configuration Options 161 | 162 | | **Option** | **Description** | 163 | |------------------|---------------------------------------------------------------------------------------------------------------------------| 164 | | `default` | **String**: The default notification library to use (e.g., `'flasher'`, `'toastr'`, `'noty'`, `'notyf'`, `'sweetalert'`). | 165 | | `main_script` | **String**: Path to the main PHPFlasher JavaScript file. | 166 | | `styles` | **Array**: List of CSS files to style your notifications. | 167 | | `options` | **Array** (Optional): Global options for all notifications (e.g., `'timeout'`, `'position'`). | 168 | | `inject_assets` | **Boolean**: Whether to automatically inject JavaScript and CSS assets into your HTML pages. | 169 | | `translate` | **Boolean**: Enable message translation using Symfony’s translation service. | 170 | | `excluded_paths` | **Array**: URL patterns to exclude from asset injection and flash_bag conversion. | 171 | | `flash_bag` | **Array**: Map Symfony flash message keys to notification types. | 172 | | `filter` | **Array** (Optional): Criteria to filter which notifications are displayed (e.g., `'limit'`). | 173 | | `presets` | **Array** (Optional): Define notification presets to simplify notification creation. | 174 | 175 | ## Quick Start 176 | 177 | 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. 178 | 179 | ### Using the `flash()` Helper 180 | 181 | ```php 182 | redirectToRoute('book_list'); 198 | } 199 | } 200 | ``` 201 | 202 | ### Using the `flasher` Service 203 | 204 | ```php 205 | flasher = $flasher; 220 | } 221 | 222 | public function register(): RedirectResponse 223 | { 224 | // Your logic here 225 | 226 | $this->flasher->success('Your changes have been saved!'); 227 | 228 | // ... redirect or render the view 229 | return $this->redirectToRoute('home'); 230 | } 231 | 232 | public function update(): RedirectResponse 233 | { 234 | // Your logic here 235 | 236 | $this->flasher->error('An error occurred while updating.'); 237 | 238 | return $this->redirectToRoute('update_page'); 239 | } 240 | } 241 | ``` 242 | 243 | ## Usage Examples 244 | 245 | ### Success Message 246 | 247 | ```php 248 | flash()->success('Operation completed successfully!'); 249 | ``` 250 | 251 | ### Error Message 252 | 253 | ```php 254 | flash()->error('An error occurred.'); 255 | ``` 256 | 257 | ### Info Message 258 | 259 | ```php 260 | flash()->info('This is an informational message.'); 261 | ``` 262 | 263 | ### Warning Message 264 | 265 | ```php 266 | flash()->warning('This is a warning message.'); 267 | ``` 268 | 269 | ### Passing Options 270 | 271 | ```php 272 | flash()->success('Custom message with options.', ['timeout' => 3000, 'position' => 'bottom-left']); 273 | ``` 274 | 275 | ### Using presets 276 | 277 | Define a preset in your `config/packages/flasher.yaml`: 278 | 279 | ```yaml 280 | flasher: 281 | # ... other configurations 282 | 283 | presets: 284 | entity_saved: 285 | type: 'success' 286 | title: 'Entity Saved' 287 | message: 'The entity has been saved successfully.' 288 | entity_deleted: 289 | type: 'warning' 290 | title: 'Entity Deleted' 291 | message: 'The entity has been deleted.' 292 | ``` 293 | 294 | Use the preset in your controller: 295 | 296 | ```php 297 | preset('entity_saved'); 311 | 312 | return $this->redirectToRoute('books.index'); 313 | } 314 | 315 | public function delete(): RedirectResponse 316 | { 317 | // Your deletion logic 318 | 319 | flash()->preset('entity_deleted'); 320 | 321 | return $this->redirectToRoute('books.index'); 322 | } 323 | } 324 | ``` 325 | 326 | ## Adapters Overview 327 | 328 | PHPFlasher supports various adapters to integrate seamlessly with different frontend libraries. Below is an overview of available adapters for Symfony: 329 | 330 | | Adapter Repository | Description | 331 | |-----------------------------------------------------------------------------------------|--------------------------------| 332 | | [flasher-symfony](https://github.com/php-flasher/flasher-symfony) | Symfony framework adapter | 333 | | [flasher-toastr-symfony](https://github.com/php-flasher/flasher-toastr-symfony) | Toastr adapter for Symfony | 334 | | [flasher-noty-symfony](https://github.com/php-flasher/flasher-noty-symfony) | Noty adapter for Symfony | 335 | | [flasher-notyf-symfony](https://github.com/php-flasher/flasher-notyf-symfony) | Notyf adapter for Symfony | 336 | | [flasher-sweetalert-symfony](https://github.com/php-flasher/flasher-sweetalert-symfony) | SweetAlert adapter for Symfony | 337 | 338 | > **Note:** Each adapter has its own repository. For detailed installation and usage instructions, please refer to the [Official Documentation](https://php-flasher.io). 339 | 340 | ## Official Documentation 341 | 342 | 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. 343 | 344 | ## Contributors and sponsors 345 | 346 | Join our team of contributors and make a lasting impact on our project! 347 | 348 | We are always looking for passionate individuals who want to contribute their skills and ideas. 349 | Whether you're a developer, designer, or simply have a great idea, we welcome your participation and collaboration. 350 | 351 | Shining stars of our community: 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 |
Younes ENNAJI
Younes ENNAJI

💻 📖 🚧
Salma Mourad
Salma Mourad

💵
Nashwan Abdullah
Nashwan Abdullah

💵
Arvid de Jong
Arvid de Jong

💵
Ash Allen
Ash Allen

🎨
Tony Murray
Tony Murray

💻
Stéphane P
Stéphane P

📖
Lucas Maciel
Lucas Maciel

🎨
Ahmed Gamal
Ahmed Gamal

💻 📖
373 | 374 | 375 | 376 | 377 | 378 | 379 | ## Contact 380 | 381 | PHPFlasher is being actively developed by yoeunes. 382 | You can reach out with questions, bug reports, or feature requests on any of the following: 383 | 384 | - [Github Issues](https://github.com/php-flasher/php-flasher/issues) 385 | - [Github](https://github.com/yoeunes) 386 | - [Twitter](https://twitter.com/yoeunes) 387 | - [Linkedin](https://www.linkedin.com/in/younes--ennaji/) 388 | - [Email me directly](mailto:younes.ennaji.pro@gmail.com) 389 | 390 | ## License 391 | 392 | PHPFlasher is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). 393 | 394 |

Made with ❤️ by Younes ENNAJI

395 | -------------------------------------------------------------------------------- /Resources/config/config.yaml: -------------------------------------------------------------------------------- 1 | flasher: 2 | # Default notification library (e.g., 'flasher', 'toastr', 'noty', 'notyf', 'sweetalert') 3 | default: flasher 4 | 5 | # Path to the main PHPFlasher JavaScript file 6 | main_script: '/vendor/flasher/flasher.min.js' 7 | 8 | # List of CSS files to style your notifications 9 | styles: 10 | - '/vendor/flasher/flasher.min.css' 11 | 12 | # Set global options for all notifications (optional) 13 | # options: 14 | # # Time in milliseconds before the notification disappears 15 | # timeout: 5000 16 | # # Where the notification appears on the screen 17 | # position: 'top-right' 18 | 19 | # Automatically inject JavaScript and CSS assets into your HTML pages 20 | inject_assets: true 21 | 22 | # Enable message translation using Symfony's translation service 23 | translate: true 24 | 25 | # URL patterns to exclude from asset injection and flash_bag conversion 26 | excluded_paths: 27 | - '/^\/_profiler/' 28 | - '/^\/_fragment/' 29 | 30 | # Map Symfony flash message keys to notification types 31 | flash_bag: 32 | success: ['success'] 33 | error: ['error', 'danger'] 34 | warning: ['warning', 'alarm'] 35 | info: ['info', 'notice', 'alert'] 36 | 37 | # Set criteria to filter which notifications are displayed (optional) 38 | # filter: 39 | # # Maximum number of notifications to show at once 40 | # limit: 5 41 | 42 | # Define notification presets to simplify notification creation (optional) 43 | # presets: 44 | # # Example preset: 45 | # entity_saved: 46 | # type: 'success' 47 | # title: 'Entity saved' 48 | # message: 'Entity saved successfully' 49 | -------------------------------------------------------------------------------- /Resources/config/services.php: -------------------------------------------------------------------------------- 1 | services() 41 | ->set('flasher', Flasher::class) 42 | ->public() 43 | ->args([ 44 | param('flasher.default'), 45 | inline_service(NotificationFactoryLocator::class) 46 | ->args([tagged_locator('flasher.factory', indexAttribute: 'alias')]), 47 | service('flasher.response_manager'), 48 | service('flasher.storage_manager'), 49 | ]) 50 | ->alias(FlasherInterface::class, 'flasher') 51 | 52 | ->set('flasher.flasher_listener', FlasherListener::class) 53 | ->args([ 54 | inline_service(ResponseExtension::class) 55 | ->args([ 56 | service('flasher'), 57 | service('flasher.csp_handler'), 58 | param('flasher.excluded_paths'), 59 | ]), 60 | ]) 61 | ->tag('kernel.event_subscriber') 62 | 63 | ->set('flasher.twig_extension', FlasherTwigExtension::class) 64 | ->args([service('flasher')]) 65 | ->tag('twig.extension') 66 | 67 | ->set('flasher.session_listener', SessionListener::class) 68 | ->args([ 69 | inline_service(RequestExtension::class) 70 | ->args([ 71 | service('flasher'), 72 | param('flasher.flash_bag'), 73 | ]), 74 | ]) 75 | ->tag('kernel.event_subscriber') 76 | 77 | ->set('flasher.notification_logger_listener', NotificationLoggerListener::class) 78 | ->tag('flasher.event_listener') 79 | ->tag('kernel.reset', ['method' => 'reset']) 80 | 81 | ->set('flasher.translation_listener', TranslationListener::class) 82 | ->args([service('flasher.translator')->nullOnInvalid()]) 83 | ->tag('flasher.event_listener') 84 | 85 | ->set('flasher.preset_listener', ApplyPresetListener::class) 86 | ->args([param('flasher.presets')]) 87 | ->tag('flasher.event_listener') 88 | 89 | ->set('flasher.install_command', InstallCommand::class) 90 | ->args([service('flasher.asset_manager')]) 91 | ->tag('console.command') 92 | 93 | ->set('flasher.flasher_component', FlasherComponent::class) 94 | ->tag('twig.component', [ 95 | 'key' => 'flasher', 96 | 'template' => '@FlasherSymfony/components/flasher.html.twig', 97 | 'attributesVar' => 'attributes', 98 | ]) 99 | 100 | ->set('flasher.notification_factory', NotificationFactory::class) 101 | ->abstract() 102 | ->args([service('flasher.storage_manager')]) 103 | 104 | ->set('flasher.storage', Storage::class) 105 | ->args([service('flasher.storage_bag')]) 106 | 107 | ->set('flasher.storage_bag', SessionBag::class) 108 | ->args([service('request_stack')]) 109 | 110 | ->set('flasher.event_dispatcher', EventDispatcher::class) 111 | 112 | ->set('flasher.filter_factory', FilterFactory::class) 113 | 114 | ->set('flasher.storage_manager', StorageManager::class) 115 | ->args([ 116 | service('flasher.storage'), 117 | service('flasher.event_dispatcher'), 118 | service('flasher.filter_factory'), 119 | param('flasher.filter'), 120 | ]) 121 | 122 | ->set('flasher.template_engine', TwigTemplateEngine::class) 123 | ->args([service('twig')->nullOnInvalid()]) 124 | 125 | ->set('flasher.resource_manager', ResourceManager::class) 126 | ->args([ 127 | service('flasher.template_engine'), 128 | service('flasher.asset_manager'), 129 | param('flasher.main_script'), 130 | param('flasher.plugins'), 131 | ]) 132 | 133 | ->set('flasher.response_manager', ResponseManager::class) 134 | ->args([ 135 | service('flasher.resource_manager'), 136 | service('flasher.storage_manager'), 137 | service('flasher.event_dispatcher'), 138 | ]) 139 | 140 | ->set('flasher.translator', Translator::class) 141 | ->args([service('translator')->nullOnInvalid()]) 142 | 143 | ->set('flasher.csp_handler', ContentSecurityPolicyHandler::class) 144 | ->args([inline_service(NonceGenerator::class)]) 145 | 146 | ->set('flasher.asset_manager', AssetManager::class) 147 | ->args([ 148 | param('flasher.public_dir'), 149 | param('flasher.json_manifest_path'), 150 | ]) 151 | 152 | ->set('flasher.data_collector', FlasherDataCollector::class) 153 | ->args([ 154 | service('flasher.notification_logger_listener'), 155 | param('flasher'), 156 | ]) 157 | ->tag('data_collector', ['id' => 'flasher', 'template' => '@FlasherSymfony/profiler/flasher.html.twig', 'priority' => 0]) 158 | ; 159 | }; 160 | -------------------------------------------------------------------------------- /Resources/translations/flasher.ar.php: -------------------------------------------------------------------------------- 1 | 20 | 27 |
28 | 29 |
30 | 31 | -------------------------------------------------------------------------------- /Resources/views/components/flasher.html.twig: -------------------------------------------------------------------------------- 1 |
2 | {{ flasher_render() }} 3 |
4 | -------------------------------------------------------------------------------- /Resources/views/profiler/_notifications_table.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {% for envelope in envelopes %} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 27 | 28 | {% endfor %} 29 | 30 |
#PluginTypeTitleMessageOptions
{{ loop.index }}{{ envelope.metadata.plugin }}{{ envelope.type }}{{ envelope.title }}{{ envelope.message }} 21 | {% if envelope.options is not empty %} 22 | {{ profiler_dump(envelope.options) }} 23 | {% else %} 24 | No Options 25 | {% endif %} 26 |
31 | -------------------------------------------------------------------------------- /Resources/views/profiler/flasher.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@WebProfiler/Profiler/layout.html.twig' %} 2 | 3 | {% macro logo() %} 4 | 5 | PHPFlasher 18 | 19 | {% endmacro %} 20 | 21 | {% block toolbar %} 22 | {% import _self as macros %} 23 | 24 | {% set displayedEnvelopes = collector.displayedEnvelopes %} 25 | {% set dispatchedEnvelopes = collector.dispatchedEnvelopes %} 26 | 27 | {% set totalDispatched = dispatchedEnvelopes|length %} 28 | {% set totalDisplayed = displayedEnvelopes|length %} 29 | 30 | {% if totalDisplayed > 0 %} 31 | {# Initialize type counts #} 32 | {% set typeCounts = {} %} 33 | {% for envelope in displayedEnvelopes %} 34 | {% set type = envelope.type|default('info')|lower %} 35 | {% set typeCounts = typeCounts | merge({ (type): (typeCounts[type]|default(0) + 1) }) %} 36 | {% endfor %} 37 | 38 | {% set icon %} 39 | {{ macros.logo() }} 40 | 41 | {{ source('@FlasherSymfony/profiler/flasher.svg') }} 42 | 43 | {% if totalDisplayed == totalDispatched %} 44 | {{ totalDisplayed }} 45 | {% else %} 46 | {{ totalDisplayed }}/{{ totalDispatched }} 47 | {% endif %} 48 | 49 | {% endset %} 50 | 51 | {% set text %} 52 |
53 | Notifications Displayed 54 | {{ totalDisplayed }} 55 |
56 | 57 | {% if totalDispatched != totalDisplayed %} 58 |
59 | Notifications Dispatched: 60 | {{ totalDispatched }} 61 |
62 | {% endif %} 63 | 64 | {% if totalDisplayed > totalDispatched %} 65 |
66 | Note: Some notifications are from previous requests. 67 |
68 | {% endif %} 69 | 70 | {% for type, count in typeCounts %} 71 |
72 | {{ type|capitalize }} 73 | {{ count }} 74 |
75 | {% endfor %} 76 | {% endset %} 77 | 78 | {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url }) }} 79 | {% endif %} 80 | {% endblock %} 81 | 82 | {% block menu %} 83 | {% import _self as macros %} 84 | 85 | {% set totalDisplayed = collector.displayedEnvelopes|length %} 86 | {% set totalDispatched = collector.dispatchedEnvelopes|length %} 87 | 88 | 89 | {{ source('@FlasherSymfony/profiler/flasher.svg') }} 90 | {{ macros.logo() }} 91 | {% if totalDisplayed > 0 %} 92 | 93 | {% if totalDisplayed == totalDispatched %} 94 | {{ totalDisplayed }} 95 | {% else %} 96 | {{ totalDisplayed }}/{{ totalDispatched }} 97 | {% endif %} 98 | 99 | {% endif %} 100 | 101 | {% endblock %} 102 | 103 | {% block panel %} 104 | {% set displayedEnvelopes = collector.displayedEnvelopes %} 105 | {% set dispatchedEnvelopes = collector.dispatchedEnvelopes %} 106 | {% set totalNotifications = dispatchedEnvelopes|length %} 107 | {% set displayedNotifications = displayedEnvelopes|length %} 108 | {% set config = collector.config %} 109 | {% set versions = collector.versions %} 110 | 111 |

PHPFlasher Notifications

112 | 113 | {% if totalNotifications == 0 %} 114 |
115 |

No notifications have been dispatched.

116 |
117 | {% else %} 118 |
119 |
120 |

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

121 |
122 | {% if displayedNotifications > totalNotifications %} 123 |
124 |

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.

125 |
126 | {% endif %} 127 | 128 |

Displayed Notifications

129 | {{ include('@FlasherSymfony/profiler/_notifications_table.html.twig', { 'envelopes': displayedEnvelopes }) }} 130 | 131 | {% if totalNotifications > displayedNotifications %} 132 |

Remaining Notifications

133 | {% set remainingNotifications = dispatchedEnvelopes|slice(displayedNotifications) %} 134 | {{ include('@FlasherSymfony/profiler/_notifications_table.html.twig', { 'envelopes': remainingNotifications }) }} 135 | {% endif %} 136 |
137 |
138 | 139 |
140 |

Debug

141 |
142 |

Version Information

143 |
    144 |
  • PHPFlasher Version: {{ versions.php_flasher }}
  • 145 |
  • PHP Version: {{ versions.php }}
  • 146 |
  • Symfony Version: {{ versions.symfony }}
  • 147 |
148 | 149 |

Configuration

150 | {{ profiler_dump(config, maxDepth=10) }} 151 |
152 |
153 |
154 | {% endif %} 155 | {% endblock %} 156 | 157 | {% block head %} 158 | {{ parent() }} 159 | 184 | {% endblock %} 185 | -------------------------------------------------------------------------------- /Resources/views/profiler/flasher.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resources/views/tailwindcss.html.twig: -------------------------------------------------------------------------------- 1 | {% if 'success' == envelope.type %} 2 | {% set title = 'Success' %} 3 | {% set text_color = 'text-green-600' %} 4 | {% set ring_color = 'ring-green-300' %} 5 | {% set background_color = 'bg-green-600' %} 6 | {% set progress_background_color = 'bg-green-100' %} 7 | {% set border_color = 'border-green-600' %} 8 | {% set icon = '' %} 9 | {% elseif 'error' == envelope.type %} 10 | {% set title = 'Error' %} 11 | {% set text_color = 'text-red-600' %} 12 | {% set ring_color = 'ring-red-300' %} 13 | {% set background_color = 'bg-red-600' %} 14 | {% set progress_background_color = 'bg-red-100' %} 15 | {% set border_color = 'border-red-600' %} 16 | {% set icon = '' %} 17 | {% elseif 'warning' == envelope.type %} 18 | {% set title = 'Warning' %} 19 | {% set text_color = 'text-yellow-600' %} 20 | {% set ring_color = 'ring-yellow-300' %} 21 | {% set background_color = 'bg-yellow-600' %} 22 | {% set progress_background_color = 'bg-yellow-100' %} 23 | {% set border_color = 'border-yellow-600' %} 24 | {% set icon = '' %} 25 | {% else %} 26 | {% set title = 'Info' %} 27 | {% set text_color = 'text-blue-600' %} 28 | {% set ring_color = 'ring-blue-300' %} 29 | {% set background_color = 'bg-blue-600' %} 30 | {% set progress_background_color = 'bg-blue-100' %} 31 | {% set border_color = 'border-blue-600' %} 32 | {% set icon = '' %} 33 | {% endif %} 34 | 35 |
36 |
37 |
38 | {{ icon | raw }} 39 |
40 |
41 |

42 | {{ title | trans }} 43 |

44 |

45 | {{ envelope.message }} 46 |

47 |
48 |
49 |
50 | 51 |
52 |
53 | -------------------------------------------------------------------------------- /Resources/views/tailwindcss_bg.html.twig: -------------------------------------------------------------------------------- 1 | {% if 'success' == envelope.type %} 2 | {% set title = 'Success' %} 3 | {% set text_color = 'text-green-700' %} 4 | {% set background_color = 'bg-green-50' %} 5 | {% set progress_background_color = 'bg-green-200' %} 6 | {% set border_color = 'border-green-600' %} 7 | {% set icon = '' %} 8 | {% elseif 'error' == envelope.type %} 9 | {% set title = 'Error' %} 10 | {% set text_color = 'text-red-700' %} 11 | {% set background_color = 'bg-red-50' %} 12 | {% set progress_background_color = 'bg-red-200' %} 13 | {% set border_color = 'border-red-600' %} 14 | {% set icon = '' %} 15 | {% elseif 'warning' == envelope.type %} 16 | {% set title = 'Warning' %} 17 | {% set text_color = 'text-yellow-700' %} 18 | {% set background_color = 'bg-yellow-50' %} 19 | {% set progress_background_color = 'bg-yellow-200' %} 20 | {% set border_color = 'border-yellow-600' %} 21 | {% set icon = '' %} 22 | {% else %} 23 | {% set title = 'Info' %} 24 | {% set text_color = 'text-blue-700' %} 25 | {% set background_color = 'bg-blue-50' %} 26 | {% set progress_background_color = 'bg-blue-200' %} 27 | {% set border_color = 'border-blue-600' %} 28 | {% set icon = '' %} 29 | {% endif %} 30 | 31 |
32 |
33 |
34 | {{ icon | raw }} 35 |
36 |
37 |

38 | {{ title | trans }} 39 |

40 |

41 | {{ envelope.message }} 42 |

43 |
44 |
45 |
46 | 47 |
48 |
49 | -------------------------------------------------------------------------------- /Resources/views/tailwindcss_r.html.twig: -------------------------------------------------------------------------------- 1 | {% if 'success' == envelope.type %} 2 | {% set title = 'Success' %} 3 | {% set text_color = 'text-green-600' %} 4 | {% set ring_color = 'ring-green-300' %} 5 | {% set background_color = 'bg-green-600' %} 6 | {% set progress_background_color = 'bg-green-100' %} 7 | {% set border_color = 'border-green-600' %} 8 | {% set icon = '' %} 9 | {% elseif 'error' == envelope.type %} 10 | {% set title = 'Error' %} 11 | {% set text_color = 'text-red-600' %} 12 | {% set ring_color = 'ring-red-300' %} 13 | {% set background_color = 'bg-red-600' %} 14 | {% set progress_background_color = 'bg-red-100' %} 15 | {% set border_color = 'border-red-600' %} 16 | {% set icon = '' %} 17 | {% elseif 'warning' == envelope.type %} 18 | {% set title = 'Warning' %} 19 | {% set text_color = 'text-yellow-600' %} 20 | {% set ring_color = 'ring-yellow-300' %} 21 | {% set background_color = 'bg-yellow-600' %} 22 | {% set progress_background_color = 'bg-yellow-100' %} 23 | {% set border_color = 'border-yellow-600' %} 24 | {% set icon = '' %} 25 | {% else %} 26 | {% set title = 'Info' %} 27 | {% set text_color = 'text-blue-600' %} 28 | {% set ring_color = 'ring-blue-300' %} 29 | {% set background_color = 'bg-blue-600' %} 30 | {% set progress_background_color = 'bg-blue-100' %} 31 | {% set border_color = 'border-blue-600' %} 32 | {% set icon = '' %} 33 | {% endif %} 34 | 35 | 36 |
37 |
38 |
39 | {{ icon | raw }} 40 |
41 |
42 |

43 | {{ title | trans }} 44 |

45 |

46 | {{ envelope.message }} 47 |

48 |
49 |
50 |
51 | 52 |
53 |
54 | -------------------------------------------------------------------------------- /Storage/FallbackSession.php: -------------------------------------------------------------------------------- 1 | 25 | */ 26 | private static array $storage = []; 27 | 28 | public function get(string $name, mixed $default = null): mixed 29 | { 30 | return \array_key_exists($name, self::$storage) ? self::$storage[$name] : $default; 31 | } 32 | 33 | public function set(string $name, mixed $value): void 34 | { 35 | self::$storage[$name] = $value; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Storage/FallbackSessionInterface.php: -------------------------------------------------------------------------------- 1 | fallbackSession = $fallbackSession ?: new FallbackSession(); 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | * 52 | * Gets all notification envelopes from storage. 53 | * 54 | * @return Envelope[] The stored notification envelopes 55 | */ 56 | public function get(): array 57 | { 58 | $session = $this->getSession(); 59 | 60 | /** @var Envelope[] $envelopes */ 61 | $envelopes = $session->get(self::ENVELOPES_NAMESPACE, []); 62 | 63 | return $envelopes; 64 | } 65 | 66 | /** 67 | * {@inheritdoc} 68 | * 69 | * Stores notification envelopes in storage. 70 | * 71 | * @param array $envelopes The notification envelopes to store 72 | */ 73 | public function set(array $envelopes): void 74 | { 75 | $session = $this->getSession(); 76 | 77 | $session->set(self::ENVELOPES_NAMESPACE, $envelopes); 78 | } 79 | 80 | /** 81 | * Gets the appropriate session storage implementation. 82 | * 83 | * Uses Symfony session if available and request is not stateless, 84 | * otherwise falls back to the fallback session implementation. 85 | * 86 | * @return SessionInterface|FallbackSessionInterface The storage implementation 87 | */ 88 | private function getSession(): SessionInterface|FallbackSessionInterface 89 | { 90 | try { 91 | $request = $this->requestStack->getCurrentRequest(); 92 | 93 | if ($request && !$request->attributes->get('_stateless', false)) { 94 | return $this->requestStack->getSession(); 95 | } 96 | } catch (SessionNotFoundException) { 97 | } 98 | 99 | return $this->fallbackSession; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Support/PluginBundle.php: -------------------------------------------------------------------------------- 1 | $config The processed bundle configuration 46 | * @param ContainerConfigurator $container The container configurator 47 | * @param ContainerBuilder $builder The container builder 48 | */ 49 | public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void 50 | { 51 | if ($this instanceof FlasherSymfonyBundle) { 52 | return; 53 | } 54 | 55 | $plugin = $this->createPlugin(); 56 | $identifier = $plugin->getServiceId(); 57 | 58 | $container->services() 59 | ->set($identifier, $plugin->getFactory()) 60 | ->parent('flasher.notification_factory') 61 | ->tag('flasher.factory', ['alias' => $plugin->getAlias()]) 62 | ->public() 63 | ; 64 | 65 | foreach ((array) $plugin->getServiceAliases() as $alias) { 66 | $builder->setAlias($alias, $identifier); 67 | } 68 | } 69 | 70 | /** 71 | * Prepends default plugin configuration for Flasher. 72 | * 73 | * This method adds the plugin's scripts, styles, and options to the Flasher 74 | * configuration before the container is compiled. 75 | * 76 | * @param ContainerConfigurator $container The container configurator 77 | * @param ContainerBuilder $builder The container builder 78 | */ 79 | public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void 80 | { 81 | if ($this instanceof FlasherSymfonyBundle) { 82 | return; 83 | } 84 | 85 | $plugin = $this->createPlugin(); 86 | 87 | $builder->prependExtensionConfig('flasher', [ 88 | 'plugins' => [ 89 | $plugin->getAlias() => [ 90 | 'scripts' => (array) $plugin->getScripts(), 91 | 'styles' => (array) $plugin->getStyles(), 92 | 'options' => $plugin->getOptions(), 93 | ], 94 | ], 95 | ]); 96 | } 97 | 98 | /** 99 | * Gets the path to the plugin's configuration file. 100 | * 101 | * Returns the absolute path to the plugin's configuration file 102 | * based on the bundle's path. 103 | * 104 | * @return string Absolute path to the configuration file 105 | */ 106 | public function getConfigurationFile(): string 107 | { 108 | return rtrim($this->getPath(), '/').'/Resources/config/config.yaml'; 109 | } 110 | 111 | /** 112 | * Gets the bundle's directory path. 113 | * 114 | * Uses reflection to determine the location of the bundle class file, 115 | * then returns its directory. 116 | * 117 | * @return string The bundle directory path 118 | */ 119 | public function getPath(): string 120 | { 121 | if (!isset($this->path)) { 122 | $reflected = new \ReflectionObject($this); 123 | // assume the modern directory structure by default 124 | $this->path = \dirname($reflected->getFileName() ?: ''); 125 | } 126 | 127 | return $this->path; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Support/PluginBundleInterface.php: -------------------------------------------------------------------------------- 1 | $context The template variables 37 | * 38 | * @return string The rendered template 39 | * 40 | * @throws \LogicException If Twig is not available 41 | */ 42 | public function render(string $name, array $context = []): string 43 | { 44 | if (null === $this->twig) { 45 | throw new \LogicException('The TwigBundle is not registered in your application. Try running "composer require symfony/twig-bundle".'); 46 | } 47 | 48 | return $this->twig->render($name, $context); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Translation/Translator.php: -------------------------------------------------------------------------------- 1 | $parameters The translation parameters 44 | * @param string|null $locale The locale or null to use the default 45 | * 46 | * @return string The translated string 47 | */ 48 | public function translate(string $id, array $parameters = [], ?string $locale = null): string 49 | { 50 | if (!$this->translator instanceof TranslatorBagInterface) { 51 | return $this->translator->trans($id, $parameters, 'flasher', $locale); 52 | } 53 | 54 | $catalogue = $this->translator->getCatalogue($locale); 55 | 56 | foreach (['flasher', 'messages'] as $domain) { 57 | if ($catalogue->has($id, $domain)) { 58 | return $this->translator->trans($id, $parameters, $domain, $locale); 59 | } 60 | } 61 | 62 | return $id; 63 | } 64 | 65 | /** 66 | * Gets the current locale from Symfony's translator. 67 | * 68 | * Falls back to system default locale if translator doesn't provide one. 69 | * 70 | * @return string The current locale code 71 | */ 72 | public function getLocale(): string 73 | { 74 | if (method_exists($this->translator, 'getLocale')) { // @phpstan-ignore-line 75 | return $this->translator->getLocale(); 76 | } 77 | 78 | return class_exists(\Locale::class) ? \Locale::getDefault() : 'en'; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Twig/FlasherTwigExtension.php: -------------------------------------------------------------------------------- 1 | render(...), ['is_safe' => ['html']]), 43 | ]; 44 | } 45 | 46 | /** 47 | * Renders the flash notifications based on the specified criteria, presenter, and context. 48 | * 49 | * @param array $criteria the criteria to filter the notifications 50 | * @param "html"|"json"|string $presenter The presenter format for rendering the notifications (e.g., 'html', 'json'). 51 | * @param array $context additional context or options for rendering 52 | * 53 | * @return mixed The rendered output (HTML string, JSON string, etc.) 54 | */ 55 | public function render(array $criteria = [], string $presenter = 'html', array $context = []): mixed 56 | { 57 | return $this->flasher->render($presenter, $criteria, $context); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "php-flasher/flasher-symfony", 3 | "type": "symfony-bundle", 4 | "license": "MIT", 5 | "homepage": "https://php-flasher.io", 6 | "description": "Integrate flash notifications into Symfony projects effortlessly with PHPFlasher. Improve user experience and application feedback loops easily.", 7 | "keywords": [ 8 | "symfony", 9 | "php", 10 | "flash-notifications", 11 | "phpflasher", 12 | "user-feedback", 13 | "open-source" 14 | ], 15 | "support": { 16 | "issues": "https://github.com/php-flasher/php-flasher/issues", 17 | "source": "https://github.com/php-flasher/php-flasher" 18 | }, 19 | "authors": [ 20 | { 21 | "name": "Younes ENNAJI", 22 | "email": "younes.ennaji.pro@gmail.com", 23 | "homepage": "https://www.linkedin.com/in/younes--ennaji/", 24 | "role": "Developer" 25 | } 26 | ], 27 | "minimum-stability": "dev", 28 | "prefer-stable": true, 29 | "require": { 30 | "php": ">=8.2", 31 | "php-flasher/flasher": "^2.1.6", 32 | "symfony/config": "^7.0", 33 | "symfony/console": "^7.0", 34 | "symfony/dependency-injection": "^7.0", 35 | "symfony/http-kernel": "^7.0" 36 | }, 37 | "suggest": { 38 | "symfony/translation": "To translate flash messages, title and presets", 39 | "symfony/ux-twig-component": "To utilize and interact with flash messages components in Twig templates" 40 | }, 41 | "autoload": { 42 | "psr-4": { 43 | "Flasher\\Symfony\\": "" 44 | } 45 | }, 46 | "config": { 47 | "preferred-install": "dist", 48 | "sort-packages": true 49 | } 50 | } 51 | --------------------------------------------------------------------------------