Enforcing Clean Controller Design In Laravel With Pestphp Architectural Testing
Last updated on

As someone who’s always trying to bring structure and discipline to Laravel applications, I’ve been diving into architectural testing using PestPHP lately. Pest has some excellent presets, especially when it comes to enforcing clean and maintainable code through expectations. One area I was particularly interested in was controller method restrictions.
We already had a basic architectural test to ensure that Laravel controllers do not expose any unnecessary public methods—just the conventional ones like index
, show
, store
, etc. But I wanted to take it a step further.
If a controller is invokable (i.e., has the
__invoke
method), then it should not have any other public methods except__construct
and__invoke
.
Simple rule, but enforcing it across your growing codebase is a different story.
PestPHP Limitation & Custom Test
I initially tried to achieve this using PestPHP’s native expect(...)->toHavePublicMethodsOnly([...])
, but there wasn’t a straightforward way to conditionally skip classes that aren't invokable. So I ended up writing a custom architectural test that walks through each controller file, determines if the class defines __invoke
, and applies the rule accordingly.
Here’s the snippet I came up with:
1test('invokable controllers only have __construct and __invoke as public methods', function () { 2 foreach (glob(module_path('User', 'app/Http/Controllers/**/*.php')) as $file) { 3 $class = Str::of($file) 4 ->after('/User') 5 ->replace( 6 ['/app', '.php'], 7 '' 8 )->toString(); 9 10 $classNamespace = app()11 ->getProvider(UserServiceProvider::class)12 ->module_namespace('User', $class);13 14 if (method_exists($classNamespace, '__invoke')) {15 expect($classNamespace)16 ->not17 ->toHavePublicMethodsBesides(['__construct', '__invoke']);18 }19 }20});
Why This Matters
In a large codebase, small things like this matter more than we think. Enforcing clean, intention-driven controller design means fewer surprises, better maintainability, and ultimately a more robust system.
Architectural testing is one of those underrated practices that can significantly improve your team’s consistency without becoming a bottleneck. And PestPHP makes it surprisingly elegant.
If you're using Laravel + Pest, I highly recommend investing time in writing these small, meaningful assertions. It pays off.
💬 Have ideas on how this can be improved or made even cleaner? I’d love to hear your thoughts. If you're exploring similar architectural patterns in your Laravel apps, or if you have a project where you'd like robust architectural or feature tests written, feel free to reach, check out my work and connect with me on Upwork.