Search code examples
phplaravellaravel-5functional-programminglaravel-5.3

How exactly works the register() method of Laravel ServiceProvider?


I am pretty new in PHP and Laravel. I am following this tutorial to add reCAPTCHA support to a page inside my Laravel applicatin:

http://tutsnare.com/how-to-use-captcha-in-laravel-5/

Anyway my doubt is not stricty related to CAPTCHA but related to the content of the previous tutorial:

As you can see it add this service provider:

'providers' => [
  Anhskohbo\NoCaptcha\NoCaptchaServiceProvider::class,
],

From what I have understand reading the official documentation: https://laravel.com/docs/5.4/providers

a ServiceProvider is something that register the "component" used by my application at its startup.

But how exactly means this?

I am trying to undertand it studying the previous NoCaptchaServiceProvider class and comparing it with the documentation.

So this is its code:

<?php

namespace Anhskohbo\NoCaptcha;

use Illuminate\Support\ServiceProvider;

class NoCaptchaServiceProvider extends ServiceProvider
{
    /**
     * Indicates if loading of the provider is deferred.
     *
     * @var bool
     */
    protected $defer = false;

    /**
     * Bootstrap the application events.
     */
    public function boot()
    {
        $app = $this->app;

        $this->bootConfig();

        $app['validator']->extend('captcha', function ($attribute, $value) use ($app) {
            return $app['captcha']->verifyResponse($value, $app['request']->getClientIp());
        });

        if ($app->bound('form')) {
            $app['form']->macro('captcha', function ($attributes = []) use ($app) {
                return $app['captcha']->display($attributes, $app->getLocale());
            });
        }
    }

    /**
     * Booting configure.
     */
    protected function bootConfig()
    {
        $path = __DIR__.'/config/captcha.php';

        $this->mergeConfigFrom($path, 'captcha');

        if (function_exists('config_path')) {
            $this->publishes([$path => config_path('captcha.php')]);
        }
    }

    /**
     * Register the service provider.
     */
    public function register()
    {
        $this->app->singleton('captcha', function ($app) {
            return new NoCaptcha(
                $app['config']['captcha.secret'],
                $app['config']['captcha.sitekey']
            );
        });
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return ['captcha'];
    }
}

It contains:

1) The register() method that:

public function register()
{
    $this->app->singleton('captcha', function ($app) {
        return new NoCaptcha(
            $app['config']['captcha.secret'],
            $app['config']['captcha.sitekey']
        );
    });
}

It only bind things into the service container. In this case it take 2 values that are stored into the .env file. These values contains the re-CAPTCHA strings.

My doubt is: at the application startup Laravel call the register() method but what exactly happens?.

What means this section of the register() method?

$this->app->singleton('captcha', function ($app) {
        return new NoCaptcha(
            $app['config']['captcha.secret'],
            $app['config']['captcha.sitekey']
        );
    });
  • Who is $this? I think that it is the NoCaptchaServiceProvider instance (but I am not so sure), is it or am I missing something?

  • What means $this->app? app is a method or a property?

  • What means $this->app->singleton('captcha'.....}); I think that it is something like a factory implementing a single instance of something....but what exactly is building? Is it a **NoCaptcha instance?

  • I think that the logic of the previous singleton building is implemented by the inner function:

     function ($app) {
         return new NoCaptcha(
            $app['config']['captcha.secret'],
            $app['config']['captcha.sitekey']
     );
    

And here I have 2 doubts:

The first one is: who pass $app to this function? Is it something like an empty variable\object that will filled later? Or is it injected in some way?

This function seems to be the logic of creation of a singleton. Because it is putted inside:

singleton('captcha', function ($app) {
        return new NoCaptcha(
            $app['config']['captcha.secret'],
            $app['config']['captcha.sitekey']
        );
    });

So what it means? It means that when the singleton() method is invocked, this function that return a new NoCaptcha object is performed and it represent the logic of creation of my singleton?

So it means that in some way PHP implement functional programming paradigm?


Solution

  • $this refers to the NoCaptchaServiceProvider.

    The NoCaptchaServiceProvider extends Service Provider. Which has the property $app, which is an instance of \Illuminate\Contracts\Foundation\Application aka the Laravel application.

    When a new service provider is created Laravel injects the application instance into the service providers' constructor:

    public function __construct($app)
    {
        $this->app = $app;
    }
    

    So with $this->app you are retrieving the laravel application instance.

    The application instance extends the Container class, which then has the the method singleton(). Which simply registers a shared binding in the container.

    public function singleton($abstract, $concrete = null)
    {
        $this->bind($abstract, $concrete, true);
    }
    

    EDIT: Notice that these namespaces are refering to the contract, and not the actual class.

    What the service container does is binding an interface to a given implementation. The contract (or inferface) simply tells what methods the class should implement.

    For example: We have UserRepository which has the UserRepositoryContract which tells that a certain implementation needs to have the functions all() and find(). We bind that Contract to the EloquentUserRepository, which is just a class that implements those methods and returns all() and find() with Eloquent.

    Now the only thing we need to do is set the binding and use the UserRepositoryContract as an instance instead of the EloquentUserRepository.

    If we later want to use Redis, we only need to create a RedisUserRepository class which implements the same methods, change the binding in the container and voila. Now all UserRepositoryContract instances will use Redis instead of Eloquent.