xrow/bootstrap-bundle
Bootstrap Bundle for Laravel brings the Bootstrap front-end framework into your PHP application, bundling its CSS and JavaScript assets with configuration options so you can build responsive, consistent interfaces without manual asset setup.
Version 1.6 is a small version bump with a disproportionately large behavioural footprint: a new cache purger now overwrites the legacy eZ cache purger service. Treat it as a migration, not a patch release.
First steps after composer update:
php artisan config:clear && php artisan cache:clear, then inspect what is bound:php artisan tinker
>>> app()->getBindings()['Vendor\Package\Contracts\CachePurger'] ?? null
Check the package service provider (vendor/.../src/*ServiceProvider.php). The 1.6 change is almost certainly a $this->app->bind()/singleton() swap of the old EzCachePurger implementation for the new purger class. That line is the source of truth for whether your existing typehints, decorators, or overrides still resolve.
Diff the published config. If you published config/cache-purger.php previously, it is now stale — the new purger likely expects its own driver key, endpoint, or credential block. Re-publish to a temp path and diff:
php artisan vendor:publish --tag=cache-purger-config --force
git diff config/cache-purger.php
use Vendor\Package\Contracts\CachePurger;
public function __construct(private CachePurger $purger) {}
public function update(Content $content): void
{
$content->save();
$this->purger->purge($content->cacheKey());
}
If the legacy purger was the thing you actually depended on, jump straight to the migration pattern below before shipping anything.
Explicit rebinding instead of relying on the default. Keep your own contract in front of the package so a future 1.7 swap doesn't ripple through your codebase:
// AppServiceProvider::register()
$this->app->bind(\App\Cache\Purger::class, function ($app) {
return new \App\Cache\Purger(
$app->make(\Vendor\Package\CachePurger::class)
);
});
Register this after the package provider. If both are in bootstrap/providers.php, order matters — put AppServiceProvider last, or use $this->app->resolving() if the package is lazy-bound.
Feature-flag the switchover. Because 1.6 overwrites the old service rather than adding a parallel one, a config-driven escape hatch is cheap insurance:
// config/cache.php or config/cache-purger.php
'purger' => env('CACHE_PURGER_DRIVER', 'ez_v2'),
$this->app->bind(CachePurger::class, function ($app) {
return match (config('cache-purger.purger')) {
'legacy' => new LegacyEzCachePurger(...),
default => new EzCachePurgerV2(...),
};
});
Purge after commit, never before. The most common production bug with any purger is purging inside a transaction, then having a queued job or concurrent read repopulate the cache with pre-commit data.
class PurgeContentCache implements ShouldQueue
{
public bool $afterCommit = true;
}
Fan-out via events, not controller calls. Let write paths stay thin; listen to domain events and let listeners decide what to purge:
Event::listen(ContentPublished::class, PurgeContentCache::class);
Event::listen(ContentTrashed::class, PurgeContentCache::class);
Queue bulk purges and coalesce them. Purging 500 items individually is 500 round trips. Collect keys, then dispatch one job with Bus::batch() or a single purgeMany(array $keys) call. For purge storms (imports, migrations), guard with a unique job or Cache::lock() so you don't queue the same purge repeatedly.
Decorator for logging/metrics. Wrap the new purger rather than subclassing it — the internals changed in 1.6 and are more likely to move again:
final class LoggingPurger implements CachePurger
{
public function __construct(
private CachePurger $inner,
private LoggerInterface $log,
) {}
public function purge(string $key): void
{
$start = microtime(true);
$this->inner->purge($key);
$this->log->info('cache.purge', ['key' => $key, 'ms' => (microtime(true) - $start) * 1000]);
}
}
Test against a fake, not the real purger. Whatever the package exposes, bind a spy in tests so assertions are on intent:
$this->mock(CachePurger::class, function ($mock) use ($content) {
$mock->shouldReceive('purge')->once()->with($content->cacheKey());
});
Integration tip for multi-store / multi-site setups: make sure the new purger is invoked once per store/scope. Legacy purgers sometimes defaulted to the current site access; if 1.6 changed that default, multi-site installs will silently under-purge. Add an explicit loop and an assertion in tests.
EzCachePurger or equivalent) will keep working if it's still autowirable, but you'll silently get the old behaviour while the rest of the app gets the new one. Grep for the old FQCN and for the contract name.php artisan config:clear && php artisan cache:clear && composer dump-autoload. On deploy, ensure config:cache runs after the new config is in place.$app->scoped()) or reset it in a listener on Octane\Events\RequestReceived.CachePurger::extend(); prefer that over rebinding the contract in a service provider, since it keeps the package's own fallbacks intact.app(CachePurger::class)::class), confirm the config key the new purger reads is populated, confirm the target cache backend actually receives the request (log the outbound call), then confirm the key format matches what the reader uses — key-prefix mismatches between purger and application are extremely common and look identical to a broken purger.composer.json ("vendor/package": "^1.6") and run the upgrade on a staging environment with a real cache backend. This release's change is behavioural, not cosmetic, so a green test suite that mocks the purger proves very little.How can I help you explore Laravel packages today?