├── .github ├── FUNDING.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── close-pull-request.yml ├── .phpstorm.meta.php ├── Command └── InstallCommand.php ├── Component └── FlasherComponent.php ├── EventListener ├── LivewireListener.php └── OctaneListener.php ├── Facade └── Flasher.php ├── FlasherServiceProvider.php ├── Http ├── Request.php └── Response.php ├── LICENSE ├── Middleware ├── FlasherMiddleware.php └── SessionMiddleware.php ├── Phpstan └── stubs │ ├── ApplicationTestingHooks.stub │ └── Repository.stub ├── README.md ├── Resources └── config.php ├── Storage └── SessionBag.php ├── Support └── PluginServiceProvider.php ├── Template └── BladeTemplateEngine.php ├── Translation ├── Translator.php └── lang │ ├── ar │ └── messages.php │ ├── de │ └── messages.php │ ├── en │ └── messages.php │ ├── es │ └── messages.php │ ├── fr │ └── messages.php │ ├── pt │ └── messages.php │ ├── ru │ └── messages.php │ └── zh │ └── messages.php ├── composer.json └── extension.neon /.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 | '@', 7 | 'Flasher\Prime\FlasherInterface' => \Flasher\Prime\Flasher::class, 8 | 'flasher' => \Flasher\Prime\Flasher::class, 9 | 'flasher.noty' => \Flasher\Noty\Prime\Noty::class, 10 | 'flasher.notyf' => \Flasher\Notyf\Prime\Notyf::class, 11 | 'flasher.sweetalert' => \Flasher\SweetAlert\Prime\SweetAlert::class, 12 | 'flasher.toastr' => \Flasher\Toastr\Prime\Toastr::class, 13 | ])); 14 | 15 | override(\Illuminate\Container\Container::makeWith(0), map([ 16 | '' => '@', 17 | 'Flasher\Prime\FlasherInterface' => \Flasher\Prime\Flasher::class, 18 | 'flasher' => \Flasher\Prime\Flasher::class, 19 | 'flasher.noty' => \Flasher\Noty\Prime\Noty::class, 20 | 'flasher.notyf' => \Flasher\Notyf\Prime\Notyf::class, 21 | 'flasher.sweetalert' => \Flasher\SweetAlert\Prime\SweetAlert::class, 22 | 'flasher.toastr' => \Flasher\Toastr\Prime\Toastr::class, 23 | ])); 24 | 25 | override(\Illuminate\Contracts\Container\Container::get(0), map([ 26 | '' => '@', 27 | 'Flasher\Prime\FlasherInterface' => \Flasher\Prime\Flasher::class, 28 | 'flasher' => \Flasher\Prime\Flasher::class, 29 | 'flasher.noty' => \Flasher\Noty\Prime\Noty::class, 30 | 'flasher.notyf' => \Flasher\Notyf\Prime\Notyf::class, 31 | 'flasher.sweetalert' => \Flasher\SweetAlert\Prime\SweetAlert::class, 32 | 'flasher.toastr' => \Flasher\Toastr\Prime\Toastr::class, 33 | ])); 34 | 35 | override(\Illuminate\Contracts\Container\Container::make(0), map([ 36 | '' => '@', 37 | 'Flasher\Prime\FlasherInterface' => \Flasher\Prime\Flasher::class, 38 | 'flasher' => \Flasher\Prime\Flasher::class, 39 | 'flasher.noty' => \Flasher\Noty\Prime\Noty::class, 40 | 'flasher.notyf' => \Flasher\Notyf\Prime\Notyf::class, 41 | 'flasher.sweetalert' => \Flasher\SweetAlert\Prime\SweetAlert::class, 42 | 'flasher.toastr' => \Flasher\Toastr\Prime\Toastr::class, 43 | ])); 44 | 45 | override(\Illuminate\Contracts\Container\Container::makeWith(0), map([ 46 | '' => '@', 47 | 'Flasher\Prime\FlasherInterface' => \Flasher\Prime\Flasher::class, 48 | 'flasher' => \Flasher\Prime\Flasher::class, 49 | 'flasher.noty' => \Flasher\Noty\Prime\Noty::class, 50 | 'flasher.notyf' => \Flasher\Notyf\Prime\Notyf::class, 51 | 'flasher.sweetalert' => \Flasher\SweetAlert\Prime\SweetAlert::class, 52 | 'flasher.toastr' => \Flasher\Toastr\Prime\Toastr::class, 53 | ])); 54 | 55 | override(\App::get(0), map([ 56 | '' => '@', 57 | 'Flasher\Prime\FlasherInterface' => \Flasher\Prime\Flasher::class, 58 | 'flasher' => \Flasher\Prime\Flasher::class, 59 | 'flasher.noty' => \Flasher\Noty\Prime\Noty::class, 60 | 'flasher.notyf' => \Flasher\Notyf\Prime\Notyf::class, 61 | 'flasher.sweetalert' => \Flasher\SweetAlert\Prime\SweetAlert::class, 62 | 'flasher.toastr' => \Flasher\Toastr\Prime\Toastr::class, 63 | ])); 64 | 65 | override(\App::make(0), map([ 66 | '' => '@', 67 | 'Flasher\Prime\FlasherInterface' => \Flasher\Prime\Flasher::class, 68 | 'flasher' => \Flasher\Prime\Flasher::class, 69 | 'flasher.noty' => \Flasher\Noty\Prime\Noty::class, 70 | 'flasher.notyf' => \Flasher\Notyf\Prime\Notyf::class, 71 | 'flasher.sweetalert' => \Flasher\SweetAlert\Prime\SweetAlert::class, 72 | 'flasher.toastr' => \Flasher\Toastr\Prime\Toastr::class, 73 | ])); 74 | 75 | override(\App::makeWith(0), map([ 76 | '' => '@', 77 | 'Flasher\Prime\FlasherInterface' => \Flasher\Prime\Flasher::class, 78 | 'flasher' => \Flasher\Prime\Flasher::class, 79 | 'flasher.noty' => \Flasher\Noty\Prime\Noty::class, 80 | 'flasher.notyf' => \Flasher\Notyf\Prime\Notyf::class, 81 | 'flasher.sweetalert' => \Flasher\SweetAlert\Prime\SweetAlert::class, 82 | 'flasher.toastr' => \Flasher\Toastr\Prime\Toastr::class, 83 | ])); 84 | 85 | override(\app(0), map([ 86 | '' => '@', 87 | 'Flasher\Prime\FlasherInterface' => \Flasher\Prime\Flasher::class, 88 | 'flasher' => \Flasher\Prime\Flasher::class, 89 | 'flasher.noty' => \Flasher\Noty\Prime\Noty::class, 90 | 'flasher.notyf' => \Flasher\Notyf\Prime\Notyf::class, 91 | 'flasher.sweetalert' => \Flasher\SweetAlert\Prime\SweetAlert::class, 92 | 'flasher.toastr' => \Flasher\Toastr\Prime\Toastr::class, 93 | ])); 94 | 95 | override(\resolve(0), map([ 96 | '' => '@', 97 | 'Flasher\Prime\FlasherInterface' => \Flasher\Prime\Flasher::class, 98 | 'flasher' => \Flasher\Prime\Flasher::class, 99 | 'flasher.noty' => \Flasher\Noty\Prime\Noty::class, 100 | 'flasher.notyf' => \Flasher\Notyf\Prime\Notyf::class, 101 | 'flasher.sweetalert' => \Flasher\SweetAlert\Prime\SweetAlert::class, 102 | 'flasher.toastr' => \Flasher\Toastr\Prime\Toastr::class, 103 | ])); 104 | 105 | override(\Psr\Container\ContainerInterface::get(0), map([ 106 | '' => '@', 107 | 'Flasher\Prime\FlasherInterface' => \Flasher\Prime\Flasher::class, 108 | 'flasher' => \Flasher\Prime\Flasher::class, 109 | 'flasher.noty' => \Flasher\Noty\Prime\Noty::class, 110 | 'flasher.notyf' => \Flasher\Notyf\Prime\Notyf::class, 111 | 'flasher.sweetalert' => \Flasher\SweetAlert\Prime\SweetAlert::class, 112 | 'flasher.toastr' => \Flasher\Toastr\Prime\Toastr::class, 113 | ])); 114 | -------------------------------------------------------------------------------- /Command/InstallCommand.php: -------------------------------------------------------------------------------- 1 | PHPFlasher resources to the public and config directories.'; 37 | 38 | /** 39 | * Creates a new InstallCommand instance. 40 | * 41 | * @param AssetManagerInterface $assetManager Manager for handling PHPFlasher assets 42 | */ 43 | public function __construct(private readonly AssetManagerInterface $assetManager) 44 | { 45 | parent::__construct(); 46 | } 47 | 48 | /** 49 | * Configure the command. 50 | * 51 | * Sets the command name, description, help text, and options. 52 | */ 53 | protected function configure(): void 54 | { 55 | $this 56 | ->setName('flasher:install') 57 | ->setDescription('Installs all PHPFlasher resources to the public and config directories.') 58 | ->setHelp('The command copies PHPFlasher assets to public/vendor/flasher/ directory and config files to the config/ directory without overwriting any existing config files.') 59 | ->addOption('config', 'c', InputOption::VALUE_NONE, 'Publish all config files to the config/packages/ directory.') 60 | ->addOption('symlink', 's', InputOption::VALUE_NONE, 'Symlink PHPFlasher assets instead of copying them.'); 61 | } 62 | 63 | /** 64 | * Execute the command. 65 | * 66 | * Installs PHPFlasher resources by: 67 | * 1. Displaying a fancy banner 68 | * 2. Processing each registered plugin 69 | * 3. Publishing assets and config files 70 | * 4. Creating a manifest file 71 | * 72 | * @param InputInterface $input Command input 73 | * @param OutputInterface $output Command output 74 | * 75 | * @return int Command exit code (0 for success, non-zero for failure) 76 | */ 77 | protected function execute(InputInterface $input, OutputInterface $output): int 78 | { 79 | $output->writeln(''); 80 | $output->writeln(' 81 | ██████╗ ██╗ ██╗██████╗ ███████╗██╗ █████╗ ███████╗██╗ ██╗███████╗██████╗ 82 | ██╔══██╗██║ ██║██╔══██╗██╔════╝██║ ██╔══██╗██╔════╝██║ ██║██╔════╝██╔══██╗ 83 | ██████╔╝███████║██████╔╝█████╗ ██║ ███████║███████╗███████║█████╗ ██████╔╝ 84 | ██╔═══╝ ██╔══██║██╔═══╝ ██╔══╝ ██║ ██╔══██║╚════██║██╔══██║██╔══╝ ██╔══██╗ 85 | ██║ ██║ ██║██║ ██║ ███████╗██║ ██║███████║██║ ██║███████╗██║ ██║ 86 | ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ 87 | '); 88 | $output->writeln(''); 89 | 90 | $output->writeln(''); 91 | $output->writeln(' INFO Copying PHPFlasher resources...'); 92 | $output->writeln(''); 93 | 94 | $useSymlinks = (bool) $input->getOption('symlink'); 95 | if ($useSymlinks) { 96 | $output->writeln('Using symlinks to publish assets.'); 97 | } else { 98 | $output->writeln('Copying assets to the public directory.'); 99 | } 100 | 101 | $publishConfig = (bool) $input->getOption('config'); 102 | if ($publishConfig) { 103 | $output->writeln('Publishing configuration files.'); 104 | } 105 | 106 | $publicDir = App::publicPath('/vendor/flasher/'); 107 | 108 | $filesystem = new Filesystem(); 109 | $filesystem->deleteDirectory($publicDir); 110 | $filesystem->makeDirectory($publicDir, recursive: true); 111 | 112 | $files = []; 113 | 114 | $exitCode = self::SUCCESS; 115 | 116 | foreach (array_keys(App::getLoadedProviders()) as $provider) { 117 | if (!is_a($provider, PluginServiceProvider::class, true)) { 118 | continue; 119 | } 120 | 121 | /** @var PluginServiceProvider $provider */ 122 | $provider = App::getProvider($provider); 123 | $plugin = $provider->createPlugin(); 124 | $configFile = $provider->getConfigurationFile(); 125 | 126 | try { 127 | $files[] = $this->publishAssets($plugin, $publicDir, $useSymlinks); 128 | 129 | if ($publishConfig) { 130 | $this->publishConfig($plugin, $configFile); 131 | } 132 | 133 | $status = \sprintf('%s', '\\' === \DIRECTORY_SEPARATOR ? 'OK' : "\xE2\x9C\x94" /* HEAVY CHECK MARK (U+2714) */); 134 | $output->writeln(\sprintf(' %s %s', $status, $plugin->getAlias())); 135 | } catch (\Exception $e) { 136 | $exitCode = self::FAILURE; 137 | $status = \sprintf('%s', '\\' === \DIRECTORY_SEPARATOR ? 'ERROR' : "\xE2\x9C\x98" /* HEAVY BALLOT X (U+2718) */); 138 | $output->writeln(\sprintf(' %s %s %s', $status, $plugin->getAlias(), $e->getMessage())); 139 | } 140 | } 141 | 142 | $output->writeln(''); 143 | 144 | if (self::SUCCESS === $exitCode) { 145 | $message = 'PHPFlasher resources have been successfully installed.'; 146 | if ($publishConfig) { 147 | $message .= ' Configuration files have been published.'; 148 | } 149 | if ($useSymlinks) { 150 | $message .= ' Assets were symlinked.'; 151 | } 152 | $output->writeln(" SUCCESS $message"); 153 | } else { 154 | $output->writeln(' ERROR An error occurred during the installation of PHPFlasher resources.'); 155 | } 156 | 157 | $this->assetManager->createManifest(array_merge([], ...$files)); 158 | 159 | $output->writeln(''); 160 | 161 | return $exitCode; 162 | } 163 | 164 | /** 165 | * Publish assets from a plugin to the public directory. 166 | * 167 | * @param PluginInterface $plugin The plugin to publish assets from 168 | * @param string $publicDir The target public directory 169 | * @param bool $useSymlinks Whether to symlink or copy assets 170 | * 171 | * @return string[] Array of published file paths 172 | */ 173 | private function publishAssets(PluginInterface $plugin, string $publicDir, bool $useSymlinks): array 174 | { 175 | $originDir = $plugin->getAssetsDir(); 176 | 177 | if (!is_dir($originDir)) { 178 | return []; 179 | } 180 | 181 | $filesystem = new Filesystem(); 182 | $finder = new Finder(); 183 | $finder->files()->in($originDir); 184 | 185 | $files = []; 186 | 187 | foreach ($finder as $file) { 188 | $relativePath = trim(str_replace($originDir, '', $file->getRealPath()), \DIRECTORY_SEPARATOR); 189 | $targetPath = $publicDir.$relativePath; 190 | 191 | $filesystem->makeDirectory(\dirname($targetPath), recursive: true, force: true); 192 | 193 | if ($useSymlinks) { 194 | $filesystem->link($file->getRealPath(), $targetPath); 195 | } else { 196 | $filesystem->copy($file->getRealPath(), $targetPath); 197 | } 198 | 199 | $files[] = $targetPath; 200 | } 201 | 202 | return $files; 203 | } 204 | 205 | /** 206 | * Publish a plugin's configuration file. 207 | * 208 | * @param PluginInterface $plugin The plugin to publish configuration for 209 | * @param string $configFile The source configuration file path 210 | */ 211 | private function publishConfig(PluginInterface $plugin, string $configFile): void 212 | { 213 | if (!file_exists($configFile)) { 214 | return; 215 | } 216 | 217 | $target = App::configPath($plugin->getName().'.php'); 218 | if (file_exists($target)) { 219 | return; 220 | } 221 | 222 | $filesystem = new Filesystem(); 223 | $filesystem->copy($configFile, $target); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /Component/FlasherComponent.php: -------------------------------------------------------------------------------- 1 | 5])" :context="json_encode(['foo' => 'bar'])" /> 15 | * 16 | * Design patterns: 17 | * - View Component: Implements Laravel's view component pattern 18 | * - Adapter: Adapts the Flasher render method to Laravel's component interface 19 | */ 20 | final class FlasherComponent extends Component 21 | { 22 | /** 23 | * Creates a new FlasherComponent instance. 24 | * 25 | * @param string $criteria JSON-encoded filtering criteria for notifications 26 | * @param string $context JSON-encoded rendering context 27 | */ 28 | public function __construct(public string $criteria = '', public string $context = '') 29 | { 30 | } 31 | 32 | /** 33 | * Renders the component. 34 | * 35 | * This method decodes the JSON criteria and context, then delegates to 36 | * the Flasher service to render the notifications as HTML. 37 | * 38 | * @return string Rendered HTML content 39 | * 40 | * @throws \JsonException If JSON decoding fails 41 | */ 42 | public function render(): string 43 | { 44 | /** @var array $criteria */ 45 | $criteria = json_decode($this->criteria, true, 512, \JSON_THROW_ON_ERROR) ?: []; 46 | 47 | /** @var array $context */ 48 | $context = json_decode($this->context, true, 512, \JSON_THROW_ON_ERROR) ?: []; 49 | 50 | return app('flasher')->render('html', $criteria, $context); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /EventListener/LivewireListener.php: -------------------------------------------------------------------------------- 1 | shouldSkip($context)) { 58 | return; 59 | } 60 | 61 | /** @var array{envelopes: Envelope[]} $data */ 62 | $data = $this->flasher->render('array', [], $this->createContext()); 63 | 64 | if (\count($data['envelopes']) > 0) { 65 | $this->dispatchNotifications($component, $context, $data); 66 | } 67 | } 68 | 69 | /** 70 | * Dispatches notifications as Livewire events. 71 | * 72 | * This method adds the notifications data to the Livewire component's 73 | * dispatched events, which will be processed by the front-end. 74 | * 75 | * @param Component $component The Livewire component 76 | * @param ComponentContext $context The Livewire component context 77 | * @param array{envelopes: Envelope[]} $data The notification data 78 | */ 79 | private function dispatchNotifications(Component $component, ComponentContext $context, array $data): void 80 | { 81 | $data['context']['livewire'] = [ 82 | 'id' => $component->getId(), 83 | 'name' => $component->getName(), 84 | ]; 85 | 86 | $dispatches = $context->effects['dispatches'] ?? []; 87 | $dispatches[] = ['name' => 'flasher:render', 'params' => $data]; 88 | 89 | $context->addEffect('dispatches', $dispatches); 90 | } 91 | 92 | /** 93 | * Determines if notification processing should be skipped. 94 | * 95 | * Skips processing in the following cases: 96 | * - Not a Livewire request 97 | * - During component mounting (initial render) 98 | * - When a redirect is in progress 99 | * 100 | * @param ComponentContext $context The Livewire component context 101 | * 102 | * @return bool True if notification processing should be skipped 103 | */ 104 | private function shouldSkip(ComponentContext $context): bool 105 | { 106 | return !$this->livewire->isLivewireRequest() || $context->mounting || isset($context->effects['redirect']); 107 | } 108 | 109 | /** 110 | * Creates the security context for rendering notifications. 111 | * 112 | * This method generates CSP nonces to ensure scripts loaded by PHPFlasher 113 | * comply with Content Security Policy. 114 | * 115 | * @return array The context with CSP nonces 116 | */ 117 | private function createContext(): array 118 | { 119 | /** @var LaravelRequest $request */ 120 | $request = ($this->request)(); 121 | $nonces = $this->cspHandler->getNonces(new Request($request)); 122 | 123 | return [ 124 | 'csp_script_nonce' => $nonces['csp_script_nonce'] ?? null, 125 | 'csp_style_nonce' => $nonces['csp_style_nonce'] ?? null, 126 | ]; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /EventListener/OctaneListener.php: -------------------------------------------------------------------------------- 1 | sandbox->make('flasher.notification_logger_listener'); 34 | $listener->reset(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Facade/Flasher.php: -------------------------------------------------------------------------------- 1 | title('Success') 31 | * ->message('Record saved') 32 | * ->option('timeout', 5000) 33 | * ->push(); 34 | * ``` 35 | * 36 | * @method static NotificationBuilder title(string $message) 37 | * @method static NotificationBuilder message(string $message) 38 | * @method static NotificationBuilder type(string $message) 39 | * @method static NotificationBuilder options(array $options, bool $merge = true) 40 | * @method static NotificationBuilder option(string $name, $value) 41 | * @method static NotificationBuilder priority(int $priority) 42 | * @method static NotificationBuilder hops(int $amount) 43 | * @method static NotificationBuilder keep() 44 | * @method static NotificationBuilder delay(int $delay) 45 | * @method static NotificationBuilder translate(array $parameters = [], ?string $locale = null) 46 | * @method static NotificationBuilder handler(string $handler) 47 | * @method static NotificationBuilder context(array $context) 48 | * @method static NotificationBuilder when(bool|\Closure $condition) 49 | * @method static NotificationBuilder unless(bool|\Closure $condition) 50 | * @method static NotificationBuilder with(StampInterface[] $stamps = array()) 51 | * @method static NotificationBuilder withStamp(StampInterface $stamp) 52 | * @method static Envelope success(string $message, array $options = [], ?string $title = null) 53 | * @method static Envelope error(string $message, array $options = [], ?string $title = null) 54 | * @method static Envelope info(string $message, array $options = [], ?string $title = null) 55 | * @method static Envelope warning(string $message, array $options = [], ?string $title = null) 56 | * @method static Envelope flash(?string $type = null, ?string $message = null, array $options = [], ?string $title = null) 57 | * @method static Envelope preset(string $preset, array $parameters = []) 58 | * @method static Envelope operation(string $operation, string|object|null $resource = null) 59 | * @method static Envelope created(string|object|null $resource = null) 60 | * @method static Envelope updated(string|object|null $resource = null) 61 | * @method static Envelope saved(string|object|null $resource = null) 62 | * @method static Envelope deleted(string|object|null $resource = null) 63 | * @method static Envelope push() 64 | * @method static Envelope addPreset(string $preset, array $parameters = []) 65 | * @method static Envelope addCreated(string|object|null $resource = null) 66 | * @method static Envelope addUpdated(string|object|null $resource = null) 67 | * @method static Envelope addDeleted(string|object|null $resource = null) 68 | * @method static Envelope addSaved(string|object|null $resource = null) 69 | * @method static Envelope addOperation(string $operation, string|object|null $resource = null) 70 | * @method static Envelope getEnvelope() 71 | */ 72 | final class Flasher extends Facade 73 | { 74 | /** 75 | * Get the registered name of the component. 76 | * 77 | * @return string The name of the facade's service binding ('flasher') 78 | */ 79 | protected static function getFacadeAccessor(): string 80 | { 81 | return 'flasher'; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /FlasherServiceProvider.php: -------------------------------------------------------------------------------- 1 | plugin = $this->createPlugin(); 70 | 71 | $this->registerConfiguration(); 72 | $this->registerFlasher(); 73 | $this->registerFactoryLocator(); 74 | $this->registerResponseManager(); 75 | $this->registerTemplateEngine(); 76 | $this->registerResourceManager(); 77 | $this->registerStorageManager(); 78 | $this->registerEventDispatcher(); 79 | $this->registerCspHandler(); 80 | $this->registerAssetManager(); 81 | } 82 | 83 | /** 84 | * Boot PHPFlasher services after all providers are registered. 85 | * 86 | * This method follows Laravel's service provider boot phase by: 87 | * 1. Setting up the service container bridge 88 | * 2. Registering commands 89 | * 3. Loading translations 90 | * 4. Registering middleware 91 | * 5. Registering Blade directives and components 92 | * 6. Setting up Livewire integration 93 | */ 94 | public function boot(): void 95 | { 96 | FlasherContainer::from(static fn () => Container::getInstance()); 97 | 98 | $this->registerCommands(); 99 | $this->loadTranslationsFrom(__DIR__.'/Translation/lang', 'flasher'); 100 | $this->registerMiddlewares(); 101 | $this->callAfterResolving('blade.compiler', $this->registerBladeDirectives(...)); 102 | $this->registerLivewire(); 103 | } 104 | 105 | /** 106 | * Create the PHPFlasher core plugin instance. 107 | * 108 | * @return FlasherPlugin The core PHPFlasher plugin 109 | */ 110 | public function createPlugin(): FlasherPlugin 111 | { 112 | return new FlasherPlugin(); 113 | } 114 | 115 | /** 116 | * Register the main Flasher service with Laravel's container. 117 | * 118 | * This service is the main entry point for all PHPFlasher functionality 119 | * and is made available via the 'flasher' service binding. 120 | */ 121 | private function registerFlasher(): void 122 | { 123 | $this->app->singleton('flasher', static function (Application $app) { 124 | $config = $app->make('config'); 125 | 126 | $default = $config->get('flasher.default'); 127 | $factoryLocator = $app->make('flasher.factory_locator'); 128 | $responseManager = $app->make('flasher.response_manager'); 129 | $storageManager = $app->make('flasher.storage_manager'); 130 | 131 | return new Flasher($default, $factoryLocator, $responseManager, $storageManager); 132 | }); 133 | 134 | $this->app->alias('flasher', Flasher::class); 135 | $this->app->bind(FlasherInterface::class, 'flasher'); 136 | } 137 | 138 | /** 139 | * Register the factory locator service. 140 | * 141 | * The factory locator is responsible for locating and providing 142 | * notification factory instances. 143 | */ 144 | private function registerFactoryLocator(): void 145 | { 146 | $this->app->singleton('flasher.factory_locator', static function () { 147 | return new NotificationFactoryLocator(); 148 | }); 149 | } 150 | 151 | /** 152 | * Register the response manager service. 153 | * 154 | * The response manager is responsible for rendering notifications 155 | * into different formats (HTML, JSON, etc.). 156 | */ 157 | private function registerResponseManager(): void 158 | { 159 | $this->app->singleton('flasher.response_manager', static function (Application $app) { 160 | $resourceManager = $app->make('flasher.resource_manager'); 161 | $storageManager = $app->make('flasher.storage_manager'); 162 | $eventDispatcher = $app->make('flasher.event_dispatcher'); 163 | 164 | return new ResponseManager($resourceManager, $storageManager, $eventDispatcher); 165 | }); 166 | } 167 | 168 | /** 169 | * Register the template engine adapter for Blade. 170 | * 171 | * This adapter allows PHPFlasher to render templates using Laravel's Blade engine. 172 | */ 173 | private function registerTemplateEngine(): void 174 | { 175 | $this->app->singleton('flasher.template_engine', static function (Application $app) { 176 | $viewFactory = $app->make('view'); 177 | 178 | return new BladeTemplateEngine($viewFactory); 179 | }); 180 | } 181 | 182 | /** 183 | * Register the resource manager service. 184 | * 185 | * The resource manager is responsible for managing assets (JS, CSS) 186 | * needed by notifications. 187 | */ 188 | private function registerResourceManager(): void 189 | { 190 | $this->app->singleton('flasher.resource_manager', static function (Application $app) { 191 | $config = $app->make('config'); 192 | 193 | $templateEngine = $app->make('flasher.template_engine'); 194 | $assetManager = $app->make('flasher.asset_manager'); 195 | $mainScript = $config->get('flasher.main_script'); 196 | $resources = $config->get('flasher.plugins'); 197 | 198 | return new ResourceManager($templateEngine, $assetManager, $mainScript, $resources); 199 | }); 200 | } 201 | 202 | /** 203 | * Register the storage manager service. 204 | * 205 | * The storage manager is responsible for storing and retrieving 206 | * notifications from storage (session in Laravel's case). 207 | */ 208 | private function registerStorageManager(): void 209 | { 210 | $this->app->singleton('flasher.storage_manager', static function (Application $app) { 211 | $config = $app->make('config'); 212 | 213 | $storageBag = new Storage(new SessionBag($app->make('session'))); 214 | $eventDispatcher = $app->make('flasher.event_dispatcher'); 215 | $filterFactory = new FilterFactory(); 216 | $criteria = $config->get('flasher.filter'); 217 | 218 | return new StorageManager($storageBag, $eventDispatcher, $filterFactory, $criteria); 219 | }); 220 | } 221 | 222 | /** 223 | * Register the event dispatcher and event listeners. 224 | * 225 | * The event dispatcher is responsible for dispatching events during 226 | * the notification lifecycle. 227 | */ 228 | private function registerEventDispatcher(): void 229 | { 230 | $this->app->singleton('flasher.notification_logger_listener', fn () => new NotificationLoggerListener()); 231 | 232 | $this->app->singleton('flasher.event_dispatcher', static function (Application $app) { 233 | $config = $app->make('config'); 234 | 235 | $eventDispatcher = new EventDispatcher(); 236 | 237 | $translatorListener = new TranslationListener(new Translator($app->make('translator'))); 238 | $eventDispatcher->addListener($translatorListener); 239 | 240 | $presetListener = new ApplyPresetListener($config->get('flasher.presets')); 241 | $eventDispatcher->addListener($presetListener); 242 | 243 | $eventDispatcher->addListener($app->make('flasher.notification_logger_listener')); 244 | 245 | return $eventDispatcher; 246 | }); 247 | 248 | $this->callAfterResolving(Dispatcher::class, function (Dispatcher $dispatcher) { 249 | $dispatcher->listen(RequestReceived::class, OctaneListener::class); 250 | }); 251 | } 252 | 253 | /** 254 | * Register the Artisan commands for PHPFlasher. 255 | * 256 | * Commands are only registered when running in console mode. 257 | */ 258 | private function registerCommands(): void 259 | { 260 | if (!$this->app->runningInConsole()) { 261 | return; 262 | } 263 | 264 | $this->registerAboutCommand(); 265 | 266 | $this->app->singleton(InstallCommand::class, static function (Application $app) { 267 | $assetManager = $app->make('flasher.asset_manager'); 268 | 269 | return new InstallCommand($assetManager); 270 | }); 271 | 272 | $this->commands(InstallCommand::class); 273 | } 274 | 275 | /** 276 | * Register PHPFlasher information with Laravel's about command. 277 | * 278 | * This adds PHPFlasher information to the output of the `php artisan about` command. 279 | */ 280 | private function registerAboutCommand(): void 281 | { 282 | if (!class_exists(AboutCommand::class)) { 283 | return; 284 | } 285 | 286 | $pluginServiceProviders = array_filter(array_keys($this->app->getLoadedProviders()), function ($provider) { 287 | return is_a($provider, PluginServiceProvider::class, true); 288 | }); 289 | 290 | $factories = array_map(function ($providerClass) { 291 | /** @var PluginServiceProvider $provider */ 292 | $provider = $this->app->getProvider($providerClass); 293 | $plugin = $provider->createPlugin(); 294 | 295 | return $plugin->getAlias(); 296 | }, $pluginServiceProviders); 297 | 298 | AboutCommand::add('PHPFlasher', [ 299 | 'Version' => Flasher::VERSION, 300 | 'Factories' => implode(' / ', array_map(fn ($factory) => \sprintf('%s', $factory), $factories)), 301 | ]); 302 | } 303 | 304 | /** 305 | * Register PHPFlasher middleware with Laravel. 306 | * 307 | * Middleware includes session processing and response modification. 308 | */ 309 | private function registerMiddlewares(): void 310 | { 311 | $this->registerSessionMiddleware(); 312 | $this->registerFlasherMiddleware(); 313 | } 314 | 315 | /** 316 | * Register the response middleware. 317 | * 318 | * This middleware injects notification assets into responses. 319 | */ 320 | private function registerFlasherMiddleware(): void 321 | { 322 | if (!$this->getConfig('inject_assets')) { 323 | return; 324 | } 325 | 326 | $this->app->singleton(FlasherMiddleware::class, static function (Application $app) { 327 | $config = $app->make('config'); 328 | 329 | $flasher = $app->make('flasher'); 330 | $cspHandler = $app->make('flasher.csp_handler'); 331 | $excludedPaths = $config->get('flasher.excluded_paths', []) ?: []; 332 | 333 | return new FlasherMiddleware(new ResponseExtension($flasher, $cspHandler, $excludedPaths)); 334 | }); 335 | 336 | $this->pushMiddlewareToGroup(FlasherMiddleware::class); 337 | } 338 | 339 | /** 340 | * Register the session middleware. 341 | * 342 | * This middleware processes flash messages from the session. 343 | */ 344 | private function registerSessionMiddleware(): void 345 | { 346 | if (!$this->getConfig('flash_bag')) { 347 | return; 348 | } 349 | 350 | $this->app->singleton(SessionMiddleware::class, static function (Application $app) { 351 | $config = $app->make('config'); 352 | 353 | $flasher = $app->make('flasher'); 354 | $mapping = $config->get('flasher.flash_bag', []) ?: []; 355 | 356 | return new SessionMiddleware(new RequestExtension($flasher, $mapping)); 357 | }); 358 | 359 | $this->pushMiddlewareToGroup(SessionMiddleware::class); 360 | } 361 | 362 | /** 363 | * Push middleware to the web middleware group. 364 | * 365 | * @param string $middleware The middleware class name 366 | */ 367 | private function pushMiddlewareToGroup(string $middleware): void 368 | { 369 | $this->callAfterResolving(HttpKernel::class, function (HttpKernel $kernel) use ($middleware) { 370 | $kernel->appendMiddlewareToGroup('web', $middleware); 371 | }); 372 | } 373 | 374 | /** 375 | * Register the Content Security Policy handler. 376 | * 377 | * This service handles CSP headers when injecting assets. 378 | */ 379 | private function registerCspHandler(): void 380 | { 381 | $this->app->singleton('flasher.csp_handler', static function () { 382 | return new ContentSecurityPolicyHandler(new NonceGenerator()); 383 | }); 384 | } 385 | 386 | /** 387 | * Register the asset manager service. 388 | * 389 | * The asset manager is responsible for managing asset paths and manifests. 390 | */ 391 | private function registerAssetManager(): void 392 | { 393 | $this->app->singleton('flasher.asset_manager', static function () { 394 | $publicDir = public_path('/'); 395 | $manifestPath = public_path('vendor'.\DIRECTORY_SEPARATOR.'flasher'.\DIRECTORY_SEPARATOR.'manifest.json'); 396 | 397 | return new AssetManager($publicDir, $manifestPath); 398 | }); 399 | } 400 | 401 | /** 402 | * Register Blade directives and components. 403 | * 404 | * @param BladeCompiler $blade The Blade compiler instance 405 | */ 406 | private function registerBladeDirectives(BladeCompiler $blade): void 407 | { 408 | $blade->directive('flasher_render', function (string $expression = '') { 409 | if (!empty($expression) && str_starts_with($expression, '(') && str_ends_with($expression, ')')) { 410 | $expression = substr($expression, 1, -1); 411 | } 412 | 413 | return "render('html', $expression); ?>"; 414 | }); 415 | 416 | $blade->component(FlasherComponent::class, 'flasher'); 417 | } 418 | 419 | /** 420 | * Register Livewire integration. 421 | * 422 | * This sets up listeners for Livewire component lifecycle events. 423 | */ 424 | private function registerLivewire(): void 425 | { 426 | if (class_exists(LivewireManager::class) && !$this->app->bound('livewire')) { 427 | return; 428 | } 429 | 430 | $this->callAfterResolving('livewire', function (LivewireManager $livewire, Application $app) { 431 | $flasher = $app->make('flasher'); 432 | $cspHandler = $app->make('flasher.csp_handler'); 433 | $request = fn () => $app->make('request'); 434 | 435 | $livewire->listen('dehydrate', new LivewireListener($livewire, $flasher, $cspHandler, $request)); 436 | }); 437 | } 438 | } 439 | -------------------------------------------------------------------------------- /Http/Request.php: -------------------------------------------------------------------------------- 1 | request->getUri(); 37 | } 38 | 39 | public function isXmlHttpRequest(): bool 40 | { 41 | return $this->request->ajax(); 42 | } 43 | 44 | public function isHtmlRequestFormat(): bool 45 | { 46 | return $this->request->acceptsHtml(); 47 | } 48 | 49 | public function hasSession(): bool 50 | { 51 | return $this->request->hasSession(); 52 | } 53 | 54 | public function isSessionStarted(): bool 55 | { 56 | $session = $this->getSession(); 57 | 58 | return $session?->isStarted() ?: false; 59 | } 60 | 61 | public function hasType(string $type): bool 62 | { 63 | if (!$this->hasSession() || !$this->isSessionStarted()) { 64 | return false; 65 | } 66 | 67 | $session = $this->getSession(); 68 | 69 | return $session?->has($type) ?: false; 70 | } 71 | 72 | public function getType(string $type): string|array 73 | { 74 | $session = $this->getSession(); 75 | 76 | /** @var false|string|string[] $type */ 77 | $type = $session?->get($type); 78 | 79 | if (!\is_string($type) && !\is_array($type)) { 80 | return []; 81 | } 82 | 83 | return $type; 84 | } 85 | 86 | public function forgetType(string $type): void 87 | { 88 | $session = $this->getSession(); 89 | 90 | $session?->forget($type); 91 | } 92 | 93 | /** 94 | * Gets the session from the request, with graceful handling of missing sessions. 95 | * 96 | * @return Session|null The session or null if not available 97 | */ 98 | private function getSession(): ?Session 99 | { 100 | try { 101 | return $this->request->session(); 102 | } catch (\RuntimeException) { 103 | return null; 104 | } 105 | } 106 | 107 | public function hasHeader(string $key): bool 108 | { 109 | return $this->request->headers->has($key); 110 | } 111 | 112 | public function getHeader(string $key): ?string 113 | { 114 | return $this->request->headers->get($key); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Http/Response.php: -------------------------------------------------------------------------------- 1 | response->isRedirection(); 38 | } 39 | 40 | public function isJson(): bool 41 | { 42 | return $this->response instanceof SymfonyJsonResponse; 43 | } 44 | 45 | public function isHtml(): bool 46 | { 47 | $contentType = $this->response->headers->get('Content-Type'); 48 | 49 | if (!\is_string($contentType)) { 50 | return false; 51 | } 52 | 53 | return false !== stripos($contentType, 'html'); 54 | } 55 | 56 | public function isAttachment(): bool 57 | { 58 | $contentDisposition = $this->response->headers->get('Content-Disposition', ''); 59 | 60 | if (!$contentDisposition) { 61 | return false; 62 | } 63 | 64 | return false !== stripos($contentDisposition, 'attachment;'); 65 | } 66 | 67 | public function isSuccessful(): bool 68 | { 69 | return $this->response->isSuccessful(); 70 | } 71 | 72 | public function getContent(): string 73 | { 74 | return $this->response->getContent() ?: ''; 75 | } 76 | 77 | /** 78 | * {@inheritdoc} 79 | * 80 | * This implementation preserves the original content in Laravel responses, 81 | * ensuring compatibility with Laravel's view system and JSON responses. 82 | */ 83 | public function setContent(string $content): void 84 | { 85 | $original = null; 86 | if ($this->response instanceof LaravelResponse && $this->response->getOriginalContent()) { 87 | $original = $this->response->getOriginalContent(); 88 | } 89 | 90 | $this->response->setContent($content); 91 | 92 | // Restore original response (eg. the View or Ajax data) 93 | if ($original && $this->response instanceof LaravelResponse) { 94 | $this->response->original = $original; 95 | } 96 | } 97 | 98 | public function hasHeader(string $key): bool 99 | { 100 | return $this->response->headers->has($key); 101 | } 102 | 103 | public function getHeader(string $key): ?string 104 | { 105 | return $this->response->headers->get($key); 106 | } 107 | 108 | public function setHeader(string $key, array|string|null $values): void 109 | { 110 | $this->response->headers->set($key, $values); 111 | } 112 | 113 | public function removeHeader(string $key): void 114 | { 115 | $this->response->headers->remove($key); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Middleware/FlasherMiddleware.php: -------------------------------------------------------------------------------- 1 | responseExtension->render(new Request($request), new Response($response)); 53 | } 54 | 55 | return $response; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Middleware/SessionMiddleware.php: -------------------------------------------------------------------------------- 1 | requestExtension->flash(new Request($request), new Response($response)); 53 | } 54 | 55 | return $response; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Phpstan/stubs/ApplicationTestingHooks.stub: -------------------------------------------------------------------------------- 1 | , 24 | * excluded_paths?: list, 25 | * filter: array, 26 | * flash_bag: array, 27 | * presets: array, 28 | * plugins: array, 29 | * } 30 | * 31 | * @phpstan-type PresetType array{ 32 | * type: string, 33 | * title: string, 34 | * message: string, 35 | * options: array, 36 | * } 37 | * 38 | * @phpstan-type PluginType array{ 39 | * scripts?: string[], 40 | * styles?: string[], 41 | * options?: array, 42 | * } 43 | */ 44 | interface Repository 45 | { 46 | /** 47 | * Get a configuration value. 48 | * 49 | * This stub provides detailed return type information for PHPFlasher's 50 | * configuration structure, enabling better static analysis. 51 | * 52 | * @param string[]|string $key The configuration key 53 | * @param mixed $default Default value if key doesn't exist 54 | * 55 | * @return mixed The configuration value with precise type information for PHPFlasher keys 56 | * 57 | * @phpstan-return ($key is 'flasher' ? ConfigType : 58 | * ($key is 'flasher.default' ? string : 59 | * ($key is 'flasher.main_script' ? string : 60 | * ($key is 'flasher.filter' ? array : 61 | * ($key is 'flasher.presets' ? array : 62 | * ($key is 'flasher.plugins' ? array : 63 | * ($key is 'flasher.flash_bag' ? array : 64 | * ($key is 'flasher.excluded_paths' ? list : 65 | * mixed)))))))) 66 | */ 67 | public function get(string|array $key, mixed $default = null): mixed; 68 | } 69 | -------------------------------------------------------------------------------- /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 Laravel Adapter](#about-phpflasher-laravel-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 Laravel Adapter 44 | 45 | **PHPFlasher Laravel Adapter** is an open-source package that seamlessly integrates PHPFlasher's powerful flash messaging capabilities into your **Laravel** applications. It simplifies the process of adding flash messages, providing an intuitive API to enhance user experience with minimal setup. 46 | 47 | With PHPFlasher Laravel 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 Laravel Integration**: Designed specifically for Laravel, 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 Laravel Adapter Version | PHP Version | Laravel Version | 60 | |------------------------------------|-------------|-----------------| 61 | | **v2.x** | ≥ 8.2 | ≥ 11 | 62 | | **v1.x** | ≥ 5.3 | ≥ 4.0 | 63 | 64 | > **Note:** Ensure your project meets the PHP and Laravel version requirements for the PHPFlasher Laravel Adapter version you intend to use. For older PHP or Laravel versions, refer to [PHPFlasher v1.x](https://github.com/php-flasher/flasher-laravel/tree/1.x). 65 | 66 | ## Installation 67 | 68 | ### Core Package 69 | 70 | Install the PHPFlasher Laravel Adapter via Composer: 71 | 72 | ```bash 73 | composer require php-flasher/flasher-laravel 74 | ``` 75 | 76 | After installation, set up the necessary assets: 77 | 78 | ```shell 79 | php artisan 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 Laravel: 87 | 88 | - [flasher-toastr-laravel](https://github.com/php-flasher/flasher-toastr-laravel) - Laravel Adapter 89 | - [flasher-noty-laravel](https://github.com/php-flasher/flasher-noty-laravel) - Laravel Adapter 90 | - [flasher-notyf-laravel](https://github.com/php-flasher/flasher-notyf-laravel) - Laravel Adapter 91 | - [flasher-sweetalert-laravel](https://github.com/php-flasher/flasher-sweetalert-laravel) - Laravel 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 Laravel 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 artisan flasher:install --config 105 | ``` 106 | 107 | This will create a file at `config/flasher.php` with the following content: 108 | 109 | ```php 110 | 'flasher', 119 | 120 | // Path to the main PHPFlasher JavaScript file 121 | 'main_script' => '/vendor/flasher/flasher.min.js', 122 | 123 | // List of CSS files to style your notifications 124 | 'styles' => [ 125 | '/vendor/flasher/flasher.min.css', 126 | ], 127 | 128 | // Set global options for all notifications (optional) 129 | // 'options' => [ 130 | // 'timeout' => 5000, // Time in milliseconds before the notification disappears 131 | // 'position' => 'top-right', // Where the notification appears on the screen 132 | // ], 133 | 134 | // Automatically inject JavaScript and CSS assets into your HTML pages 135 | 'inject_assets' => true, 136 | 137 | // Enable message translation using Laravel's translation service 138 | 'translate' => true, 139 | 140 | // URL patterns to exclude from asset injection and flash_bag conversion 141 | 'excluded_paths' => [], 142 | 143 | // Map Laravel flash message keys to notification types 144 | 'flash_bag' => [ 145 | 'success' => ['success'], 146 | 'error' => ['error', 'danger'], 147 | 'warning' => ['warning', 'alarm'], 148 | 'info' => ['info', 'notice', 'alert'], 149 | ], 150 | 151 | // Set criteria to filter which notifications are displayed (optional) 152 | // 'filter' => [ 153 | // 'limit' => 5, // Maximum number of notifications to show at once 154 | // ], 155 | 156 | // Define notification presets to simplify notification creation (optional) 157 | // 'presets' => [ 158 | // 'entity_saved' => [ 159 | // 'type' => 'success', 160 | // 'title' => 'Entity saved', 161 | // 'message' => 'Entity saved successfully', 162 | // ], 163 | // ], 164 | ]); 165 | ``` 166 | 167 | ### Configuration Options 168 | 169 | | **Option** | **Description** | 170 | |------------------|---------------------------------------------------------------------------------------------------------------------------| 171 | | `default` | **String**: The default notification library to use (e.g., `'flasher'`, `'toastr'`, `'noty'`, `'notyf'`, `'sweetalert'`). | 172 | | `main_script` | **String**: Path to the main PHPFlasher JavaScript file. | 173 | | `styles` | **Array**: List of CSS files to style your notifications. | 174 | | `options` | **Array** (Optional): Global options for all notifications (e.g., `'timeout'`, `'position'`). | 175 | | `inject_assets` | **Boolean**: Whether to automatically inject JavaScript and CSS assets into your HTML pages. | 176 | | `translate` | **Boolean**: Enable message translation using Laravel’s translation service. | 177 | | `excluded_paths` | **Array**: URL patterns to exclude from asset injection and flash_bag conversion. | 178 | | `flash_bag` | **Array**: Map Laravel flash message keys to notification types. | 179 | | `filter` | **Array** (Optional): Criteria to filter which notifications are displayed (e.g., `'limit'`). | 180 | | `presets` | **Array** (Optional): Define notification presets to simplify notification creation. | 181 | 182 | ## Quick Start 183 | 184 | 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. 185 | 186 | ### Using the `flash()` Helper 187 | 188 | ```php 189 | back(); 204 | } 205 | } 206 | ``` 207 | 208 | ### Using the `flasher` Service 209 | 210 | ```php 211 | success('Your changes have been saved!'); 227 | 228 | // ... redirect or render the view 229 | } 230 | 231 | public function update() 232 | { 233 | // Your logic here 234 | 235 | app('flasher')->error('An error occurred while updating.'); 236 | 237 | return redirect()->back(); 238 | } 239 | } 240 | ``` 241 | 242 | ## Usage Examples 243 | 244 | ### Success Message 245 | 246 | ```php 247 | flash()->success('Operation completed successfully!'); 248 | ``` 249 | 250 | ### Error Message 251 | 252 | ```php 253 | flash()->error('An error occurred.'); 254 | ``` 255 | 256 | ### Info Message 257 | 258 | ```php 259 | flash()->info('This is an informational message.'); 260 | ``` 261 | 262 | ### Warning Message 263 | 264 | ```php 265 | flash()->warning('This is a warning message.'); 266 | ``` 267 | 268 | ### Passing Options 269 | 270 | ```php 271 | flash()->success('Custom message with options.', ['timeout' => 3000, 'position' => 'bottom-left']); 272 | ``` 273 | 274 | ### Using presets 275 | 276 | Define a preset in your `config/flasher.php`: 277 | 278 | ```php 279 | [ 289 | 'entity_saved' => [ 290 | 'type' => 'success', 291 | 'title' => 'Entity Saved', 292 | 'message' => 'The entity has been saved successfully.', 293 | ], 294 | 'entity_deleted' => [ 295 | 'type' => 'warning', 296 | 'title' => 'Entity Deleted', 297 | 'message' => 'The entity has been deleted.', 298 | ], 299 | ], 300 | ]); 301 | ``` 302 | 303 | Use the preset in your controller: 304 | 305 | ```php 306 | preset('entity_saved'); 326 | 327 | return redirect()->route('books.index'); 328 | } 329 | 330 | /** 331 | * Delete an existing book entity. 332 | * 333 | * @return RedirectResponse 334 | */ 335 | public function deleteBook(): RedirectResponse 336 | { 337 | // Your deletion logic here (e.g., finding and deleting the book) 338 | 339 | // Trigger the 'entity_deleted' preset 340 | flash()->preset('entity_deleted'); 341 | 342 | return redirect()->route('books.index'); 343 | } 344 | } 345 | ``` 346 | 347 | ## Adapters Overview 348 | 349 | PHPFlasher supports various adapters to integrate seamlessly with different frontend libraries. Below is an overview of available adapters for Laravel: 350 | 351 | | Adapter Repository | Description | 352 | |-----------------------------------------------------------------------------------------|--------------------------------| 353 | | [flasher-laravel](https://github.com/php-flasher/flasher-laravel) | Laravel framework adapter | 354 | | [flasher-toastr-laravel](https://github.com/php-flasher/flasher-toastr-laravel) | Toastr adapter for Laravel | 355 | | [flasher-noty-laravel](https://github.com/php-flasher/flasher-noty-laravel) | Noty adapter for Laravel | 356 | | [flasher-notyf-laravel](https://github.com/php-flasher/flasher-notyf-laravel) | Notyf adapter for Laravel | 357 | | [flasher-sweetalert-laravel](https://github.com/php-flasher/flasher-sweetalert-laravel) | SweetAlert adapter for Laravel | 358 | 359 | > **Note:** Each adapter has its own repository. For detailed installation and usage instructions, please refer to the [Official Documentation](https://php-flasher.io). 360 | 361 | ## Official Documentation 362 | 363 | 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. 364 | 365 | ## Contributors and sponsors 366 | 367 | Join our team of contributors and make a lasting impact on our project! 368 | 369 | We are always looking for passionate individuals who want to contribute their skills and ideas. 370 | Whether you're a developer, designer, or simply have a great idea, we welcome your participation and collaboration. 371 | 372 | Shining stars of our community: 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 |
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

