CMS Framework - v2.2.2
Themes - Lifecycle Hooks
The Themes module fires action hooks at four points in a theme's lifecycle — two on activation, two on installation. Hooks use the artisanpack-ui/hooks package (doAction() / addAction()).
Added in 2.0.0.
Activation hooks
ThemeManager::activateTheme($slug) fires:
theme.activating
- Fired: before the active-theme setting changes.
- Arguments:
(string $slug, ThemeContract $theme) - Listener may throw: yes — a thrown exception short-circuits activation and the prior theme stays active.
- Typical uses: validate the incoming theme against app-side preconditions, clear theme-scoped caches, write an audit log.
theme.activated
- Fired: after the active-theme setting is updated and view paths are re-registered.
- Arguments:
(string $slug, ThemeContract $theme) - Listener may throw: yes, but the change is already committed — throwing rolls back nothing.
- Typical uses: warm caches that depend on the active theme, send a "theme switched" notification, fire a webhook.
Installation hooks
ThemeManager::installFromZip($zipPath) fires:
theme.installing
- Fired: after successful extraction and strict manifest validation, before the install is committed.
- Arguments:
(string $slug, array $manifest) - Listener may throw: yes — a thrown exception aborts the install. The extracted directory is rolled back so you never end up with a half-installed theme.
- Typical uses: verify the installing user has permission, check a license server, scan the extracted files for unwanted contents.
theme.installed
- Fired: after the install is committed.
- Arguments:
(string $slug, array $manifest) - Listener may throw: technically yes, but the install is already on disk; throwing won't roll it back.
- Typical uses: run theme-shipped seeders, queue a thumbnail-generation job, notify the user, fire a webhook.
Registering listeners
Register with addAction():
use function addAction;
// In a service provider's boot()
addAction('theme.activating', function (string $slug, $theme) {
if (! auth()->user()?->can('themes.activate')) {
throw new \RuntimeException('Not allowed to activate themes.');
}
});
addAction('theme.installed', function (string $slug, array $manifest) {
\Log::info('Theme installed', ['slug' => $slug, 'version' => $manifest['version'] ?? 'unknown']);
\App\Jobs\GenerateThemeThumbnail::dispatch($slug);
});
Listener priority
addAction() accepts an optional priority argument — lower numbers run first, default is 10. Use this to ensure your listener runs before or after another package's listener:
addAction('theme.installing', fn () => /* runs first */, 5);
addAction('theme.installing', fn () => /* runs default */);
addAction('theme.installing', fn () => /* runs last */, 20);
Order of operations
activateTheme($slug):
1. discoverThemes() (refresh cache)
2. resolveTheme($slug)
3. doAction('theme.activating', $slug, $theme) ← may throw to abort
4. update settings['themes.activeTheme'] = $slug
5. re-register view paths
6. doAction('theme.activated', $slug, $theme)
installFromZip($zipPath):
1. validateZip($zipPath)
2. extractZip($zipPath) → temporary directory
3. validateManifest($extractedManifest) ← may throw, rolls back
4. doAction('theme.installing', $slug, $manifest) ← may throw, rolls back
5. move extracted directory into themes path
6. doAction('theme.installed', $slug, $manifest)
Removing listeners
Use removeAction() to unregister a specific callback, or removeAllActions() to clear everything at a given priority (or for the whole hook):
use function removeAction;
use function removeAllActions;
$callback = fn (string $slug) => \Log::info('Activated', ['slug' => $slug]);
addAction('theme.activated', $callback);
// Remove just this callback
removeAction('theme.activated', $callback);
// Remove all callbacks at priority 5
removeAllActions('theme.activated', 5);
// Remove every listener for the hook
removeAllActions('theme.activated');