6

How does Laravel uses $this->comment() method inside console.php file in "routes" directory while Artisan::command() being a static method?

enter image description here

<?php

use Illuminate\Foundation\Inspiring;

Artisan::command('inspire', function() {
    $this->comment(Inspiring::(quote));
})->describe('Display an inspiring quote');

2 Answers 2

12

$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...')
  • ...
Sign up to request clarification or add additional context in comments.

Comments

7

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.

3 Comments

Endless number of thanks for your answer!!! It clarifies a lot! I've been struggling with attempts to understand some under-the-hood thing regarding laravel work and you helped a lot. I have some other questions regarding this under-the-hood topic, when I ask them on stackoverflow I will link here to comments. Hope you don't mind answering them as well:)
Much appreciated, I'm glad I could help! Personally, I have a much easier time trusting and understanding a thing if I can see the nuts and bolts. Also helps in debugging sometimes, to know where to stick a die-dump for clues, even if it's more within the core code than your own business logic. I'll keep an eye out, and if it there's any more clarity I can offer, I'd be happy to.
" I have a much easier time trusting and understanding a thing if I can see the nuts and bolts." -- Same thing!!! When I don't see how it actually works I can't trust it on some subconscious level as well as it is much easier to remember the API when I know "what methods came from where"...:) As for the further help from your side, I have already figured out my other related questions:) But I am still on learning curve, so I was wondering if I could contact with you via linkedin in the future? If not, it is totally ok with me:)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.