mirror of
https://github.com/filamentphp/filament.git
synced 2026-10-24 12:04:06 +08:00
Merge pull request #11258 from filamentphp/feature/cached-components
feature: Allow components to be cached
This commit is contained in:
@@ -43,6 +43,32 @@ Not sure where to start? Review the [Getting Started guide](getting-started) to
|
||||
## Using other Filament packages
|
||||
The Filament Panel Builder pre-installs the [Form Builder](/docs/forms), [Table Builder](/docs/tables), [Notifications](/docs/notifications), [Actions](/docs/actions), [Infolists](/docs/infolists), and [Widgets](/docs/widgets) packages. No other installation steps are required to use these packages within a panel.
|
||||
|
||||
## Improving Filament panel performance
|
||||
|
||||
### Caching Blade Icons
|
||||
|
||||
You may wish to consider running `php artisan icons:cache` locally, and also in your deployment script. This is because Filament uses the [Blade Icons](https://blade-ui-kit.com/blade-icons) package, which can be much more performant when cached.
|
||||
|
||||
### Caching Filament components
|
||||
|
||||
You may also wish to consider running `php artisan filament:cache-components` in your deployment script, especially if you have large numbers of components (resources, pages, widgets, relation managers, custom Livewire components, etc.). This will create cache files in the `bootstrap/cache/filament` directory of your application, which contain indexes for each type of component. This can significantly improve the performance of Filament in some apps, as it reduces the number of files that need to be scanned and auto-discovered for components.
|
||||
|
||||
However, if you are actively developing your app locally, you should avoid using this command, as it will prevent any new components from being discovered until the cache is cleared or rebuilt.
|
||||
|
||||
You can clear the cache at any time without rebuilding it by running `php artisan filament:clear-cached-components`.
|
||||
|
||||
### Enabling OPcache on your server
|
||||
|
||||
From the [Laravel Forge documentation](https://forge.laravel.com/docs/servers/php.html#opcache):
|
||||
|
||||
> Optimizing the PHP OPcache for production will configure OPcache to store your compiled PHP code in memory to greatly improve performance.
|
||||
|
||||
Please use a search engine to find the relevant OPcache setup instructions for your environment.
|
||||
|
||||
### Optimizing your Laravel app
|
||||
|
||||
You should also consider optimizing your Laravel app for production by running `php artisan optimize` in your deployment script. This will cache the configuration files and routes.
|
||||
|
||||
## Deploying to production
|
||||
|
||||
### Allowing users to access a panel
|
||||
@@ -73,10 +99,6 @@ class User extends Authenticatable implements FilamentUser
|
||||
|
||||
Learn more about [users](users).
|
||||
|
||||
### Caching Blade Icons
|
||||
|
||||
You may wish to consider using `php artisan icons:cache` in your deployment process, as Filament uses the [Blade Icons](https://blade-ui-kit.com/blade-icons) package, which can be much more performant when cached. This is very hardware-dependant though, and many servers do not require this command for Filament to run smoothly.
|
||||
|
||||
## Publishing configuration
|
||||
|
||||
You can publish the Filament package configuration (if needed) using the following command:
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Commands;
|
||||
|
||||
use Filament\Facades\Filament;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class CacheComponentsCommand extends Command
|
||||
{
|
||||
protected $description = 'Cache all components';
|
||||
|
||||
protected $signature = 'filament:cache-components';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$this->info('Caching registered components...');
|
||||
|
||||
foreach (Filament::getPanels() as $panel) {
|
||||
$panel->cacheComponents();
|
||||
}
|
||||
|
||||
$this->info('All done!');
|
||||
|
||||
return static::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Commands;
|
||||
|
||||
use Filament\Facades\Filament;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ClearCachedComponentsCommand extends Command
|
||||
{
|
||||
protected $description = 'Cache all components';
|
||||
|
||||
protected $signature = 'filament:clear-cached-components';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$this->info('Clearing cached components...');
|
||||
|
||||
foreach (Filament::getPanels() as $panel) {
|
||||
$panel->clearCachedComponents();
|
||||
}
|
||||
|
||||
$this->info('All done!');
|
||||
|
||||
return static::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -96,6 +96,8 @@ class FilamentServiceProvider extends PackageServiceProvider
|
||||
protected function getCommands(): array
|
||||
{
|
||||
$commands = [
|
||||
Commands\CacheComponentsCommand::class,
|
||||
Commands\ClearCachedComponentsCommand::class,
|
||||
Commands\MakeClusterCommand::class,
|
||||
Commands\MakePageCommand::class,
|
||||
Commands\MakePanelCommand::class,
|
||||
|
||||
@@ -46,10 +46,7 @@ class Panel extends Component
|
||||
|
||||
public static function make(): static
|
||||
{
|
||||
$static = app(static::class);
|
||||
$static->configure();
|
||||
|
||||
return $static;
|
||||
return app(static::class);
|
||||
}
|
||||
|
||||
public function default(bool $condition = true): static
|
||||
|
||||
@@ -97,11 +97,17 @@ trait HasComponents
|
||||
|
||||
protected bool | Closure $hasReadOnlyRelationManagersOnResourceViewPagesByDefault = true;
|
||||
|
||||
protected ?bool $hasCachedComponents = null;
|
||||
|
||||
/**
|
||||
* @param array<class-string> $pages
|
||||
*/
|
||||
public function pages(array $pages): static
|
||||
{
|
||||
if ($this->hasCachedComponents()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->pages = [
|
||||
...$this->pages,
|
||||
...$pages,
|
||||
@@ -120,6 +126,10 @@ trait HasComponents
|
||||
*/
|
||||
public function resources(array $resources): static
|
||||
{
|
||||
if ($this->hasCachedComponents()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->resources = [
|
||||
...$this->resources,
|
||||
...$resources,
|
||||
@@ -154,6 +164,10 @@ trait HasComponents
|
||||
*/
|
||||
public function widgets(array $widgets): static
|
||||
{
|
||||
if ($this->hasCachedComponents()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->widgets = [
|
||||
...$this->widgets,
|
||||
...$widgets,
|
||||
@@ -181,6 +195,10 @@ trait HasComponents
|
||||
|
||||
public function discoverPages(string $in, string $for): static
|
||||
{
|
||||
if ($this->hasCachedComponents()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->pageDirectories[] = $in;
|
||||
$this->pageNamespaces[] = $for;
|
||||
|
||||
@@ -218,6 +236,10 @@ trait HasComponents
|
||||
|
||||
public function discoverClusters(string $in, string $for): static
|
||||
{
|
||||
if ($this->hasCachedComponents()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->clusterDirectories[] = $in;
|
||||
$this->clusterNamespaces[] = $for;
|
||||
|
||||
@@ -261,6 +283,10 @@ trait HasComponents
|
||||
|
||||
public function discoverResources(string $in, string $for): static
|
||||
{
|
||||
if ($this->hasCachedComponents()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->resourceDirectories[] = $in;
|
||||
$this->resourceNamespaces[] = $for;
|
||||
|
||||
@@ -298,6 +324,10 @@ trait HasComponents
|
||||
|
||||
public function discoverWidgets(string $in, string $for): static
|
||||
{
|
||||
if ($this->hasCachedComponents()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->widgetDirectories[] = $in;
|
||||
$this->widgetNamespaces[] = $for;
|
||||
|
||||
@@ -329,11 +359,15 @@ trait HasComponents
|
||||
|
||||
public function discoverLivewireComponents(string $in, string $for): static
|
||||
{
|
||||
$component = [];
|
||||
if ($this->hasCachedComponents()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$components = [];
|
||||
|
||||
$this->discoverComponents(
|
||||
Component::class,
|
||||
$component,
|
||||
$components,
|
||||
directory: $in,
|
||||
namespace: $for,
|
||||
);
|
||||
@@ -434,6 +468,10 @@ trait HasComponents
|
||||
*/
|
||||
public function livewireComponents(array $components): static
|
||||
{
|
||||
if ($this->hasCachedComponents()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
foreach ($components as $component) {
|
||||
$this->queueLivewireComponentForRegistration($component);
|
||||
}
|
||||
@@ -443,60 +481,60 @@ trait HasComponents
|
||||
|
||||
protected function registerLivewireComponents(): void
|
||||
{
|
||||
$this->queueLivewireComponentForRegistration(DatabaseNotifications::class);
|
||||
$this->queueLivewireComponentForRegistration(EditProfile::class);
|
||||
$this->queueLivewireComponentForRegistration(GlobalSearch::class);
|
||||
$this->queueLivewireComponentForRegistration(Notifications::class);
|
||||
if (! $this->hasCachedComponents()) {
|
||||
$this->queueLivewireComponentForRegistration(DatabaseNotifications::class);
|
||||
$this->queueLivewireComponentForRegistration(EditProfile::class);
|
||||
$this->queueLivewireComponentForRegistration(GlobalSearch::class);
|
||||
$this->queueLivewireComponentForRegistration(Notifications::class);
|
||||
|
||||
if ($this->hasEmailVerification() && is_subclass_of($emailVerificationRouteAction = $this->getEmailVerificationPromptRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($emailVerificationRouteAction);
|
||||
}
|
||||
|
||||
if ($this->hasLogin() && is_subclass_of($loginRouteAction = $this->getLoginRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($loginRouteAction);
|
||||
}
|
||||
|
||||
if ($this->hasPasswordReset()) {
|
||||
if (is_subclass_of($requestPasswordResetRouteAction = $this->getRequestPasswordResetRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($requestPasswordResetRouteAction);
|
||||
if ($this->hasEmailVerification() && is_subclass_of($emailVerificationRouteAction = $this->getEmailVerificationPromptRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($emailVerificationRouteAction);
|
||||
}
|
||||
|
||||
if (is_subclass_of($resetPasswordRouteAction = $this->getResetPasswordRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($resetPasswordRouteAction);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->hasRegistration() && is_subclass_of($registrationRouteAction = $this->getRegistrationRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($registrationRouteAction);
|
||||
}
|
||||
|
||||
foreach ($this->getResources() as $resource) {
|
||||
foreach ($resource::getPages() as $pageRegistration) {
|
||||
$this->queueLivewireComponentForRegistration($pageRegistration->getPage());
|
||||
if ($this->hasLogin() && is_subclass_of($loginRouteAction = $this->getLoginRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($loginRouteAction);
|
||||
}
|
||||
|
||||
foreach ($resource::getRelations() as $relation) {
|
||||
if ($relation instanceof RelationGroup) {
|
||||
foreach ($relation->getManagers() as $groupedRelation) {
|
||||
$this->queueLivewireComponentForRegistration($this->normalizeRelationManagerClass($groupedRelation));
|
||||
}
|
||||
|
||||
continue;
|
||||
if ($this->hasPasswordReset()) {
|
||||
if (is_subclass_of($requestPasswordResetRouteAction = $this->getRequestPasswordResetRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($requestPasswordResetRouteAction);
|
||||
}
|
||||
|
||||
$this->queueLivewireComponentForRegistration($this->normalizeRelationManagerClass($relation));
|
||||
if (is_subclass_of($resetPasswordRouteAction = $this->getResetPasswordRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($resetPasswordRouteAction);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($resource::getWidgets() as $widget) {
|
||||
$this->queueLivewireComponentForRegistration($this->normalizeWidgetClass($widget));
|
||||
if ($this->hasRegistration() && is_subclass_of($registrationRouteAction = $this->getRegistrationRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($registrationRouteAction);
|
||||
}
|
||||
|
||||
foreach ($this->getResources() as $resource) {
|
||||
foreach ($resource::getPages() as $pageRegistration) {
|
||||
$this->queueLivewireComponentForRegistration($pageRegistration->getPage());
|
||||
}
|
||||
|
||||
foreach ($resource::getRelations() as $relation) {
|
||||
if ($relation instanceof RelationGroup) {
|
||||
foreach ($relation->getManagers() as $groupedRelation) {
|
||||
$this->queueLivewireComponentForRegistration($this->normalizeRelationManagerClass($groupedRelation));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->queueLivewireComponentForRegistration($this->normalizeRelationManagerClass($relation));
|
||||
}
|
||||
|
||||
foreach ($resource::getWidgets() as $widget) {
|
||||
$this->queueLivewireComponentForRegistration($this->normalizeWidgetClass($widget));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->livewireComponents as $componentName => $componentClass) {
|
||||
Livewire::component($componentName, $componentClass);
|
||||
}
|
||||
|
||||
$this->livewireComponents = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -561,4 +599,78 @@ trait HasComponents
|
||||
|
||||
return $this->clusteredComponents[$cluster] ?? [];
|
||||
}
|
||||
|
||||
public function hasCachedComponents(): bool
|
||||
{
|
||||
return $this->hasCachedComponents ??= ((! app()->runningInConsole()) && app(Filesystem::class)->exists($this->getComponentCachePath()));
|
||||
}
|
||||
|
||||
public function cacheComponents(): void
|
||||
{
|
||||
$this->hasCachedComponents = false;
|
||||
|
||||
$cachePath = $this->getComponentCachePath();
|
||||
|
||||
$filesystem = app(Filesystem::class);
|
||||
|
||||
$filesystem->ensureDirectoryExists((string) str($cachePath)->beforeLast(DIRECTORY_SEPARATOR));
|
||||
|
||||
$filesystem->put(
|
||||
$cachePath,
|
||||
'<?php return ' . var_export([
|
||||
'livewireComponents' => $this->livewireComponents,
|
||||
'clusters' => $this->clusters,
|
||||
'clusteredComponents' => $this->clusteredComponents,
|
||||
'clusterDirectories' => $this->clusterDirectories,
|
||||
'clusterNamespaces' => $this->clusterNamespaces,
|
||||
'pages' => $this->pages,
|
||||
'pageDirectories' => $this->pageDirectories,
|
||||
'pageNamespaces' => $this->pageNamespaces,
|
||||
'resources' => $this->resources,
|
||||
'resourceDirectories' => $this->resourceDirectories,
|
||||
'resourceNamespaces' => $this->resourceNamespaces,
|
||||
'widgets' => $this->widgets,
|
||||
'widgetDirectories' => $this->widgetDirectories,
|
||||
'widgetNamespaces' => $this->widgetNamespaces,
|
||||
], true) . ';',
|
||||
);
|
||||
|
||||
$this->hasCachedComponents = true;
|
||||
}
|
||||
|
||||
public function restoreCachedComponents(): void
|
||||
{
|
||||
if (! $this->hasCachedComponents()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cache = require $this->getComponentCachePath();
|
||||
|
||||
$this->livewireComponents = $cache['livewireComponents'] ?? [];
|
||||
$this->clusters = $cache['clusters'] ?? [];
|
||||
$this->clusteredComponents = $cache['clusteredComponents'] ?? [];
|
||||
$this->clusterDirectories = $cache['clusterDirectories'] ?? [];
|
||||
$this->clusterNamespaces = $cache['clusterNamespaces'] ?? [];
|
||||
$this->pages = $cache['pages'] ?? [];
|
||||
$this->pageDirectories = $cache['pageDirectories'] ?? [];
|
||||
$this->pageNamespaces = $cache['pageNamespaces'] ?? [];
|
||||
$this->resources = $cache['resources'] ?? [];
|
||||
$this->resourceDirectories = $cache['resourceDirectories'] ?? [];
|
||||
$this->resourceNamespaces = $cache['resourceNamespaces'] ?? [];
|
||||
$this->widgets = $cache['widgets'] ?? [];
|
||||
$this->widgetDirectories = $cache['widgetDirectories'] ?? [];
|
||||
$this->widgetNamespaces = $cache['widgetNamespaces'] ?? [];
|
||||
}
|
||||
|
||||
public function clearCachedComponents(): void
|
||||
{
|
||||
app(Filesystem::class)->delete($this->getComponentCachePath());
|
||||
|
||||
$this->hasCachedComponents = false;
|
||||
}
|
||||
|
||||
public function getComponentCachePath(): string
|
||||
{
|
||||
return (config('filament.cache_path') ?? base_path('bootstrap/cache/filament')) . DIRECTORY_SEPARATOR . "panels/{$this->getId()}.php";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,31 @@
|
||||
|
||||
namespace Filament\Panel\Concerns;
|
||||
|
||||
use Exception;
|
||||
|
||||
trait HasId
|
||||
{
|
||||
protected string $id;
|
||||
|
||||
public function id(string $id): static
|
||||
{
|
||||
if (isset($this->id)) {
|
||||
throw new Exception("The panel has already been registered with the ID [{$this->id}].");
|
||||
}
|
||||
|
||||
$this->id = $id;
|
||||
$this->configure();
|
||||
$this->restoreCachedComponents();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
if (! isset($this->id)) {
|
||||
throw new Exception('A panel has been registered without an `id()`.');
|
||||
}
|
||||
|
||||
return $this->id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ return [
|
||||
| Default Filesystem Disk
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the storage disk Filament will use to put media. You may use any
|
||||
| of the disks defined in the `config/filesystems.php`.
|
||||
| This is the storage disk Filament will use to store files. You may use
|
||||
| any of the disks defined in the `config/filesystems.php`.
|
||||
|
|
||||
*/
|
||||
|
||||
@@ -56,6 +56,20 @@ return [
|
||||
|
||||
'assets_path' => null,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the directory that Filament will use to store cache files that
|
||||
| are used to optimize the registration of components.
|
||||
|
|
||||
| After changing the path, you should run `php artisan filament:cache-components`.
|
||||
|
|
||||
*/
|
||||
|
||||
'cache_path' => base_path('bootstrap/cache/filament'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Livewire Loading Delay
|
||||
|
||||
Reference in New Issue
Block a user