Panel Cache
Discovery walks directories and reflects over classes. That is fine in development and wasteful on every production request, so php artisan panel:cache does it once and writes the answer to a manifest. With a manifest present, discovery does not run at all: no filesystem scan, no reflection, nothing per request.
Caching and clearing
php artisan panel:cache
# Panels cached: {panels} panels, {n} resources, {n} pages, {n} widgets.
php artisan panel:clear
# Panel manifest cleared.2
3
4
5
panel:cache reports the totals across every registered panel, which is the quickest check that a panel is registered and discovering what you expect.
Both are registered as optimize hooks, so a deploy that already runs optimize gets them:
php artisan optimize # config, routes, events, views, panels
php artisan optimize:clear # and the reverse2
panel:clear treats a missing manifest as success, so it is safe on a fresh checkout and safe to run twice.
What the manifest holds
The file is bootstrap/cache/panels.php, resolved through app()->bootstrapPath('cache/panels.php') so an application that moved that directory does not end up with a cache optimize:clear cannot find.
<?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 ( 0 => 'App\\Panels\\Admin\\Pages\\AccountsDashboard', /* ... */ ),
'widgets' => array ( 0 => 'App\\Panels\\Admin\\Widgets\\RecentUsers', /* ... */ ),
),
),
'fingerprint' => '…',
);2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Class names only. It is written with var_export rather than serialization, so opcache can hold it and a human can read it, and it is written to a temporary file and moved into place, so a half-written file can never be loaded. Lists are sorted, which makes two machines produce byte-identical output.
Never cached: authorization results, navigation active state, badge values, record data, widget data, the unread notification count. Those depend on the user and the URL, so caching them would serve one person's answers to everybody.
What goes into it
For each registered panel, the manifest merges what the panel registered explicitly with what discovery finds under its discovery paths:
$panel
->resources([UserResource::class]) // explicit
->discoverResources(app_path('Panels/Admin/Resources')); // discovered2
3
A class named in both appears once. ResourceConfiguration entries are not part of the manifest — they are panel configuration, evaluated at boot — but the class they configure is registered from the configuration itself, so it does not need discovering.
Reading it back
PanelManager asks PandaPanel\Cache\PanelManifest for each panel's classes while building its registries:
use PandaPanel\Cache\PanelManifest;
$manifest = app(PanelManifest::class);
PanelManifest::path(); // absolute path to bootstrap/cache/panels.php
$manifest->exists(); // bool
$manifest->for(panel('admin')); // ['resources' => [...], 'pages' => [...], 'widgets' => [...]]2
3
4
5
6
7
for() returns the cached entry when one exists and runs discovery otherwise, so the same call works cached or not. The file is read once per process and held in memory.
Two more methods exist for tooling:
use PandaPanel\Core\PanelRegistry;
$manifest->write(app(PanelRegistry::class)); // build and write, returns the manifest array
$manifest->clear(); // delete it, returns bool2
3
4
The staleness warning
panel:cache writes a fingerprint beside the classes: the count of PHP files under each discovery path and the newest modification time among them. On boot, PanelManifest::warnIfStale() recomputes it and logs a warning when it 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
This exists because the failure it describes is unguessable from the symptom: a resource added after caching is absent. No route, no sidebar entry, no error.
The check runs only in development — when debug mode is on, or the environment is local or testing — and only when a manifest exists at all, which in development is the unusual case. In production the manifest is the authority and nothing touches the filesystem. It costs a stat per PHP file under the discovery paths, against reflection and class loading for the real thing.
use PandaPanel\Cache\DiscoveryFingerprint;
DiscoveryFingerprint::of([panel('admin'), panel('app')]); // string
DiscoveryFingerprint::isStale([panel('admin')], $recorded); // bool2
3
4
isStale() answers false whenever the answer cannot be established — no fingerprint recorded, an unreadable path — because being unsure is not a reason to tell somebody their cache is stale.
Where it fits in a deploy
composer install --no-dev --optimize-autoloader
php artisan migrate --force
npm ci && npm run build
php artisan optimize # includes panel:cache2
3
4
Cache after the code is in place, never before. Rolling back code without clearing leaves a manifest naming classes that no longer exist; optimize:clear on the way out avoids that.
Notes
- Adding a resource, page or widget after caching makes it invisible until
panel:clearor anotherpanel:cache. In development, prefer no manifest at all. - The manifest does not cache the panels themselves.
config/panda-panel.phpis still read on every boot, andconfig:cacheis what makes that cheap. - Route caching is separate and complementary: panel routes point at controllers rather than closures precisely so
route:cachekeeps working. - A manifest written by an older version — a flat map with no
fingerprintkey — is still read correctly, so upgrading does not require a cache clear to boot. - The test suite deletes the manifest between tests that write one. A suite that caches panels and then registers a fixture panel will not see the fixture, which is the same trap the warning describes.