From 3711331bdd59967f9259bddf0b3811754ec39c63 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 18 Jul 2025 09:05:18 +0200 Subject: [PATCH 1/3] Added post: Why I disallow Laravel Facade usages --- .../2025-07-18-why-i-disallow-facade-usage.md | 333 ++++++++++++++++++ 1 file changed, 333 insertions(+) create mode 100644 source/_posts/2025-07-18-why-i-disallow-facade-usage.md diff --git a/source/_posts/2025-07-18-why-i-disallow-facade-usage.md b/source/_posts/2025-07-18-why-i-disallow-facade-usage.md new file mode 100644 index 00000000..e1f9d193 --- /dev/null +++ b/source/_posts/2025-07-18-why-i-disallow-facade-usage.md @@ -0,0 +1,333 @@ +--- +layout: post +title: Why I disallow Laravel Facade usages +category: Security +tags: [ "PHP", "Laravel", "Facade", "Anti-Patterns" ] +year: 2025 +month: 07 +day: 18 +published: true +summary: "Laravel Facades: why I disallow their usage in my software project" +description: A technical overview of why Facades are problematic from a software engineering point of view, and why I disallow them in my software projects +--- + +

+ For non-Laravel developers: beware that this is not about the facade pattern. +

+ +

+ Recently, I had a heated discussion with a software developer: they approached me, asking + me why I had flagged all usages of Laravel facades in a code review. +

+ +

+ Laravel Facades are a pattern that has been deeply ingrained in the development practices of teams that + solely design Laravel code, and they are mostly promoted in terms of DX, + rather than technical merits. +

+ +

The Example

+ +

+ Following is a piece of code that uses a Laravel Facade to store some data in cache: +

+ +@TODO change this example to use a different facade: something that sends an email notification would be nice + +~~~php +heavyLifting(); + + Cache::put('a key', $result); + + return $result; + } + + private function heavyLifting(): Result + { + // ... irrelevant ... + } +} +~~~ + +

+ From an inexperienced observer's point of view, this code is relatively straightforward: it + does some heavy work, it stores the information in a cache, then returns. +

+ +

Problem 1: shared mutable state

+ +~~~php +doSomeHeavyWork(); +$result2 = $service2->doSomeHeavyWork(); +~~~ + +

+ @TODO we need a stronger example here: what's a good "whoops" example, like a checkout on the wrong banking coordinates? +

+ +

+ @TODO expose how this problem occurs especially when: + * an application grows in size, accommodating for multiple service instances + * tests: multiple services needed, one per test! +

+ +

Problem 2: hidden dependencies

+ +

+ A second problem occurs when running this code in isolation: assuming you have **only** + autoloading configured, what will it do? +

+ +~~~php +doSomeHeavyWork(); // this will crash: the cache was never configured! +~~~ + +

+ This kind of crash is very similar to what you'd experience with the service location pattern: since + you are deferring all instantiation to the point at which a dependency is effectively invoked, you + may run in crashes due to a missed dependency. +

+ +

+ In addition to the disadvantages of service-location, you also have the hidden dependency of the facade's + internal + + static::$app + . +

+ +

+ Your code cannot work until you've bootstrapped a Laravel application, which is a complex and heavy + (performance-wise) operation. +

+ + +

Problem 2: framework dependencies added

+ +

+ Another issue with our MyService is that we have widely expanded the contact surface with the + framework: our code now depends on illuminate/support, and also on illuminate/contracts + in order for it to function at basic level. +

+ +

+ This is both an issue of Laravel, which exposes a very bloated illuminate/contracts package, and + of our code, since framework upgrades can now affect our ability to perform upgrades. +

+ +

+ Coming myself from the + DDD + community, isolating software dependencies is critical for the long-term maintenance of a system, and reducing + the amount of dependencies is always a good idea, as it is entropy that will easily spin out of control. +

+ +

+ In this case, we wanted a cache, not the entire framework. +

+ +

Problem 3: hidden complexity

+ +

+ @TODO expose here how adding a facade introduces magic method calls, stack frames, static analysis complexity. +

+ +

+ @TODO expose concept of "simple" != "easy". Systems can be complex and easy, or harder to use, but simple. +

+ +

The simple solution

+ +

+ Here's our service re-implemented to make things simple and explicit: +

+ +~~~php +heavyLifting(); + + $this->cache->put('a key', $result); + + return $result; + } + + private function heavyLifting(): Result + { + // ... irrelevant ... + } +} +~~~ + +

+ The above is a little more verbose, but follows very old and well-functioning + dependency injection rules. +

+ + +

+ By adding 2 lines of code, we: +

+ + + +

+ Does the above solution have worse DX? + Given all the listed advantages, I think it provides much better experience. +

+ +

+ You have to also remember that facades were designed and built in an age when auto-wiring dependency injection + containers weren't common in PHP: the simpler approach may actually even be easier, when using the full framework. +

+ +

But what about my test helpers?

+ +

+ @TODO Laravel has MyFacade::spy() and MyFacade::mock() helpers - let's document those +

+ +~~~php +with('a key', 'a value'); + + $service = new MyService(); + + $result = $service->doSomeHeavyWork(); + + // more assertions on $result + } +} +~~~ + +

+ The above example swaps the facade underlying service location mechanism with a mock, and relies on an + + extremely complex base test class + to do framework startup/cleanup operations. +

+ +

+ Here's the "simple" version instead: +

+ + +~~~php +createMock(Cache::class); + + $cache->expects(self::once()) + ->method('put') + ->with('a key', 'a value'); + + $service = new MyService($cache); + + $result = $service->doSomeHeavyWork(); + + // more assertions on $result + } +} +~~~ + +

+ Notice how we got rid of: +

+ + + +

+ Don't like using automatic mocks by PHPUnit? Bring your own! +

+ +~~~php + */ + private array $recorded = []; + function put(string $key, mixed $value) { + $this->recorded[$key] = $value; + } +} + +// look! No testing framework either! +function my_test() { + $cache = new CacheSpy() + + $service = new MyService($cache); + + $result = $service->doSomeHeavyWork(); + + assert('a value' === $cache->recorded['a key']); + + // more assertions +} +~~~ + +

