neirda24/liipthemeprovider
LiipThemeProvider adds flexible theme support to Laravel apps, inspired by LiipThemeBundle. Register multiple themes, switch the active theme at runtime, and resolve theme-specific views and assets. Ideal for white-labeling, multi-tenant interfaces, or seasonal designs, with simple service-provider...
This is a Symfony bundle, not a Laravel package — the Liip* naming and the "theme list" concept point at an extension for LiipThemeBundle that lets you feed the theme list from a custom source instead of static config.yml entries. Before writing a line of code, confirm that with three files:
composer.json — check "type": "symfony-bundle", the require block (Symfony version constraints are the real gate), and whether LiipThemeBundle is a hard dependency.DependencyInjection/ — the bundle extension and any compiler pass. This is where the provider tag is collected.getThemes()), plus Resources/config/services.xml showing the default implementation and tag name.Minimal steps if you are in a Symfony app:
composer require neirda24/liipthemeprovider
# register in config/bundles.php
Then implement the provider interface, register the class as a service, and tag it with whatever tag the compiler pass reads. Clear the cache and load any page — the theme request listener consumes the aggregated list on every request, so a broken provider surfaces immediately.
First real use case: themes that can't live in YAML — multi-tenant skins stored in a database, or a directory scan of themes/*. That's the whole point of the package: one provider class per source, aggregated by the bundle.
If you are actually on Laravel: don't install it. There is no bundle registration, no service tags, no compiler passes. Read the interface and the aggregator, then reimplement the pattern as a ThemeManager bound in a service provider with Theme::extend(). It's roughly 60 lines of Laravel and avoids dragging a Symfony kernel into your app.
One provider per source, never a god-provider. A DatabaseThemeProvider, a FilesystemThemeProvider, a ConfigThemeProvider — each tagged. The aggregator merges them. This keeps cache invalidation and testing scoped to a single concern.
Decorate the default provider rather than replacing it. If you only need to filter (e.g. drop disabled themes) or sort, wrap the existing service and delegate, then tag the decorator with higher priority. You keep upstream behaviour and upgrades stay cheap.
Cache aggressively — the list is hot. The theme listener resolves the active theme on essentially every request. A provider doing a DB query or a filesystem scan per request will show up in your profiler. Wrap the result in a cache pool keyed on the source's version/updated-at, and invalidate from a model event or a console command.
Laravel-flavoured equivalent, if you port it:
// AppServiceProvider
$this->app->singleton(ThemeRepository::class, DatabaseThemeRepository::class);
$this->app->extend(ThemeManager::class, fn ($manager) =>
$manager->extend('db', fn () => app(ThemeRepository::class)->all())
);
with Cache::rememberForever('themes', ...) and a Theme::saved/deleted observer calling Cache::forget('themes').
Testing: swap the provider for an in-memory stub in the test container (or $this->app->instance() in Laravel) and assert the resolved theme name, not the provider internals. Provider classes should be trivially unit-testable with no container.
Integration tip: expose the theme list through your own thin service rather than injecting the bundle's aggregator everywhere. When the package goes unmaintained, you replace one class instead of grepping 40 files.
composer.json, read the provider interface and compiler pass yourself, and have a plan to fork. Don't put it on a critical path without that.config/bundles.php, no tags, no compiler passes, no autowired public services. composer require will succeed and then nothing will work the way the README implies.liip_theme.provider and you wrote liip.theme_provider, your provider is simply never collected — no exception, just a missing theme. When a theme doesn't appear, dump the tagged services (debug:container --tag=...) first, before debugging your provider logic.cache:clear or your invalidation fires. This burns an hour for everyone once.How can I help you explore Laravel packages today?