💻 📖
394 | 395 | 396 | 397 | 398 | 399 | 400 | ## Contact 401 | 402 | PHPFlasher is being actively developed by yoeunes. 403 | You can reach out with questions, bug reports, or feature requests on any of the following: 404 | 405 | - [Github Issues](https://github.com/php-flasher/php-flasher/issues) 406 | - [Github](https://github.com/yoeunes) 407 | - [Twitter](https://twitter.com/yoeunes) 408 | - [Linkedin](https://www.linkedin.com/in/younes--ennaji/) 409 | - [Email me directly](mailto:younes.ennaji.pro@gmail.com) 410 | 411 | ## License 412 | 413 | PHPFlasher is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). 414 | 415 |

Made with ❤️ by Younes ENNAJI

416 | -------------------------------------------------------------------------------- /Resources/config.php: -------------------------------------------------------------------------------- 1 | PHPFlasher configuration 15 | */ 16 | return Configuration::from([ 17 | // Default notification library (e.g., 'flasher', 'toastr', 'noty', 'notyf', 'sweetalert') 18 | 'default' => 'flasher', 19 | 20 | // Path to the main PHPFlasher JavaScript file 21 | 'main_script' => '/vendor/flasher/flasher.min.js', 22 | 23 | // List of CSS files to style your notifications 24 | 'styles' => [ 25 | '/vendor/flasher/flasher.min.css', 26 | ], 27 | 28 | // Set global options for all notifications (optional) 29 | // 'options' => [ 30 | // 'timeout' => 5000, // Time in milliseconds before the notification disappears 31 | // 'position' => 'top-right', // Where the notification appears on the screen 32 | // ], 33 | 34 | // Automatically inject JavaScript and CSS assets into your HTML pages 35 | 'inject_assets' => true, 36 | 37 | // Enable message translation using Laravel's translation service 38 | 'translate' => true, 39 | 40 | // URL patterns to exclude from asset injection and flash_bag conversion 41 | 'excluded_paths' => [], 42 | 43 | // Map Laravel flash message keys to notification types 44 | 'flash_bag' => [ 45 | 'success' => ['success'], 46 | 'error' => ['error', 'danger'], 47 | 'warning' => ['warning', 'alarm'], 48 | 'info' => ['info', 'notice', 'alert'], 49 | ], 50 | 51 | // Set criteria to filter which notifications are displayed (optional) 52 | // 'filter' => [ 53 | // 'limit' => 5, // Maximum number of notifications to show at once 54 | // ], 55 | 56 | // Define notification presets to simplify notification creation (optional) 57 | // 'presets' => [ 58 | // 'entity_saved' => [ 59 | // 'type' => 'success', 60 | // 'title' => 'Entity saved', 61 | // 'message' => 'Entity saved successfully', 62 | // ], 63 | // ], 64 | ]); 65 | -------------------------------------------------------------------------------- /Storage/SessionBag.php: -------------------------------------------------------------------------------- 1 | session->get(self::ENVELOPES_NAMESPACE, []); 48 | 49 | return $envelopes; 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | * 55 | * Stores notification envelopes in the session. 56 | * 57 | * @param Envelope[] $envelopes The notification envelopes to store 58 | */ 59 | public function set(array $envelopes): void 60 | { 61 | $this->session->put(self::ENVELOPES_NAMESPACE, $envelopes); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Support/PluginServiceProvider.php: -------------------------------------------------------------------------------- 1 | plugin = $this->createPlugin(); 55 | 56 | $this->registerConfiguration(); 57 | $this->afterRegister(); 58 | } 59 | 60 | /** 61 | * Bootstrap services after all providers are registered. 62 | * 63 | * This method: 64 | * 1. Registers the plugin factory 65 | * 2. Calls the afterBoot hook for customization 66 | */ 67 | public function boot(): void 68 | { 69 | $this->registerFactory(); 70 | $this->afterBoot(); 71 | } 72 | 73 | /** 74 | * Get the plugin's configuration file path. 75 | * 76 | * @return string The absolute path to the configuration file 77 | */ 78 | public function getConfigurationFile(): string 79 | { 80 | return rtrim($this->getResourcesDir(), '/').'/config.php'; 81 | } 82 | 83 | /** 84 | * Get a configuration value with optional default. 85 | * 86 | * @param string|null $key The configuration key to retrieve, or null for all config 87 | * @param mixed $default The default value to return if the key doesn't exist 88 | * 89 | * @return mixed The configuration value 90 | */ 91 | protected function getConfig(?string $key = null, mixed $default = null): mixed 92 | { 93 | /** @var Repository $config */ 94 | $config = $this->app->make('config'); 95 | 96 | return $key ? $config->get('flasher.'.$key, $default) : $config->get('flasher'); 97 | } 98 | 99 | /** 100 | * Get the plugin's resources directory path. 101 | * 102 | * @return string The absolute path to the resources directory 103 | */ 104 | protected function getResourcesDir(): string 105 | { 106 | $r = new \ReflectionClass($this); 107 | 108 | return pathinfo($r->getFileName() ?: '', \PATHINFO_DIRNAME).'/Resources/'; 109 | } 110 | 111 | /** 112 | * Register the plugin's configuration. 113 | * 114 | * This method merges the plugin's default configuration with any user-defined 115 | * configuration and registers it with Laravel's config repository. 116 | */ 117 | protected function registerConfiguration(): void 118 | { 119 | if ($this->app instanceof CachesConfiguration && $this->app->configurationIsCached()) { 120 | return; 121 | } 122 | 123 | $alias = $this->plugin->getAlias(); 124 | $config = $this->app->make('config'); 125 | 126 | $key = 'flasher' === $alias ? $alias : "flasher.plugins.$alias"; 127 | /** 128 | * @var array{ 129 | * scripts?: string|string[], 130 | * styles?: string|string[], 131 | * options?: array, 132 | * } $current 133 | */ 134 | $current = $config->get($key, []); 135 | 136 | $config->set($key, $this->plugin->normalizeConfig($current)); 137 | } 138 | 139 | /** 140 | * Hook method executed after registration. 141 | * 142 | * Child classes can override this method to add custom registration logic. 143 | */ 144 | protected function afterRegister(): void 145 | { 146 | } 147 | 148 | /** 149 | * Hook method executed after boot. 150 | * 151 | * Child classes can override this method to add custom boot logic. 152 | */ 153 | protected function afterBoot(): void 154 | { 155 | } 156 | 157 | /** 158 | * Register the plugin's notification factory. 159 | * 160 | * This method: 161 | * 1. Registers the factory as a singleton 162 | * 2. Registers any aliases for the factory 163 | * 3. Adds the factory to the factory locator 164 | */ 165 | protected function registerFactory(): void 166 | { 167 | $this->app->singleton($this->plugin->getServiceId(), function (Application $app) { 168 | $factory = $this->plugin->getFactory(); 169 | 170 | return new $factory($app->make('flasher.storage_manager')); 171 | }); 172 | 173 | $identifier = $this->plugin->getServiceId(); 174 | foreach ((array) $this->plugin->getServiceAliases() as $alias) { 175 | $this->app->alias($identifier, $alias); 176 | } 177 | 178 | $this->app->extend('flasher.factory_locator', function (NotificationFactoryLocator $factoryLocator, Application $app) { 179 | $factoryLocator->addFactory($this->plugin->getAlias(), fn () => $app->make($this->plugin->getServiceId())); 180 | 181 | return $factoryLocator; 182 | }); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /Template/BladeTemplateEngine.php: -------------------------------------------------------------------------------- 1 | $context The template variables 39 | * 40 | * @return string The rendered template 41 | */ 42 | public function render(string $name, array $context = []): string 43 | { 44 | return $this->blade->make($name, $context)->render(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Translation/Translator.php: -------------------------------------------------------------------------------- 1 | $parameters The parameters for variable substitution 43 | * @param string|null $locale The locale to use, or null for default 44 | * 45 | * @return string The translated string 46 | */ 47 | public function translate(string $id, array $parameters = [], ?string $locale = null): string 48 | { 49 | $parameters = $this->formatParameters($parameters); 50 | 51 | $translation = $this->translator->has('flasher::messages.'.$id, $locale) 52 | ? $this->translator->get('flasher::messages.'.$id, $parameters, $locale) 53 | : ($this->translator->has('messages.'.$id, $locale) 54 | ? $this->translator->get('messages.'.$id, $parameters, $locale) 55 | : $this->translator->get($id, $parameters, $locale)); 56 | 57 | if (!\is_string($translation)) { 58 | return $id; 59 | } 60 | 61 | return $translation; 62 | } 63 | 64 | /** 65 | * {@inheritdoc} 66 | * 67 | * Gets the current locale from Laravel's translator. 68 | * 69 | * @return string The current locale code 70 | */ 71 | public function getLocale(): string 72 | { 73 | return $this->translator->getLocale(); 74 | } 75 | 76 | /** 77 | * Formats the parameters by stripping the colon prefix from keys for Laravel's translator. 78 | * 79 | * This ensures compatibility between PHPFlasher's parameter format (:parameter) 80 | * and Laravel's parameter format (parameter). 81 | * 82 | * @param array $parameters The parameters with potential colon prefixes 83 | * 84 | * @return array The formatted parameters 85 | */ 86 | private function formatParameters(array $parameters): array 87 | { 88 | foreach ($parameters as $key => $value) { 89 | $parameters[ltrim($key, ':')] = $value; 90 | } 91 | 92 | return $parameters; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Translation/lang/ar/messages.php: -------------------------------------------------------------------------------- 1 | Key-value pairs of message identifiers and translations 16 | */ 17 | return Flasher\Prime\Translation\Messages::get('ar'); 18 | -------------------------------------------------------------------------------- /Translation/lang/de/messages.php: -------------------------------------------------------------------------------- 1 | Key-value pairs of message identifiers and translations 16 | */ 17 | return Flasher\Prime\Translation\Messages::get('de'); 18 | -------------------------------------------------------------------------------- /Translation/lang/en/messages.php: -------------------------------------------------------------------------------- 1 | Key-value pairs of message identifiers and translations 16 | */ 17 | return Flasher\Prime\Translation\Messages::get('en'); 18 | -------------------------------------------------------------------------------- /Translation/lang/es/messages.php: -------------------------------------------------------------------------------- 1 | Key-value pairs of message identifiers and translations 16 | */ 17 | return Flasher\Prime\Translation\Messages::get('es'); 18 | -------------------------------------------------------------------------------- /Translation/lang/fr/messages.php: -------------------------------------------------------------------------------- 1 | Key-value pairs of message identifiers and translations 16 | */ 17 | return Flasher\Prime\Translation\Messages::get('fr'); 18 | -------------------------------------------------------------------------------- /Translation/lang/pt/messages.php: -------------------------------------------------------------------------------- 1 | Key-value pairs of message identifiers and translations 16 | */ 17 | return Flasher\Prime\Translation\Messages::get('pt'); 18 | -------------------------------------------------------------------------------- /Translation/lang/ru/messages.php: -------------------------------------------------------------------------------- 1 | Key-value pairs of message identifiers and translations 16 | */ 17 | return Flasher\Prime\Translation\Messages::get('ru'); 18 | -------------------------------------------------------------------------------- /Translation/lang/zh/messages.php: -------------------------------------------------------------------------------- 1 | Key-value pairs of message identifiers and translations 16 | */ 17 | return Flasher\Prime\Translation\Messages::get('zh'); 18 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "php-flasher/flasher-laravel", 3 | "type": "library", 4 | "license": "MIT", 5 | "homepage": "https://php-flasher.io", 6 | "description": "Seamlessly integrate flash notifications into your Laravel applications with PHPFlasher. Enhance user feedback and engagement with minimal setup.", 7 | "keywords": [ 8 | "laravel", 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 | "illuminate/support": "^11.0|^12.0", 32 | "php-flasher/flasher": "^2.1.6" 33 | }, 34 | "autoload": { 35 | "psr-4": { 36 | "Flasher\\Laravel\\": "" 37 | } 38 | }, 39 | "config": { 40 | "preferred-install": "dist", 41 | "sort-packages": true 42 | }, 43 | "extra": { 44 | "phpstan": { 45 | "includes": [ 46 | "extension.neon" 47 | ] 48 | }, 49 | "laravel": { 50 | "providers": [ 51 | "Flasher\\Laravel\\FlasherServiceProvider" 52 | ], 53 | "aliases": { 54 | "Flasher": "Flasher\\Laravel\\Facade\\Flasher" 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /extension.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | stubFiles: 3 | - Phpstan/stubs/Repository.stub 4 | - Phpstan/stubs/ApplicationTestingHooks.stub 5 | --------------------------------------------------------------------------------