+ The above is obviously brought to extremes, but it highlights the added degrees of fredom that are introduced. +

+ +

+ Now imagine having to upgrade a test suite with 5000 tests, all depending on Facade spies + and Illuminate\Foundation\Testing\TestCase: sounds fun? No? I had to do it, a few times, + and I can also assure you it's not fun. +

+ +

Disallowing facades

+ +

+ From my point of view, Facades are technical debt, and of a particularly bad and sneaky kind. +

+ +

+ There is no reason to skip the extra legwork to keep a system simple: systems increase complexity + over time by nature, and it is our job as software designers to keep it at bay. +

+ +

+ Introducing complexity for the sole purpose of some very questionable DX + claims is therefore not acceptable, and is not something I accept in software systems that I manage. +

\ No newline at end of file From 0e45e5579976dc64f0b29a82fbfe235e53978200 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 18 Jul 2025 09:21:07 +0200 Subject: [PATCH 2/3] Minor adjustment on header alert --- source/_posts/2025-07-18-why-i-disallow-facade-usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/_posts/2025-07-18-why-i-disallow-facade-usage.md b/source/_posts/2025-07-18-why-i-disallow-facade-usage.md index e1f9d193..caf9c826 100644 --- a/source/_posts/2025-07-18-why-i-disallow-facade-usage.md +++ b/source/_posts/2025-07-18-why-i-disallow-facade-usage.md @@ -11,7 +11,7 @@ summary: "Laravel Facades: why I disallow their usage in my software project" description: A technical overview of why Facades are problematic from a software engineering point of view, and why I disallow them in my software projects --- -

+

For non-Laravel developers: beware that this is not about the facade pattern.

From b321352344a32ed7917bbd825553e2d85917a1dd Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 18 Jul 2025 16:43:21 +0200 Subject: [PATCH 3/3] Adding TODO about clarifying inputs/outputs visibility, which is not true when using facades --- source/_posts/2025-07-18-why-i-disallow-facade-usage.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/_posts/2025-07-18-why-i-disallow-facade-usage.md b/source/_posts/2025-07-18-why-i-disallow-facade-usage.md index caf9c826..ab58bbcf 100644 --- a/source/_posts/2025-07-18-why-i-disallow-facade-usage.md +++ b/source/_posts/2025-07-18-why-i-disallow-facade-usage.md @@ -152,6 +152,11 @@ $result = $service->doSomeHeavyWork(); // this will crash: the cache was never c @TODO expose concept of "simple" != "easy". Systems can be complex and easy, or harder to use, but simple.

+

+ @TODO expose how the facade is "hidden API" - not all interactions with the object are exposed by the public + class signature anymore, while constructor shows clear inputs/outputs. +

+

The simple solution