Reaching for Facades | Laravel News


Interfaces, people seem to either love it or hate it. Either way, they are a natural part of what Laravel is today. However, Laravel’s interfaces are not exclusive interfaces; are they? Instead, it is a static conductor to solve a class of a container.

When I first started using Laravel, I hated it, and after three years of using Larvel – I finally started accepting it. I was your usual Laravel hater today. When I learned to embrace Laravel for what it is, instead of trying to fight the framework of my way of thinking, I learned to love it. Some would say that I am one of its biggest advocates now.

One of the things I hated for some time was the interfaces. I was in the angry camp complaining about the fixed method calls, blah blah blah. But I didn’t know how they worked in Laravel. I was joining the noise repeating what the other developers have said without knowing what I was talking about.

Fast forward to today, and I understand how interfaces work – and you know what? I definitely changed my tune. I want to write this tutorial not so that you all agree with me, although you should, but also so that you can understand how interfaces work and where they have their benefits.

This is not a strict tutorial, as I will walk you through the current code I wrote, whereas I usually write the code as I write the tutorial so I can explain the normal rebuild points. The code I’m going to point you to is the Get Send Stack Laravel package which you can find on GitHub here.

While creating this package, I did what I normally do and started building an API integration using the HTTP interface – using the interface/nodes to take advantage of the DI container for instance injection when needed. Let me take you through these stages of code. We’ll start by not using the DI container first.

1class AddSubscriberController

2{

3 public function __invoke(AddSubscriberRequest $request)

4 {

5 $client = new Client(

6 url: strval(config('services.sendstack.url')),

7 token: strval(config('services.sendstack.token')),

8 );

9 

10 try {

11 $subscriber = $client->subscribers()->create(

12 request: new SubscriberRequest(

13 email: $request->get('email'),

14 firstName: $request->get('first_name'),

15 lastName: $request->get('last_name'),

16 optIn: $request->get('opt_in'),

17 ),

18 );

19 } catch (Throwable $exception) {

20 throw new FailedToSubscribeException(

21 message: $exception->getMessage(),

22 previous: $exception,

23 );

24 }

25 

26 // return redirect or response.

27 }

28}

So that’s a long “little”, but it’s clear to see what you’re doing. You can create the client, submit the request through the client, and catch a possible exception. Finally, return either a redirect or a response depending on whether it’s an API or a web console. This code is not bad. You can test it. You can ensure behavior inside the console with little effort.

However, if the package changes how you integrate with it, everywhere you are renewing the client to work with the API, you will have to review your code base and make all changes as required. This is the perfect time to consider restructuring, as you save yourself future work by working smarter. Using a DI container directly, let’s take a look at a refactored version of the code above.

1class AddSubscriberController

2{

3 public function __construct(

4 private readonly ClientContract $client,

5 ) {}

6 

7 public function __invoke(AddSubscriberRequest $request)

8 {

9 try {

10 $subscriber = $this->client->subscribers()->create(

11 request: new SubscriberRequest(

12 email: $request->get('email'),

13 firstName: $request->get('first_name'),

14 lastName: $request->get('last_name'),

15 optIn: $request->get('opt_in'),

16 ),

17 );

18 } catch (Throwable $exception) {

19 throw new FailedToSubscribeException(

20 message: $exception->getMessage(),

21 previous: $exception,

22 );

23 }

24 

25 // return redirect or response.

26 }

27}

Cleaner and more manageable now, we are injecting the nodes/interface from the DI container, which will resolve the client for us – the package service provider has detailed instructions on how to build the client. There is nothing wrong with this approach. It’s a pattern I use a lot in my code. I can replace the app to get a different result and still use the same API package I’m using for the interface/node. But again, while I’m using the container – am I fighting the framework? One of the things that many of us love about Laravel is the developer experience, and we can thank Eloquent very much for that. We don’t have to mess with the container to create a new form or anything like that. We are very accustomed to the constant connection of what we want when we want. Let’s look at the example above using the interface I created with the package.

1class AddSubscriberController

2{

3 public function __invoke(AddSubscriberRequest $request)

4 {

5 try {

6 $subscriber = SendStack::subscribers()->create(

7 request: new SubscriberRequest(

8 email: $request->get('email'),

9 firstName: $request->get('first_name'),

10 lastName: $request->get('last_name'),

11 optIn: $request->get('opt_in'),

12 ),

13 );

14 } catch (Throwable $exception) {

15 throw new FailedToSubscribeException(

16 message: $exception->getMessage(),

17 previous: $exception,

18 );

19 }

20 

21 // return redirect or response.

22 }

23}

We don’t have to worry about containers – and we get that familiar Laravel feel we’ve been missing. The plus side here is that the developer experience is straightforward, the implementation looks clean, and we achieve the same result. What are the negatives? Because there are some, of course. The only downside is that you can’t switch the implementation because the interface is fixed in its implementation. But in my experience, going from Provider A to Provider B when you’re talking about external services is a lot more complicated than creating and associating a new implementation into the container. People who are always banging that drum look at the problem from a narrow ideological perspective. In fact, Changing providers is a huge effort, not just from a code perspective – so there’s always enough time to focus on implementing something different where you need to. Sometimes the new provider has something the older one doesn’t. You probably have to send additional data through your requests and so on.

My point here is that while SOLID principles are great, and you should look for them for advice – it’s often an unrealistic dream that won’t work in practice, or you’ll spend so much time writing the feature that the scope changes before it’s finished. Fighting the framework at every turn doesn’t help you build good products. You are making good products by accepting less than perfection and acknowledging change that may be required.

How does this relate to interfaces? As you can see from the code examples, interfaces make it easier in many ways. Both cases are incorrect, and both cases are incorrect. The interface will allow for a more friendly implementation but will force you to go a certain path. Using a container gives you more flexibility going forward, but it’s not a magic bullet and comes with its own risks. Just replenishing states when you need them is easy, but it’s also lazy when there are better ways to achieve the same result.

What does the interface actually look like? This is the exact code from the package.

1declare(strict_types=1);

2 

3namespace SendStackLaravelFacades;

4 

5use IlluminateSupportFacadesFacade;

6use SendStackLaravelContractsClientContract;

7use SendStackLaravelHttpResourcesSubscribersResource;

8use SendStackLaravelHttpResourcesTagResource;

9 

10/**

11 * @method static SubscribersResource subscribers()

12 * @method static TagResource tags()

13 * @method static bool isActiveSubscriber(string $email)

14 *

15 * @see ClientContract

16 */

17class SendStack extends Facade

18{

19 protected static function getFacadeAccessor()

20 {

21 return ClientContract::class;

22 }

23}

It has a protected static method to get the class it needs to build and build, and the class we’re extending will redirect all static calls to that class once it’s resolved from the container. People talk about it like a dirty word, but it’s actually no different than creating a container alias, really, other than the syntax. In my example, I’ve added docblocks for the methods on the app/interface to allow the IDE to be better completed – but that’s just an extra step I like to do.

The moral of this story is that facades are not evil and can actually be very beneficial – so ignore the haters and embrace them as I did. You’ll be happier for that and you’ll be more productive.