panel:cache
Runs discovery once and writes the result to a manifest, so it never runs in a request. Reach for it on every deploy, and never in development.
php artisan panel:cacheINFO Panels cached: 2 panels, 1 resources, 5 pages, 4 widgets.Signature
panel:cacheNo arguments and no options. The command caches every registered panel, because a manifest holding some panels and not others would be a cache whose absence is indistinguishable from a panel that discovers nothing.
// PandaPanel\Console\Commands\CachePanelsCommand
public function handle(PandaPanel\Cache\PanelManifest $manifest, PandaPanel\Core\PanelRegistry $registry): int2
What it writes
bootstrap/cache/panels.php, through PandaPanel\Cache\PanelManifest::path():
<?php
// Generated by "php artisan panel:cache". Do not edit.
return array (
'panels' =>
array (
'admin' =>
array (
'resources' =>
array (
0 => 'App\\Panels\\Admin\\Resources\\Users\\UserResource',
),
'pages' => array ( /* ... */ ),
'widgets' => array ( /* ... */ ),
),
),
'fingerprint' => '…',
);2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Class names only. No closures, no resolved metadata, nothing user-specific — the test suite asserts the rendered file contains neither Closure nor function. The list per panel is explicit registration plus discovery, deduplicated and sorted, so two runs on two machines produce a byte-identical file.
The path goes through the application's bootstrapPath() rather than base_path('bootstrap/...'), because an application may move that directory, and a manifest written somewhere optimize:clear does not look is a stale cache nobody can clear.
Part of optimize
Both commands are registered as optimize hooks under the key panels:
php artisan optimize # config, routes, events, views — and panel:cache
php artisan optimize:clear # and panel:clear2
A deploy that already runs optimize gets the panel manifest for free. There is nothing extra to add to a deploy script that has that line.
Where it goes in a deploy
composer install --no-dev --optimize-autoloader
php artisan optimize
npm ci && npm run build2
3
After composer install, not before: discovery resolves file paths through Composer's PSR-4 map, and a manifest written against an old autoloader names classes that are no longer where it says they are.
Reading the manifest yourself
PandaPanel\Cache\PanelManifest is a container singleton and the only reader and writer of that file.
| Method | Signature |
|---|---|
path | static path(): string |
exists | exists(): bool |
for | for(Panel $panel): array{resources: list<string>, pages: list<string>, widgets: list<string>} |
write | write(PanelRegistry $registry): array |
clear | clear(): bool |
warnIfStale | warnIfStale(PanelRegistry $registry): void |
use PandaPanel\Cache\PanelManifest;
use PandaPanel\Core\PanelManager;
$manifest = app(PanelManifest::class);
$manifest->exists(); // bool
PanelManifest::path(); // '/app/bootstrap/cache/panels.php'
$manifest->for(app(PanelManager::class)->get('admin')); // the three lists2
3
4
5
6
7
8
write() returns the same array it wrote, so a command or a test can assert on it without re-reading the file. It renders to panels.php.{pid}.tmp and moves it into place, so a half-written file can never be loaded and two processes caching at once cannot interleave.
for() is the seam every panel goes through during registration: a panel present in the file is served from it; a panel that is not — one registered by a test, or added since the cache was written — falls back to discovery for itself alone.
The staleness warning
Caching is the whole point, and it is also the trap: a resource added after panel:cache is not in the panel at all. No error, no empty state, no route.
PanelManifest::warnIfStale() runs once at the end of provider boot and does nothing unless a manifest exists and the environment is local, testing, or has debug mode on. When the discovery fingerprint no longer matches:
[panel] The cached panel manifest is out of date: the classes under the
discovery paths have changed since `php artisan panel:cache` last ran. Until
you run `php artisan panel:clear`, anything added since then is invisible — no
route, no navigation entry, and no error to say so.2
3
4
Production gets no such warning, because a production deploy is expected to cache after it copies code.
Exit code
Always 0. Discovery either finds classes or finds none, and finding none is a legitimate answer for a panel that has not been built out yet.
Gotchas
- Do not cache in development. Everything you add afterwards is invisible. If you ran
optimizelocally, runoptimize:clear. - It does not cache routes. Run
route:cacheas well. Panel routes are cacheable because every one points at a controller method rather than a closure. - Nothing user-specific is cached, ever. Authorization results, navigation active state, badge values and record data are recomputed per request, because caching them would serve one person's answers to everybody.
- The manifest is keyed by panel id. Renaming a panel invalidates its entry, and that panel falls back to discovery until the cache is rebuilt.
- A malformed file is treated as empty. Discovery then runs, which is slower but correct.
- Under Octane, a worker that already loaded the manifest keeps serving it until it is recycled. Deploys should restart workers, which they already do.
See also
- panel:clear
- Caching — the manifest, the fingerprint, and what is never cached
- Discovery
- Panel cache in production
- Production checklist
- Route cache, Config cache
- Octane
- Panel cache configuration