Laravel: Extend (almost) any class

Macro what you want, however you want.

Italo Baeza Cabrera
4 min readSep 9, 2019
Photo by Mariana B. on Unsplash

Some of the problematics when using frameworks is that you’re bound to what their tools provide, and in some parts you have to do things the framework way — this is debatable, some frameworks do better and other worst. And if you want to extend something, you vary between two solutions: create your own helper method or Class somewhere, or extend the tool and override it when your application boots.

The last option, extending a class with your own plus new methods, is a very hacky thing, since you have to not only tell the framework to load your tool instead of the default, but also have a keen eye for updates, since a new version may break your code without you ever knowing — that’s what tests are for anyway.

If there was an easy way to add your own custom method to a Class…

…wait! There is! And is called Macros!

What the hell are Macros?

Macros, as Laravel calls them, are a way include your own callable method into a Class. It's been around since Laravel 4.2, so it's not something new.

Think about them as a glue or tape that allows you to stick a method in form of a Closure into the Class you want, without having to modify it directly — something totally not recommended when the class comes from another external package — or having to extend it with your own overriding code. Like gluing a hammer with a flashlight so you can hit the nail in the darkness, without having to call the manufacturer to create one for you. And if you buy a new updated model, the tape and flashlight can still work.

As you may be guessing, Macros are additive. You cannot use Macros to delete methods or override methods completely. If you need to override a method, you’re f*cked: extend the class and do what you must.

The Macros are not documented, except on some classes that can prove to be useful like Response Macros and Collection Macros. Considering the use case, is totally fine to not have them documented, but you’re bound to search for them (more on that later).

The functionality is magical:

  • You can use the macro() method on the Class you want to extend, or through its Facade, with the name of the new method and the Closure to include.
  • The method gets saved inside an static array.
  • When you call the Macro you registered in the Class, it will bind the $this to the Closure you registered and execute the code, which will depend if the call is static or dynamic.

For example, if you use $this inside your Closure, it won’t reference the class where it’s being declared, but the class you’re extending. If you’re extending the View class, $this will reference just that instance.

<?phpuse \Illuminate\Routing\ResponseFactory;class RandomThing
{
public static function registerMacro()
{
ResponseFactory::macro('whatClassIs', function() {
return get_class($this);
});
}
}
echo response()->whatClassIs();
//
// "\Illuminate\Routing\ResponseFactory".
//

All of this is done thanks to the Macroable trait, which includes the magic methods __call() and _callStatic() that are in charge of taking your method call and wiring it to the Macro you have registered.

Hey, what if the Class already uses the “__call” magic method?

Some classes use the _call() magic method for its own logic. When you use a trait that has the same method name, the Class method takes precedence. As you may guess, when a class uses __call(), this poses a problem since the Macroable trait won’t work.

The solution in some classes is to declare the Macroable, open the curly brackets, and tell PHP to include the _call() method as macroCall():

class ToBeMacroable
{
use Macroable {
__call as macroCall,
}
// ...
}

Then, in the _call() methods inside the class, we kindly ask if there is a Macro registered, and execute it using macroCall(). For example, the View class uses this little fix to let both methods live happily.

The Macroable Classes

As I mentioned before, the list of Macroable classes is not documented. The best thing you can do is going to the API and make a search on Macroable on the Github repository or using your own IDE.

Give me all Classes that include the Macroable

Doing that will allow you to have a clear list of what Classes allow to be extensible.

Does Laravel itself uses Macros?

Of course! Let’s take sample from the code that allows us to use the handy $request->validate(). If you look the Request class, you will see no “validate” method. Guess what, this is macro’ed by the hidden but useful FoundationServiceProvider.

/**     
* Register the "validate" macro on the request.
*
* @return void
*/
public function registerRequestValidation()
{
Request::macro('validate', function (array $rules, ...$params) {
return validator()
->validate($this->all(), $rules, ...$params);
});
}

This macro’ed method wires up a validator instance with rules and the Request input. We can then use it without needing to adding more lines of code, neat!

public function post(Request $request)
{
$request->validate([
'name' => 'required|string',
'email' => 'required|email',
]);

// ...
}

The best way to register a Macro is under any Service Provider that makes sense. For example, if I want to register a Macro for the View class, I would use the AppServiceProvider, but if I’m registering one for the Authentication Manager, I would use the AuthServiceProvider.

And that’s all. No need to do shaky-hacky spaghetti code to just add some functionality in the framework tools.

--

--

Italo Baeza Cabrera

Graphic Designer graduate. Full Stack Web Developer. Retired Tech & Gaming Editor. https://italobc.com