├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── pint.json ├── src ├── Commands │ └── MakeContextCommand.php ├── Concerns │ ├── ContextualPage.php │ └── ContextualResource.php ├── ContextServiceProvider.php ├── FilamentMultiContextManager.php ├── FilamentMultiContextServiceProvider.php └── Http │ └── Middleware │ └── ApplyContext.php └── stubs ├── ContextServiceProvider.stub └── config.stub /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `filament-multi-context` will be documented in this file. 4 | 5 | ## V2.1.2 - 2023-05-15 6 | 7 | ### What's Changed 8 | 9 | - Bump aglipanci/laravel-pint-action from 2.1.0 to 2.2.0 by @dependabot in https://github.com/artificertech/filament-multi-context/pull/38 10 | - Bump dependabot/fetch-metadata from 1.3.6 to 1.4.0 by @dependabot in https://github.com/artificertech/filament-multi-context/pull/40 11 | - 📌 Loosen up Filament version constraint by @juliomotol in https://github.com/artificertech/filament-multi-context/pull/42 12 | 13 | **Full Changelog**: https://github.com/artificertech/filament-multi-context/compare/v2.1.1...V2.1.2 14 | 15 | ## v2.1.1 - 2023-03-02 16 | 17 | ### What's Changed 18 | 19 | - Bump dependabot/fetch-metadata from 1.3.4 to 1.3.5 by @dependabot in https://github.com/artificertech/filament-multi-context/pull/29 20 | - Update README.md by @weeg in https://github.com/artificertech/filament-multi-context/pull/30 21 | - Bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 by @dependabot in https://github.com/artificertech/filament-multi-context/pull/33 22 | - bump composer deps for laravel 10 support by @josefbehr in https://github.com/artificertech/filament-multi-context/pull/35 23 | - Bump aglipanci/laravel-pint-action from 1.0.0 to 2.1.0 by @dependabot in https://github.com/artificertech/filament-multi-context/pull/31 24 | 25 | ### New Contributors 26 | 27 | - @weeg made their first contribution in https://github.com/artificertech/filament-multi-context/pull/30 28 | - @josefbehr made their first contribution in https://github.com/artificertech/filament-multi-context/pull/35 29 | 30 | **Full Changelog**: https://github.com/artificertech/filament-multi-context/compare/v2.1.0...v2.1.1 31 | 32 | ## v2.1.0 - 2022-11-03 33 | 34 | ### What's Changed 35 | 36 | - Bump dependabot/fetch-metadata from 1.3.3 to 1.3.4 by @dependabot in https://github.com/artificertech/filament-multi-context/pull/22 37 | - 🐛 Should register context livewire compnent aliases by @juliomotol in https://github.com/artificertech/filament-multi-context/pull/26 38 | - 👽️ Sync `ContextServiceProvider::packageRegistered()` behavior with parent class by @juliomotol in https://github.com/artificertech/filament-multi-context/pull/27 39 | - allow guard customization for contexts by @coleshirley in https://github.com/artificertech/filament-multi-context/pull/28 40 | 41 | ### New Contributors 42 | 43 | - @juliomotol made their first contribution in https://github.com/artificertech/filament-multi-context/pull/26 44 | 45 | **Full Changelog**: https://github.com/artificertech/filament-multi-context/compare/v2.0.1...v2.1.0 46 | 47 | ## v2.0.1 - 2022-09-27 48 | 49 | ### What's Changed 50 | 51 | - fix run-tests workflow by @coleshirley in https://github.com/artificertech/filament-multi-context/pull/21 52 | - Fix config file not being created for windows filesystems by @iotronlab in https://github.com/artificertech/filament-multi-context/pull/17 53 | 54 | ### New Contributors 55 | 56 | - @iotronlab made their first contribution in https://github.com/artificertech/filament-multi-context/pull/17 57 | 58 | **Full Changelog**: https://github.com/artificertech/filament-multi-context/compare/V2.0.0...v2.0.1 59 | 60 | ## V2.0.0 - 2022-09-06 61 | 62 | ### What's Changed 63 | 64 | - Bump dependabot/fetch-metadata from 1.3.1 to 1.3.3 by @dependabot in https://github.com/artificertech/filament-multi-context/pull/8 65 | - 2.0 Release Canidate by @coleshirley in https://github.com/artificertech/filament-multi-context/pull/11 66 | - set the context using the string key in forAllContexts by @coleshirley in https://github.com/artificertech/filament-multi-context/pull/12 67 | - bringing back context traits to fix url generation by @coleshirley in https://github.com/artificertech/filament-multi-context/pull/14 68 | - Bump aglipanci/laravel-pint-action from 0.1.0 to 1.0.0 by @dependabot in https://github.com/artificertech/filament-multi-context/pull/15 69 | 70 | **Full Changelog**: https://github.com/artificertech/filament-multi-context/compare/v1.0.0...V2.0.0 71 | 72 | ## v1.0.0 - 2022-07-01 73 | 74 | **Full Changelog**: https://github.com/artificertech/filament-multi-context/compare/v1.0-beta.3...v1.0.0 75 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) artificertech 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A package for adding multiple contexts to the filament admin panel 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/artificertech/filament-multi-context.svg?style=flat-square)](https://packagist.org/packages/artificertech/filament-multi-context) 4 | [![GitHub Tests Action Status](https://img.shields.io/github/workflow/status/artificertech/filament-multi-context/run-tests?label=tests)](https://github.com/artificertech/filament-multi-context/actions?query=workflow%3Arun-tests+branch%3Amain) 5 | [![GitHub Code Style Action Status](https://img.shields.io/github/workflow/status/artificertech/filament-multi-context/Check%20&%20fix%20styling?label=code%20style)](https://github.com/artificertech/filament-multi-context/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amain) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/artificertech/filament-multi-context.svg?style=flat-square)](https://packagist.org/packages/artificertech/filament-multi-context) 7 | 8 | This package allows you to register multiple filament contexts in your 9 | application with their own set of resources and pages 10 | 11 | ## Installation 12 | 13 | You can install the package via composer: 14 | 15 | ```bash 16 | composer require artificertech/filament-multi-context 17 | ``` 18 | 19 | ## Usage 20 | 21 | create a new filament context using 22 | 23 | ```bash 24 | php artisan make:filament-context FilamentTeams 25 | ``` 26 | 27 | The above command will create the following files and directories: 28 | 29 | ``` 30 | app/FilamentTeams/Pages/ 31 | app/FilamentTeams/Resources/ 32 | app/FilamentTeams/Widgets/ 33 | app/Providers/FilamentTeamsServiceProvider.php 34 | config/filament-teams.php 35 | ``` 36 | 37 | `Filament` cannot be passed as a context to this command as it is reserved for 38 | the default filament installation 39 | 40 | ### **_Register Provider:_** 41 | Be sure to add the `FilamentTeamsServiceProvider` class to your providers array in `config/app.php` 42 | 43 | ### Adding Resources and Pages 44 | You may now add filament resources in your FilamentTeams directories. 45 | 46 | > **_Context Traits:_** be sure to add the ContextualPage and ContextualResource 47 | > traits to their associated classes inside of your context directories. (I 48 | > tried really hard with v2 to make this unnecessary but sadly here we are). 49 | > Without this when filament generates navigation links it will try to use 50 | > `filament.pages.*` and `filament.resources.{resource}.*` instead of 51 | > `{context}.pages.*` and `{context}.resources.{resource}.*` as the route names 52 | 53 | #### ContextualPage & ContextualResource traits 54 | 55 | Pages: 56 | 57 | ```php 58 | namespace App\FilamentTeams\Pages; 59 | 60 | use Artificertech\FilamentMultiContext\Concerns\ContextualPage; 61 | use Filament\Pages\Page; 62 | 63 | class Dashboard extends Page 64 | { 65 | use ContextualPage; 66 | } 67 | ``` 68 | 69 | Resources: 70 | 71 | ```php 72 | namespace App\FilamentTeams\Resources; 73 | 74 | use Artificertech\FilamentMultiContext\Concerns\ContextualResource; 75 | use Filament\Resources\Resource; 76 | 77 | class UserResource extends Resource 78 | { 79 | use ContextualResource; 80 | } 81 | ``` 82 | 83 | ## Configuration 84 | 85 | The `config/filament-teams.php` file contains a subset of the 86 | `config/filament.php` configuration file. The values in the `filament-teams.php` 87 | file can be adjusted and will only affect the pages, resources, and widgets for 88 | the `filament-teams` context. 89 | 90 | Currently the configuration values that can be modified for a specific context 91 | are: 92 | 93 | ``` 94 | 'path' 95 | 'domain' 96 | 'pages' 97 | 'resources' 98 | 'widgets' 99 | 'livewire' 100 | 'middleware' 101 | ``` 102 | 103 | ### ContextServiceProvider 104 | 105 | Your `ContextServiceProvider` found in your 106 | `app/Providers/FilamentTeamsServiceProvider.php` is an extension of the Filament 107 | `PluginServiceProvder` so features of the `PluginServiceProvider` may be used 108 | for your context. Any UserMenuItems, scripts, styles, or scriptData added to 109 | the provider will be registered for that context only 110 | 111 | ### Custom Page and Resource Routing 112 | 113 | If you would like more control over the way pages and resources are routed you 114 | may override the `componentRoutes()` function in your 115 | `FilamentTeamsServiceProvider` 116 | 117 | ```php 118 | protected function componentRoutes(): callable 119 | { 120 | return function () { 121 | Route::name('pages.')->group(function (): void { 122 | foreach (Facades\Filament::getPages() as $page) { 123 | Route::group([], $page::getRoutes()); 124 | } 125 | }); 126 | 127 | Route::name('resources.')->group(function (): void { 128 | foreach (Facades\Filament::getResources() as $resource) { 129 | Route::group([], $resource::getRoutes()); 130 | } 131 | }); 132 | }; 133 | } 134 | ``` 135 | 136 | ### Changing the context guard 137 | 138 | By default all contexts will use the guard defined in the primary `filament.php` 139 | config file. However if you need to specify the guard for a specific context you 140 | may add the following lines to your context config file: 141 | 142 | ```php 143 | /* 144 | |-------------------------------------------------------------------------- 145 | | Auth 146 | |-------------------------------------------------------------------------- 147 | | 148 | | This is the configuration that Filament will use to handle authentication 149 | | into the admin panel. 150 | | 151 | */ 152 | 153 | 'auth' => [ 154 | 'guard' => 'my-custom-guard', 155 | ], 156 | ``` 157 | 158 | ## !!! The Filament Facade 159 | 160 | In order for this package to work the `filament` app service has been overriden. 161 | Each context is represented by its own `Filament\FilamentManager` object. Within 162 | your application calls to the filament facade (such as `Filament::serving`) will 163 | be proxied to the appropriate `Filament\FilamentManager` object based on the 164 | current context of your application (which is determined by the route of the 165 | request) 166 | 167 | ### Context Functions 168 | 169 | The following functions have been added to facilitate multiple 170 | `Filament\FilamentManger` objects in your application: 171 | 172 | ```php 173 | // retrieve the string name of the current application context 174 | // defaults to `filament` 175 | 176 | Filament::currentContext(): string 177 | ``` 178 | 179 | ```php 180 | // retrieve the Filament\FilamentManager object for the current app context 181 | 182 | Filament::getContext() 183 | ``` 184 | 185 | ```php 186 | // retrieve the array of Filament\FilamentManager objects keyed by the context name 187 | 188 | Filament::getContexts() 189 | ``` 190 | 191 | ```php 192 | // set the current app context. 193 | // Passing null or nothing sets the context to 'filament' 194 | 195 | Filament::setContext(string|null $context) 196 | ``` 197 | 198 | ```php 199 | // sets the context for the duration of the callback function, then resets it back to the original value 200 | Filament::forContext(string $context, function () { 201 | // ... 202 | }) 203 | ``` 204 | 205 | ```php 206 | // loops through each registered context (including the default 'filament' context), 207 | // sets that context as the current context, 208 | // runs the callback, then resets to the original value 209 | Filament::forAllContexts(function () { 210 | // ... 211 | }) 212 | ``` 213 | 214 | ```php 215 | // creates a new FilamentManager object and registers it under the $name context 216 | // this method is used by your ContextServiceProvider to register your context 217 | // you shouldn't need to use this method during normal development 218 | Filament::addContext(string $name) 219 | ``` 220 | 221 | ## Testing 222 | 223 | ```bash 224 | composer test 225 | ``` 226 | 227 | ## Changelog 228 | 229 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed 230 | recently. 231 | 232 | ## Contributing 233 | 234 | Please see 235 | [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for 236 | details. 237 | 238 | ## Security Vulnerabilities 239 | 240 | Please review [our security policy](../../security/policy) on how to report 241 | security vulnerabilities. 242 | 243 | ## Credits 244 | 245 | - [Cole Shirley](https://github.com/cole.shirley) 246 | - [All Contributors](../../contributors) 247 | 248 | ## License 249 | 250 | The MIT License (MIT). Please see [License File](LICENSE.md) for more 251 | information. 252 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "artificertech/filament-multi-context", 3 | "description": "a package for adding multiple contexts to the filament admin panel", 4 | "keywords": [ 5 | "artificertech", 6 | "laravel", 7 | "filament", 8 | "filament-multi-context" 9 | ], 10 | "homepage": "https://github.com/artificertech/filament-multi-context", 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Cole Shirley", 15 | "email": "cole.shirley@artificertech.com", 16 | "role": "Developer" 17 | } 18 | ], 19 | "require": { 20 | "php": "^8.0", 21 | "filament/filament": "^2.15.16", 22 | "illuminate/contracts": "^8.6|^9.0|^10.0", 23 | "spatie/laravel-package-tools": "^1.9.2" 24 | }, 25 | "require-dev": { 26 | "laravel/pint": "^1.1", 27 | "nunomaduro/collision": "^6.0", 28 | "nunomaduro/larastan": "^2.0.1", 29 | "orchestra/testbench": "^7.0|^8.0", 30 | "pestphp/pest": "^1.21", 31 | "pestphp/pest-plugin-laravel": "^1.1", 32 | "pestphp/pest-plugin-livewire": "^1.0", 33 | "phpstan/extension-installer": "^1.1", 34 | "phpstan/phpstan-deprecation-rules": "^1.0", 35 | "phpstan/phpstan-phpunit": "^1.0", 36 | "phpunit/phpunit": "^9.5", 37 | "spatie/laravel-ray": "^1.26" 38 | }, 39 | "autoload": { 40 | "psr-4": { 41 | "Artificertech\\FilamentMultiContext\\": "src" 42 | } 43 | }, 44 | "autoload-dev": { 45 | "psr-4": { 46 | "Artificertech\\FilamentMultiContext\\Tests\\": "tests", 47 | "Artificertech\\FilamentMultiContext\\Tests\\App\\": "tests/app", 48 | "Artificertech\\FilamentMultiContext\\Tests\\Database\\Factories\\": "tests/database/factories" 49 | } 50 | }, 51 | "scripts": { 52 | "analyse": "vendor/bin/phpstan analyse", 53 | "test": "vendor/bin/pest", 54 | "test-coverage": "vendor/bin/pest --coverage", 55 | "format": "vendor/bin/pint" 56 | }, 57 | "config": { 58 | "sort-packages": true, 59 | "allow-plugins": { 60 | "pestphp/pest-plugin": true, 61 | "phpstan/extension-installer": true 62 | } 63 | }, 64 | "extra": { 65 | "laravel": { 66 | "providers": [ 67 | "Artificertech\\FilamentMultiContext\\FilamentMultiContextServiceProvider" 68 | ] 69 | } 70 | }, 71 | "minimum-stability": "dev", 72 | "prefer-stable": true 73 | } 74 | -------------------------------------------------------------------------------- /pint.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": [ 3 | "build" 4 | ], 5 | "rules": { 6 | "concat_space": false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Commands/MakeContextCommand.php: -------------------------------------------------------------------------------- 1 | getContextInput()) 22 | ->trim('/') 23 | ->trim('\\') 24 | ->trim(' ') 25 | ->replace('/', '\\'); 26 | 27 | $this->copyStubs($context); 28 | 29 | $this->createDirectories($context); 30 | 31 | $this->info("Successfully created {$context} context!"); 32 | 33 | return static::SUCCESS; 34 | } 35 | 36 | public function getContextInput(): string 37 | { 38 | return $this->validateInput( 39 | fn () => $this->argument('name') ?? $this->askRequired('Name (e.g. `FilamentTeams`)', 'name'), 40 | 'name', 41 | ['required', 'not_in:filament'] 42 | ); 43 | } 44 | 45 | protected function copyStubs($context) 46 | { 47 | $serviceProviderClass = $context->afterLast('\\')->append('ServiceProvider'); 48 | 49 | $contextName = $context->afterLast('\\')->kebab(); 50 | 51 | $serviceProviderPath = $serviceProviderClass 52 | ->prepend('/') 53 | ->prepend(app_path('Providers')) 54 | ->append('.php'); 55 | 56 | $configPath = config_path($contextName->prepend('/')->append('.php')); 57 | 58 | $contextNamespace = $context 59 | ->replace('\\', '\\\\') 60 | ->prepend('\\\\') 61 | ->prepend('App'); 62 | 63 | if (! $this->option('force') && $this->checkForCollision([ 64 | $serviceProviderPath, 65 | ])) { 66 | return static::INVALID; 67 | } 68 | 69 | if (! $this->option('force') && $this->checkForCollision([ 70 | $configPath, 71 | ])) { 72 | return static::INVALID; 73 | } 74 | 75 | $this->copyStubToApp('ContextServiceProvider', $serviceProviderPath, [ 76 | 'class' => (string) $serviceProviderClass, 77 | 'name' => (string) $contextName, 78 | ]); 79 | 80 | $this->copyStubToApp('config', $configPath, [ 81 | 'namespace' => (string) $contextNamespace, 82 | 'path' => (string) $context->replace('\\', '/'), 83 | ]); 84 | } 85 | 86 | protected function createDirectories($context) 87 | { 88 | $directoryPath = app_path( 89 | (string) $context 90 | ->replace('\\', '/') 91 | ); 92 | 93 | app(Filesystem::class)->makeDirectory($directoryPath, force: $this->option('force')); 94 | app(Filesystem::class)->makeDirectory($directoryPath.'/Pages', force: $this->option('force')); 95 | app(Filesystem::class)->makeDirectory($directoryPath.'/Resources', force: $this->option('force')); 96 | app(Filesystem::class)->makeDirectory($directoryPath.'/Widgets', force: $this->option('force')); 97 | } 98 | 99 | protected function copyStubToApp(string $stub, string $targetPath, array $replacements = []): void 100 | { 101 | $filesystem = app(Filesystem::class); 102 | 103 | if (! $this->fileExists($stubPath = base_path("stubs/filament/{$stub}.stub"))) { 104 | $stubPath = __DIR__."/../../stubs/{$stub}.stub"; 105 | } 106 | 107 | $stub = Str::of($filesystem->get($stubPath)); 108 | 109 | foreach ($replacements as $key => $replacement) { 110 | $stub = $stub->replace("{{ {$key} }}", $replacement); 111 | } 112 | 113 | $stub = (string) $stub; 114 | 115 | $this->writeFile($targetPath, $stub); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Concerns/ContextualPage.php: -------------------------------------------------------------------------------- 1 | app->booting(function () { 30 | $this->registerComponents(); 31 | }); 32 | 33 | $this->app->resolving('filament', function () { 34 | Filament::addContext(static::$name); 35 | 36 | Filament::forContext(static::$name, function () { 37 | Facades\Filament::registerPages($this->getPages()); 38 | Facades\Filament::registerResources($this->getResources()); 39 | Facades\Filament::registerWidgets($this->getWidgets()); 40 | 41 | Facades\Filament::serving(function () { 42 | if (Filament::currentContext() !== static::$name) { 43 | return; 44 | } 45 | 46 | Facades\Filament::registerUserMenuItems($this->getUserMenuItems()); 47 | Facades\Filament::registerScripts($this->getBeforeCoreScripts(), true); 48 | Facades\Filament::registerScripts($this->getScripts()); 49 | Facades\Filament::registerStyles($this->getStyles()); 50 | Facades\Filament::registerScriptData($this->getScriptData()); 51 | }); 52 | }); 53 | }); 54 | } 55 | 56 | public function bootingPackage() 57 | { 58 | Filament::setContext(static::$name); 59 | 60 | $this->bootRoutes(); 61 | } 62 | 63 | protected function bootRoutes() 64 | { 65 | if (! ($this->app instanceof CachesRoutes && $this->app->routesAreCached())) { 66 | Route::domain($this->contextConfig('domain')) 67 | ->middleware(array_merge([ApplyContext::class.':'.static::$name], $this->contextConfig('middleware.base'))) 68 | ->name(static::$name.'.') 69 | ->group(function () { 70 | Route::prefix($this->contextConfig('path'))->group(function () { 71 | Route::middleware($this->contextConfig('middleware.auth'))->group($this->componentRoutes()); 72 | }); 73 | }); 74 | } 75 | } 76 | 77 | protected function componentRoutes(): callable 78 | { 79 | return function () { 80 | Route::name('pages.')->group(function (): void { 81 | foreach (Facades\Filament::getPages() as $page) { 82 | Route::group([], $page::getRoutes()); 83 | } 84 | }); 85 | 86 | Route::name('resources.')->group(function (): void { 87 | foreach (Facades\Filament::getResources() as $resource) { 88 | Route::group([], $resource::getRoutes()); 89 | } 90 | }); 91 | }; 92 | } 93 | 94 | public function packageBooted(): void 95 | { 96 | parent::packageBooted(); 97 | 98 | Filament::setContext(); 99 | 100 | $this->bootLivewireComponents(); 101 | } 102 | 103 | protected function registerComponents(): void 104 | { 105 | $this->pages = $this->contextConfig('pages.register') ?? []; 106 | $this->resources = $this->contextConfig('resources.register') ?? []; 107 | $this->widgets = $this->contextConfig('widgets.register') ?? []; 108 | 109 | $directory = $this->contextConfig('livewire.path'); 110 | $namespace = $this->contextConfig('livewire.namespace'); 111 | 112 | $this->registerComponentsFromDirectory( 113 | Page::class, 114 | $this->pages, 115 | $this->contextConfig('pages.path'), 116 | $this->contextConfig('pages.namespace'), 117 | ); 118 | 119 | $this->registerComponentsFromDirectory( 120 | Resource::class, 121 | $this->resources, 122 | $this->contextConfig('resources.path'), 123 | $this->contextConfig('resources.namespace'), 124 | ); 125 | 126 | $this->registerComponentsFromDirectory( 127 | Widget::class, 128 | $this->widgets, 129 | $this->contextConfig('widgets.path'), 130 | $this->contextConfig('widgets.namespace'), 131 | ); 132 | 133 | $filesystem = app(Filesystem::class); 134 | 135 | if (! $filesystem->isDirectory($directory)) { 136 | return; 137 | } 138 | foreach ($filesystem->allFiles($directory) as $file) { 139 | $fileClass = (string) Str::of($namespace) 140 | ->append('\\', $file->getRelativePathname()) 141 | ->replace(['/', '.php'], ['\\', '']); 142 | 143 | if ((new ReflectionClass($fileClass))->isAbstract()) { 144 | continue; 145 | } 146 | 147 | $filePath = Str::of($directory.'/'.$file->getRelativePathname()); 148 | 149 | if ($filePath->startsWith($this->contextConfig('resources.path')) && is_subclass_of($fileClass, Resource::class)) { 150 | $this->resources[] = $fileClass; 151 | 152 | continue; 153 | } 154 | 155 | if ($filePath->startsWith($this->contextConfig('pages.path')) && is_subclass_of($fileClass, Page::class)) { 156 | $this->pages[] = $fileClass; 157 | 158 | continue; 159 | } 160 | 161 | if ($filePath->startsWith($this->contextConfig('widgets.path')) && is_subclass_of($fileClass, Widget::class)) { 162 | $this->widgets[] = $fileClass; 163 | 164 | continue; 165 | } 166 | 167 | if (is_subclass_of($fileClass, RelationManager::class)) { 168 | continue; 169 | } 170 | 171 | if (! is_subclass_of($fileClass, Component::class)) { 172 | continue; 173 | } 174 | 175 | $livewireAlias = Str::of($fileClass) 176 | ->after($namespace.'\\') 177 | ->replace(['/', '\\'], '.') 178 | ->prepend(static::$name.'.') 179 | ->explode('.') 180 | ->map([Str::class, 'kebab']) 181 | ->implode('.'); 182 | 183 | $this->livewireComponents[$livewireAlias] = $fileClass; 184 | } 185 | } 186 | 187 | protected function registerComponentsFromDirectory(string $baseClass, array &$register, ?string $directory, ?string $namespace): void 188 | { 189 | if (blank($directory) || blank($namespace)) { 190 | return; 191 | } 192 | 193 | if (Str::of($directory)->startsWith($this->contextConfig('livewire.path'))) { 194 | return; 195 | } 196 | 197 | $filesystem = app(Filesystem::class); 198 | 199 | if (! $filesystem->exists($directory)) { 200 | return; 201 | } 202 | 203 | $register = array_merge( 204 | $register, 205 | collect($filesystem->allFiles($directory)) 206 | ->map(function (SplFileInfo $file) use ($namespace): string { 207 | return (string) Str::of($namespace) 208 | ->append('\\', $file->getRelativePathname()) 209 | ->replace(['/', '.php'], ['\\', '']); 210 | }) 211 | ->filter(fn (string $class): bool => is_subclass_of($class, $baseClass) && (! (new ReflectionClass($class))->isAbstract())) 212 | ->all(), 213 | ); 214 | } 215 | 216 | protected function contextConfig(string $key, string $default = null) 217 | { 218 | return Arr::get(config(static::$name), $key, $default); 219 | } 220 | 221 | protected function bootLivewireComponents(): void 222 | { 223 | foreach ($this->livewireComponents as $alias => $class) { 224 | Livewire::component($alias, $class); 225 | } 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/FilamentMultiContextManager.php: -------------------------------------------------------------------------------- 1 | contexts['filament'] = $filament; 21 | } 22 | 23 | public function setContext(string $context = null) 24 | { 25 | $this->currentContext = $context; 26 | 27 | return $this; 28 | } 29 | 30 | public function currentContext(): string 31 | { 32 | return $this->currentContext ?? 'filament'; 33 | } 34 | 35 | public function getContext() 36 | { 37 | return $this->contexts[$this->currentContext ?? 'filament']; 38 | } 39 | 40 | public function getContexts(): array 41 | { 42 | return $this->contexts; 43 | } 44 | 45 | public function addContext(string $name) 46 | { 47 | $this->contexts[$name] = new FilamentManager(); 48 | 49 | return $this; 50 | } 51 | 52 | public function forContext(string $context, callable $callback) 53 | { 54 | $currentContext = Filament::currentContext(); 55 | 56 | Filament::setContext($context); 57 | 58 | $callback(); 59 | 60 | Filament::setContext($currentContext); 61 | 62 | return $this; 63 | } 64 | 65 | public function forAllContexts(callable $callback) 66 | { 67 | $currentContext = Filament::currentContext(); 68 | 69 | foreach ($this->contexts as $key => $context) { 70 | Filament::setContext($key); 71 | 72 | $callback(); 73 | } 74 | 75 | Filament::setContext($currentContext); 76 | 77 | return $this; 78 | } 79 | 80 | public function auth(): Guard 81 | { 82 | $context = $this->currentContext(); 83 | 84 | return auth()->guard(config("{$context}.auth.guard", config('filament.auth.guard'))); 85 | } 86 | 87 | /** 88 | * Dynamically handle calls into the filament instance. 89 | * 90 | * @param string $method 91 | * @param array $parameters 92 | * @return mixed 93 | */ 94 | public function __call($method, $parameters) 95 | { 96 | $response = $this->forwardCallTo($this->getContext(), $method, $parameters); 97 | 98 | if ($response instanceof FilamentManager) { 99 | return $this; 100 | } 101 | 102 | return $response; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/FilamentMultiContextServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('filament-multi-context') 22 | ->hasCommand(MakeContextCommand::class); 23 | } 24 | 25 | public function packageRegistered(): void 26 | { 27 | $this->app->extend('filament', function ($service, $app) { 28 | return new FilamentMultiContextManager($service); 29 | }); 30 | } 31 | 32 | public function packageBooted(): void 33 | { 34 | Livewire::addPersistentMiddleware([ 35 | ApplyContext::class, 36 | ]); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Http/Middleware/ApplyContext.php: -------------------------------------------------------------------------------- 1 | env('FILAMENT_PATH', 'admin'), 29 | 30 | /* 31 | |-------------------------------------------------------------------------- 32 | | Filament Domain 33 | |-------------------------------------------------------------------------- 34 | | 35 | | You may change the domain where Filament should be active. If the domain 36 | | is empty, all domains will be valid. 37 | | 38 | */ 39 | 40 | 'domain' => env('FILAMENT_DOMAIN'), 41 | 42 | /* 43 | |-------------------------------------------------------------------------- 44 | | Pages 45 | |-------------------------------------------------------------------------- 46 | | 47 | | This is the namespace and directory that Filament will automatically 48 | | register pages from. You may also register pages here. 49 | | 50 | */ 51 | 52 | 'pages' => [ 53 | 'namespace' => '{{ namespace }}\\Pages', 54 | 'path' => app_path('{{ path }}/Pages'), 55 | 'register' => [ 56 | Pages\Dashboard::class, 57 | ], 58 | ], 59 | 60 | /* 61 | |-------------------------------------------------------------------------- 62 | | Resources 63 | |-------------------------------------------------------------------------- 64 | | 65 | | This is the namespace and directory that Filament will automatically 66 | | register resources from. You may also register resources here. 67 | | 68 | */ 69 | 70 | 'resources' => [ 71 | 'namespace' => '{{ namespace }}\\Resources', 72 | 'path' => app_path('{{ path }}/Resources'), 73 | 'register' => [], 74 | ], 75 | 76 | /* 77 | |-------------------------------------------------------------------------- 78 | | Widgets 79 | |-------------------------------------------------------------------------- 80 | | 81 | | This is the namespace and directory that Filament will automatically 82 | | register dashboard widgets from. You may also register widgets here. 83 | | 84 | */ 85 | 86 | 'widgets' => [ 87 | 'namespace' => '{{ namespace }}\\Widgets', 88 | 'path' => app_path('{{ path }}/Widgets'), 89 | 'register' => [ 90 | Widgets\AccountWidget::class, 91 | Widgets\FilamentInfoWidget::class, 92 | ], 93 | ], 94 | 95 | /* 96 | |-------------------------------------------------------------------------- 97 | | Livewire 98 | |-------------------------------------------------------------------------- 99 | | 100 | | This is the namespace and directory that Filament will automatically 101 | | register Livewire components inside. 102 | | 103 | */ 104 | 105 | 'livewire' => [ 106 | 'namespace' => '{{ namespace }}', 107 | 'path' => app_path('{{ path }}'), 108 | ], 109 | 110 | /* 111 | |-------------------------------------------------------------------------- 112 | | Middleware 113 | |-------------------------------------------------------------------------- 114 | | 115 | | You may customise the middleware stack that Filament uses to handle 116 | | requests. 117 | | 118 | */ 119 | 120 | 'middleware' => [ 121 | 'auth' => [ 122 | Authenticate::class, 123 | ], 124 | 'base' => [ 125 | EncryptCookies::class, 126 | AddQueuedCookiesToResponse::class, 127 | StartSession::class, 128 | AuthenticateSession::class, 129 | ShareErrorsFromSession::class, 130 | VerifyCsrfToken::class, 131 | SubstituteBindings::class, 132 | DispatchServingFilamentEvent::class, 133 | MirrorConfigToSubpackages::class, 134 | ], 135 | ], 136 | ]; 137 | --------------------------------------------------------------------------------