How does Laravel uses $this->comment() method inside console.php file in "routes" directory while Artisan::command() being a static method?
<?php
use Illuminate\Foundation\Inspiring;
Artisan::command('inspire', function() {
$this->comment(Inspiring::(quote));
})->describe('Display an inspiring quote');
$this
isn't being used inside the static method itself, it's used in the closure that's passed to that method. From the Laravel manual:
The Closure is bound to the underlying command instance, so you have full access to all of the helper methods you would typically be able to access on a full command class.
So $this
in this context is a Command instance. This is achieved using PHP's bindTo
method, which allows you to specify the scope for any given closure.
This kind of methods are not exclusive to Artisan commands though. In general, we call this feature Facades
:
Facades provide a "static" interface to classes that are available in the application's service container. Laravel ships with many facades which provide access to almost all of Laravel's features. Laravel facades serve as "static proxies" to underlying classes in the service container, providing the benefit of a terse, expressive syntax while maintaining more testability and flexibility than traditional static methods.
There are quite some other facades, which all provide static access to instances that live within the service container. Some of the more common facades and methods are:
Cache::get('key')
and Cache::set('key', 'value')
Request::input('some_field')
and Request::only('some_field')
Log::info('be aware of this...')
- ...
Laravel makes very liberal use of magic methods. When you do something like Artisan::command()
, there is no actual public static function command()
definition. So php instead looks to see if a __callStatic()
method is defined, as a catchall for undefined methods. So somewhere in the Artisan
Facade, you're likely to find something to the effect of:
public static function __callStatic($name, array $args = [])
{
$newObj = new static();
if (method_exists($newObj, $name)) {
return $newObj->$name(...$args);
}
}
Another tricky thing here is, more than likely the object you get back is not freshly instantiated as in the above example. Most of these follow the Singleton Pattern, meaning not only are you calling a non-static method statically, but you're calling it against the same instance of the target object every time.
$newObj = new static();
Would look more like
self::$preexistingObject = self::$preexistingObject ?: new static();
$newObj = self::$preexistingObject;
Most of this magic happens when the ServiceProviders 'boot' up. Somewhere in a configuration file, Laravel has been told what root class to associate with that "Artisan" Facade. It creates a new instance of that class, and holds on to it, reusing it, for the duration of that session.
Finally, to more directly answer your question and elaborate on Iainn's answer, this blew my mind the first time I discovered it, but native Php actually supports changing which object $this
actually refers to within your anonymous function. You just call $closure->bindTo($newObject)
, as if the Closure is itself and object and bindTo() is a method. (For all I know, under the hood, Php might not actually make much of a distinction.)
You can do some cool stuff with this too. You could set your class up to receive a Closure, re-bind it to its own scope instead of that of the caller, retain it in a static associative-array, and access it later via. the Magic __call()
method. The result is basically method-overloading; an opportunity to inject custom algorithms into a helper-class for use later in a declarative context.
Laravel provides a tool that can do just that. Macros, a Trait that you can plug into whatever you like. And it already has it baked into some toolsets that are known candidates for expansion, such as Collections, Eloquent\Builder, and Responses.