sebwite/contracts
Sebwite Contracts provides a lightweight contract layer for Laravel and PHP projects, helping define clear interfaces between components. It improves type safety, keeps integrations explicit, and makes code easier to test, extend, and maintain.
Prerequisites to check before you install anything. This package targets an older Laravel generation and pulls in Assetic, which has been unmaintained since ~2015. Confirm three things first:
composer.json of the package — read the require block for illuminate/*, php, and assetic/* constraints.config/app.php.php constraint covers your runtime. On PHP 8.x you will almost certainly need --ignore-platform-reqs or a fork; on PHP 7.1–7.4 you have a fighting chance of a clean install.Minimal install
composer require sebwite/contracts
Then, in config/app.php (if not auto-discovered):
'providers' => [
// ...
Sebwite\Contracts\Providers\ContractsServiceProvider::class,
],
Where to look first — in this order
vendor/sebwite/contracts/src/ — the interfaces themselves. This package is the contract half of a theme framework; it defines the shape (ThemeInterface, asset/resolver contracts, etc.) that the sibling theme package implements. Read every interface before writing a line of your own code; they are the spec.vendor/sebwite/contracts/composer.json — tells you whether this is a pure interface package or ships concrete classes too.sebwite/theme, or whatever it names) — the contracts package alone gives you nothing runnable. It is a dependency of a theme implementation, not a theme framework by itself.config/ after php artisan vendor:publish.First use case. The lowest-risk first integration is to bind a single custom theme class that implements the package's theme contract, resolve it from the container, and echo a resolved view name. That proves the contract → container → view-finder chain works before you start layering inheritance and Assetic asset compilation on top. Inheritance and asset pipelines are where the framework's cost/benefit lives, so validate the boring path first.
Contracts-first binding. Treat this package as a set of ports and put your implementations behind them. Bind in a dedicated service provider rather than in AppServiceProvider:
$this->app->singleton(ThemeInterface::class, function ($app) {
return new MyTheme($app['config']->get('theme.active'));
});
Everything else (controllers, view composers, asset helpers) should type-hint the interface, never the concrete theme. That is the entire reason to depend on a contracts package.
Theme inheritance via view path cascading. The framework's selling point is child theme → parent theme → framework views. The idiomatic wiring is to register view paths in precedence order so the child resolves first, and let the parent act as fallback. Do the registration in a service provider that runs after the package's own provider, otherwise your paths get displaced. Keep one theme per app instance; multi-theme-per-request is possible but the view finder state is global and will leak between tenants unless you reset it.
Assetic asset collections. Define named collections (e.g. app.js, app.css) with their source files and filters, keyed per environment — debug should emit unminified, un-concatenated assets with cache-busting disabled, production should concatenate and filter. Put the collection definitions in config, not in PHP classes, so ops can adjust filters without a deploy. Register custom Assetic filters through the contract's filter registry rather than instantiating them inline; that keeps them mockable.
Config-driven theme selection. Drive theme.active from an environment variable and override per-deploy. This lets staging run the child theme while production runs a different variant, without code changes. If you need per-user theming, resolve the theme name in a middleware and rebind the container singleton before the response is rendered — but be aware of the view-finder caching implications.
Testing pattern. Because the package is contracts-first, your test suite should bind a stub ThemeInterface and a stub asset manager. That lets you assert "controller resolved theme X and rendered view Y" without touching Assetic, which is slow and filesystem-dependent. Treat Assetic as an integration-test concern only.
Integration with existing Blade views. The theme's view namespace should be separate from resources/views so you can migrate incrementally. Publish/override one template at a time and keep the app's default views as the final fallback layer; this makes the framework adoptable on a live codebase rather than an all-or-nothing rewrite.
Assetic is the biggest liability. Its filter chain expects external binaries (lessc, sass, coffee, java for closure/yui) on the PATH of the web user, not your shell user. When a filter silently no-ops in production, it's almost always a missing binary or a write_to directory the web server can't write to. Run the asset build as part of deploy and serve static files; do not rely on runtime compilation behind PHP-FPM.
Cache invalidation is two-layered. You have Laravel's view cache and Assetic's compiled asset cache. php artisan view:clear does not touch the latter. After changing a theme template or an asset collection, clear both — and if you see stale markup, check the theme's compiled/cache directory before you debug your controller.
Namespace collision with Laravel itself. Sebwite\Contracts\... versus Illuminate\Contracts\... is an easy autocomplete trap, especially for anyone who has muscle memory around Laravel contracts. Alias on import if you must use both in one file.
Signature drift between package versions. Interface methods in a small, low-activity contracts package change without semver discipline. Pin an exact version ("sebwite/contracts": "1.x.y" not "^1.0") and, if you fork, keep the fork in a VCS repository entry in composer.json so the change is reviewable.
Auto-discovery and config publishing may not exist. Older packages expect manual provider registration and manual config copying. If vendor:publish shows nothing, look for a config/ directory inside the package and copy the file by hand, then merge the keys into your own config file — don't leave a package config unmerged if you have config caching enabled, or config:cache will bake in stale values.
Debugging checklist when a theme "doesn't apply": (1) dump get_class(app(ThemeInterface::class)); (2) dump the view finder's registered paths in order; (3) check the theme's own config for a paths/views key you forgot to set; (4) confirm the provider order in config/app.php or the auto-discovery manifest.
Extension points worth knowing. The contracts package exists so you can swap implementations — custom theme resolvers, custom asset filters, custom template-name resolvers. Any behavior you're tempted to hack into vendor code should instead be a new implementation of one of these interfaces, bound in your own provider. That's the only durable way to work with a package of this age.
Overall day-to-day verdict. The contracts themselves are cheap to adopt and give you a clean seam for theming. The Assetic dependency is where the operational cost concentrates, and it is high. Budget for replacing the asset half of the stack with Vite or Mix within a year, and treat the theme-inheritance half as the part actually worth keeping.
How can I help you explore Laravel packages today?