Ryan Chandler (Senior Software Engineer, Laravel) is on the Laravel core team. He has spoken at Laravel Live UK before (2023) and at Laracon EU 2026. This talk covers practical workflow improvements for shipping code to production with confidence.
- ryangjchandler.co.uk
- GitHub: @ryangjchandler
Pest architecture tests
Pest architecture tests enforce structural rules about the codebase as part of the test suite. They fail like any other test, so violations are caught in CI rather than in code review.
What they can enforce:
- Controllers only use the standard resource method names (
index,show,create,store,edit,update,destroy) and are always suffixed withController - Models extend the correct base class
- No debug functions (
dd,dump,var_dump) in production namespaces - All classes in a namespace are
final - No direct model access outside of repositories
- Service classes implement defined interfaces
Pest ships with two presets relevant here. The laravel preset enforces Laravel
conventions. The strict preset enforces stricter PHP standards including
strict_types in all files and all classes being final.
// Enforce no debug calls in production code
arch()->preset()->laravel();
arch('no debug calls')
->expect('App')
->not->toUse(['dd', 'dump', 'var_dump', 'ray']);
arch('controllers follow conventions')
->expect('App\Http\Controllers')
->toHaveSuffix('Controller');
arch('services implement contracts')
->expect('App\Services')
->toImplement('App\Contracts\ServiceInterface');
Architecture tests complement static analysis tools like PHPStan and Larastan. PHPStan catches type errors; architecture tests catch structural violations that static analysis does not express easily.
References:
Rule tests for coding conventions
Beyond the built-in presets, Pest architecture tests can be used to enforce project-specific coding conventions as explicit, named tests. The point is that conventions normally enforced through code review or documentation can instead be enforced automatically.
Any team convention that can be expressed as a structural rule should be. Each rule becomes a failing test if violated, which means it gets caught in CI with a clear error message rather than being caught inconsistently in review.
Examples of conventions that can be expressed as rules:
arch('form requests must be suffixed correctly')
->expect('App\Http\Requests')
->toHaveSuffix('Request');
arch('jobs must be queued')
->expect('App\Jobs')
->toImplement('Illuminate\Contracts\Queue\ShouldQueue');
arch('no Eloquent in controllers')
->expect('App\Http\Controllers')
->not->toUse('Illuminate\Database\Eloquent\Builder');
arch('enums are backed')
->expect('App\Enums')
->toBeStringBackedEnums();
This overlaps with the approach discussed by both Aaron Francis and Yannick Chenot at the same conference.
Feature flags with Laravel Pennant
Feature flags decouple deployment from release. Code ships to production behind a flag that is off by default. The feature becomes visible to users only when the flag is enabled, which can happen independently of the deployment.
Benefits for workflow:
- PRs stay smaller because a feature in progress can be merged while still gated behind a flag
- A broken feature can be disabled instantly without a rollback or new deployment
- Flags act as kill switches in production for anything that turns out to be problematic
- Gradual rollouts become possible (enable for 10% of users, monitor, then widen)
Laravel Pennant is the official first-party package for feature flags in Laravel.
composer require laravel/pennant
php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider"
php artisan migrate
Defining a feature:
// app/Features/NewCheckoutFlow.php
class NewCheckoutFlow
{
public function resolve(User $user): bool
{
// Enable for beta testers only initially
return $user->is_beta_tester;
}
}
Checking a flag:
if (Feature::active(NewCheckoutFlow::class)) {
return view('checkout.new');
}
return view('checkout.legacy');
In Blade:
@feature('new-checkout-flow')
<!-- new UI -->
@endfeature
Flags can also use the Lottery helper for percentage-based rollouts, or be
activated/deactivated per user explicitly via Feature::activateFor($user, ...)
and Feature::deactivateFor($user, ...).
Pennant also fires an UnknownFeatureResolved event when a flag is checked but
not defined, which is useful for catching stale references after a flag has been
removed.
References:
SafeMigration helper
Ryan referenced a SafeMigration helper for detecting or preventing unsafe
database operations in migrations before they reach production. The specific
implementation shown was not a well-known published package and may be an
internal utility or something Ryan built himself.
The concept it addresses is common: certain migration operations are dangerous in
production without zero-downtime precautions. Operations like renaming a column,
dropping a column, adding a NOT NULL constraint without a default, or running a
large UPDATE in a migration can cause table locks or application errors while
the old code is still running against the new schema.
The closest equivalent in the PHP/Laravel ecosystem is
aramayismirzoyan/laravel-safe-migrations, which flags migrations that modify
already-committed migration files. In the Rails world, the strong_migrations
gem does this more comprehensively; it detects unsafe operations and requires
them to be wrapped in a safety_assured block as explicit acknowledgement.
The general pattern for zero-downtime migrations in Laravel is the expand-contract approach:
- Deploy a migration that only adds (new column, new table), as additive changes are safe
- Update application code to write to both old and new
- Backfill existing rows via a queued job
- Update code to read from the new structure
- Deploy a migration to drop the old structure
Applying this in a Laravel context means avoiding $table->dropColumn(),
$table->renameColumn(), and $table->change() without careful sequencing
across multiple deployments.
References:
- Zero downtime Laravel migrations - PlanetScale
- ankane/strong_migrations - Rails equivalent for reference
Notes from Laravel Live UK 2026.