Laravel service container

DI

DI is often referred to as dependency injection, so what is dependency injection?

For example, a computer (not a laptop) needs a keyboard and mouse for us to operate. This' need 'in other words, it depends on the keyboard and mouse.

Accordingly, a class needs another class to perform a job, which is called dependency.

Look at a piece of code:

class Computer {
    protected $keyboard;
    
    public function __construct() {
        $this->$keyboard = new Keyboard();
    }
}

there Computer Class depends on the keyboard class.

Well, now that we know what dependency is, what is injection?

Let's modify the above code:
class Computer {
protected $keyboard;

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

$computer = new Computer(new Keyboard());


there Computer Class dependency injection Keyboard Class.

As for dependency injection, my understanding is:

The required class is passed in the form of parameters, which is dependency injection.

After understanding dependency injection, we can then understand IOC.
IOC

What is IOC?

It is called control inversion in Chinese. What do you mean? This can be easily understood after reading DI.

Through DI, we can see that the dependent classes required by a class are actively instantiated and passed into the class.

What does inversion of control have to do with this?

Inversion of control means handing over the control of dependent classes from active to passive.

Look at a laravel Code:
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class SessionController extends Controller
{

public function login(Request $request)
{
    //This is IOC. We don't need to actively pass in classes. Everything is implemented by laravel
}

}

Seeing this, you may have questions. How did this happen?

This is the service container. Please look down.
Service container

After reading many articles, I agree that service container is a design pattern.

Its purpose is to decouple dependencies.

It is a bit similar to the "enjoy yuan mode" I mentioned earlier. The difference is that the service container solves all dependent implementations.

Here, let's see from beginning to end how to evolve a service container step by step.

It is still an example of computers. We know that computers rely on keyboard and mouse, but there are many kinds of keyboard and mouse.

Let's take a look at the most primitive code example:

 class Computer {
    protected $keyboard;
    
    public function __construct($type == null) {
        
        switch($type) {
            case 'common':
                $this->keyboard = new CommonKeyboard();
                break;
            case 'awesome':
                $this->keyboard = new AweSomeKeyboard();
                break;
            default:
                $this->keyboard = new Keyboard();
                break;
        }
      
    }
}

Maybe you can see the problem at a glance.

If we want to add another keyboard, we have to modify this class. In this way, the class will become large and highly coupled.

So how can we modify it?

Factory mode

In this way, we can avoid directly modifying the Computer class.
Simple factory
class Factory {

    public static function getInstance($type){
        switch($type) {
            case 'common':
                $this->keyboard = new CommonKeyboard();
                break;
            case 'awesome':
                $this->keyboard = new AweSomeKeyboard();
                break;
            default:
                $this->keyboard = new Keyboard();
                break;
        }
    }
}

class Computer {
    protected $keyboard;
    
    public function __construct($type == null) {
        $this->keyboard = Factory::getInstance($type);
    }
}

In this way, after using the simple factory mode, we can modify the factory class instead of the Computer class. This is equivalent to understanding the Computer class.

Although the Computer class no longer depends on those keyboard classes, it has become dependent on the factory class.

If you add a new type of keyboard later, you must modify the factory class.

Therefore, this factory class can not meet the requirements. We know that the Computer interface to the Keyboard is consistent. The Keyboard must implement this interface to be recognized by the Computer. Then we modify the Computer and Keyboard classes.

DI(Dependency injection)

interface Board {
    public function type();
}

class CommonBoard implements Board {
    public function type(){
        echo 'Ordinary keyboard';
    }
}

class MechanicalKeyboard implements Board {
    public function type(){
        echo 'Mechanical keyboard';
    }
}

class Computer {
    protected $keyboard;
    
    public function __construct (Board $keyboard) {
        $this->keyboard = $keyboard;
    }
}

$computer = new Computer(new MechanialKeyBoard());

But there are also problems. What if we are not satisfied with the keyboard used by this computer in the future? We are back to the origin. We must modify the incoming keyboard class.

Can it be made configurable?

IOC Service container (super factory)

class Container
{
protected $binds;

protected $instances;

public function bind($abstract, $concrete)
{
    if ($concrete instanceof Closure) {
        $this->binds[$abstract] = $concrete;
    } else {
        $this->instances[$abstract] = $concrete;
    }
}

public function make($abstract, $parameters = [])
{
    if (isset($this->instances[$abstract])) {
        return $this->instances[$abstract];
    }

    array_unshift($parameters, $this);

    return call_user_func_array($this->binds[$abstract], $parameters);
}

}

This is a simple IOC service container.

How can we solve the above problems?
$container = new Container;

$container->bind('Board', function($container){
    return new CommonBoard;
});

$container->bind('Computer',function($container,$module){
    return new Computer($container->make($module));
});

$computer = $container->make('Computer',['Board']);

The Computer class produced here is a Computer class using an ordinary keyboard.

Explain the code:
bind(name,function($container){
return new Name;
})

there name and Name The relationship is:
When I need name Class, you instantiate it for me Name Class.

make(name)The method is right name Production returns an instance.

What if we want to change the keyboard?
c o n t a i n e r − > b i n d ( ′ B o a r d ′ , f u n c t i o n ( container->bind('Board', function( container−>bind(′Board′,function(container){
return new MechanicalBoard;
});

$container->bind('Computer',function($container,$module){
    return new Computer($container->make($module));
});

$computer = $container->make('Computer',['Board']);

As long as we modify the implementation of the Board class bound by bind, we can easily replace the keyboard. This is a service container.

An understanding of the service container:

A container is a container, like a bowl. And service is the rice, dishes, and so on that bowl needs to be filled. When we need food, we can get it from this bowl. If you want to add some food to the rice (that is, the rice dependency is injected into the dish), we can take the rice directly from the bowl, and these dependencies are solved by the container (that is, control inversion).

What we need to do is to maintain the services provided.

Let's look at a real code that can run on the laravel framework:

// IOC processing dependency


class Me {
    protected $something;
    
    public function __construct(Something $something ) {
        $this->something = $something;
    }
}


class Something {
    
}


App::bind('foo', function(){
    return new Me(new Something());
});

dd(App::make('foo'));

Of course, the service container of laravel framework is much more complex than that here, but it is not difficult to start laravel when we understand its purpose and use scenario.

Keywords: PHP Laravel

Added by dewed on Sun, 26 Sep 2021 06:11:32 +0300