Introducing Macroable Trait0:00
If you browse the Laravel source code, you'll see constant references to this trait called Macroable. Here it is on the Collection class. And in fact, if we search for it in the vendor directory, and I scroll down, you'll see it everywhere. Here it is on the Session class, in the Repository, in the Command, in the Event, in the Cookie jar. It's everywhere in the Laravel source code. So what on earth is it, and why is it useful? Let's take a look here. If we go to the bottom, yeah, we're dealing with about 120 lines here. So not that much going on, and yet still incredibly useful. So at its core, the Macroable trait allows us to hook into a class and extend it with additional functionality in real time. Let me show you an example. Let's work with that Collection class. So what might be additional functionality that we want the Collection class to offer? And what's neat about this is you can add
Adding a Collection Macro0:42
an example. Let's work with that Collection class. So what might be additional functionality that we want the Collection class to offer? And what's neat about this is you can add application-specific functionality directly to that class, and you don't have to update it. You don't have to copy or fork or clone it. You can simply throw code like this in a ServiceProvider and enhance or extend the class as you need to. So for example, on any Collection class, let's say you have posts. Of course, you can do things like give me the first post or give me the last post. But for something simple, how about give me the second post? Maybe that's useful to you. Okay, well, that's not offered out of the box. So let's add it. Add a macro called second, and here will be my handler. Now again, what's neat about this is this will be scoped to the Collection instance. Now I'm going to keep it simple and just say return $this->items and then
and here will be my handler. Now again, what's neat about this is this will be scoped to the collection instance. Now I'm going to keep it simple and just say return $this->items and then give me the second one there. But if we wanted to match what Laravel offers, let's see for first, get the first item from the collection that passes the given truth test. So yeah, by default, it'll give you the first one, or you could say, give me the first one where the ID is greater than four or something like that. But anyways, we're going to keep it simple here and just stick with the second item in the collection. All right, and that's it, believe it or not. So let's collect, I will just say one, two, three, and now I want to grab the second item there. Okay, so if we did not have this and we ran it in the browser, of course, we'd get a bad method call exception. second does not exist. But if we bring it back, run it again, it works. We've now
Building Macroable from Scratch2:11
so if we did not have this and we ran it in the browser, of course, we'd get a bad method call exception. Second does not exist. But if we bring it back, run it again, it works. We've now enhanced the functionality of the Collection class without changing it or copying it or extending it. And because that macro will trade is used all over the Laravel source code, that means when and if you need to, you can extend basically anything, which is pretty neat, I think. Now, if you're curious how this might work under the hood, let me give you a little demonstration here. Let's imagine you were building it from scratch. All right, well, we know we need a trait, so we could call it Macroable here. And we know we need a static method called macro that should accept a name and that should accept a handler. I believe Laravel just calls it macro there. And this method should simply store it. So we'll have our macros here that will default to an array. Okay, so now
name and that should accept a handler. I believe Laravel just calls it macro there. And this method should simply store it. So we'll have our macros here that will default to an array. Okay, so now we can push to it, add a new macro with that given name and make it equal to that function there. Okay, so now imagine you had, and we'll stick with that collection idea, but we're not going to use Laravel's collections. And if that's the case, that means we need to give it the list of items when we instantiate it. And then maybe you have methods like first, like a typical collection would. So that would return the items and then give me the first one there. Yeah, kind of a basic starting collection there. Okay, so now we could instantiate the ExampleCollection. We'll give it one, two, three. And then once again, we will grab the first item there. All right, so if I come back to Firefox, give it a refresh. That's working. Now let's extend it. So we will pull in that.
one, two, three. And then once again, we will grab the first item there. All right, so if I come back to Firefox, give it a refresh. That's working. Now let's extend it. So we will pull in that Macroble trait. Next, we'll register a new macro. We'll do it here. Add a new macro. Once again, let's do last this time. And let's just return the end of the array. Now, of course, remember, if you're in control of ExampleCollection, and if it makes sense, there's no need to do this. You would simply add another method here. But the use case for this is situations where, one, the method you are adding to it really doesn't make sense going on it by default. It's very unique to one application. Or two, you're working with third-party code or framework code that you're not in charge of, and you can't update on your own. Anyways, if we come back and we now call this new last method, let's see. We run it, and it doesn't work. Call the undefined method last.
Handling Missing Methods4:35
you're not in charge of, and you can't update on your own. Anyways, if we come back and we now call this new last method, let's see. We run it, and it doesn't work. Call the undefined method last. All right, we have our next step. So think about it. At the moment, we have registered a macro. So if we scroll up, this macros array does consist of one, but we're not doing anything with it. So as it turns out, the secret sauce of the Macroable trait comes from the __call magic method. So we would accept the method and the parameters here. And actually, so much of the magic Laravel provides comes from the __call magic method. You'll see it on your Eloquent model. You'll see it for things like higherOrderTap. You'll see it for the Macroable trait. You'll see it all over the place. So now, if you're unfamiliar, the __call method will be triggered if you call a method on the class that does not exist. This has the opportunity to respond. So we'll say respond.
it all over the place. So now, if you're unfamiliar, the call method will be triggered if you call a method on the class that does not exist. This has the opportunity to respond. So we'll say respond right here, come back to Firefox, and you'll see we did catch that. All right, so think about it. By default, we would want to throw a badMethodCallException, and we'll say method does not exist. All right, so if we come back, here we go. This is what we'd want by default. But before we do that, why don't we check to see if a macro was registered? So I could say if isset(static::$macros), and let's look for the method that was called. If that's the case, then we'll call the macro instead of throwing a badMethodCallException. So the macro will be, let's grab that static::$macros method. And then ultimately, we need to call that macro and send through the parameters, and we'll just destructure that into an argument list. Finally, we will return that result. But
static macros method. And then ultimately, we need to call that macro and send through the parameters, and we'll just destructure that into an argument list. Finally, we will return that result. But this still isn't quite what we want. So if I come back and give it a refresh, it fails and expects parameter one to be an array, but we have null. And that's because the $this keyword is not being scoped correctly. So for example, if we were just to say return, it works. This, in fact, would work. But as soon as we make use of $this, or in fact, if I dd $this, and I come back and give it a refresh, you'll notice we wanted it to refer to the example collection, right? That should be the scope. But at the moment, it's still the route file registrar. So here's how we're going to change that. We'll scroll back up, and PHP offers this bindTo method. This allows us to bind the $this keyword to something else. It's very similar to JavaScript's bind functionality. So I will bind
Binding Closures to $this6:56
that. We'll scroll back up, and php offers this bindTo method. This allows us to bind the this keyword to something else. It's very similar to JavaScript's bind functionality. So I will bind this anonymous function to the this keyword. And in fact, let's take a look at this real quick. All right. Duplicate the closure with a new bound object and class scope. So you'll see the parameter list. The first one is what is the new this keyword? What should that refer to? Well, that should refer to the collection, at least in this example. Otherwise, whatever class you have added the macro will trade to. All right. Next, what is the new scope? And this determines the visibility of the protected and private methods and whether you have access to them. So let's set that to the current class. Okay. So now if we update that, come back and give it a refresh, you'll see that now that this keyword refers to the example collection. And again, that's because we had this anonymous
current class. Okay. So now if we update that, come back and give it a refresh, you'll see that now that this keyword refers to the example collection. And again, that's because we had this anonymous function and we bound that this keyword to, in this case, the example collection. And then we set the scope so that we can, for example, access protected properties. So if I did not have that and we run it, it's probably going to say, oops, let's come back. Return and this items. Yeah, by default, we don't have access to that protected property there. So that's what that second argument allows for. Come back and now it works. Okay. So this is a quick implementation, but basically that's exactly what's happening in Laravel's macro build trait. All of the magic starts here in the call magic method. To confirm, if you call a method on your class that does not exist, we hit the call method. And there we can check, well, did you register a macro at some point?
Reviewing Laravel Implementation8:34
starts here in the __call() magic method. To confirm, if you call a method on your class that does not exist, we hit the __call() method. And there we can check, well, did you register a macro at some point? If so, let's bind that function to the current class instance, and then we call it and return the results to you. Now, if there's not a macro registered, okay, sorry, we don't know what to do. So we will call a BadMethodCallException as you see there. All right. So we'll finish up by taking a look at the official Macroable trait. You'll see it's basically that. You call a macro method, and then we store it or cache it. You also have one for mixin, which would mix in a full object into the class. And next we have a little helper here. And then finally, we have callStatic() as well as call(). And you'll notice the same thing. So here it's in reverse. If there is no macro on the class, throw a BadMethodCallException. Otherwise, grab the macro. And then if we are
as well as call. And you'll notice the same thing. So here it's in reverse. If there is no macro on the class, throw a BadMethodCallException. Otherwise, grab the macro. And then if we are working with a closure, bind the macro to the current instance, and then call the array while passing through the parameters.
