BeWithYou

胡搞的技术博客

  1. 首页
  2. PHP
  3. Laravel中依赖注入与IoC相关组件的关系

Laravel中依赖注入与IoC相关组件的关系


依赖注入(Dependency Injection)

  • 将类内部依赖的其他类,以注入的方式set进去。从而不需要在类的内部每次都new一个被依赖类的实例。

非侵入式(No intrusive)

  • 框架的目标之一是非侵入式
  • 组件可以直接拿到另一个应用活框架之中使用

容器(Container)

  • 管理对象的生成,资源取得,销毁等生命周期,建立对象与对象之间的依赖关系
  • 启动容器后,所有对象直接取用,不用编写代码来产生对象、或者建立以来关系

IoC(Inversion of Control)

  • 控制反转
  • 依赖关系的转移
  • 依赖抽象而非确定的实例

依赖注入

未注入存储对象,主类依赖Redis类

class DataProcessor
{
    public function save($data)
    {
        $redis = new Redis();
        return $redis->save($data);
    }
}
$oDataProcessor = new DataProcessor();
$oDataProcessor->save();

注入存储对象,将依赖关系的控制反转到调用链的起点。可以将Redis换做Memcached进行save操作。或者再进一步封装成工厂类。

class DataProcessor
{
    private $saveObj;
    public function __construct($obj)
    {
        $this->saveObj = $obj;
    }

    public function save($data)
    {
        return $this->saveObj->save($data);
    }
}
//将主类对调用类的依赖,反转到调用的地方
$oDataProcessor = new DataProcessor(new Memcached());
$oDataProcessor->save(); 

依赖注入的例子

这里有一个层次依赖的例子,后面会用到。

 class Bim
{
    public function doSomething()
    {
        echo __METHOD__, '|';
    }
}

class Bar
{
    private $bim;

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

    public function doSomething()
    {
        $this->bim->doSomething();
        echo __METHOD__, '|';
    }
}

class Foo
{
    private $bar;

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

    public function doSomething()
    {
        $this->bar->doSomething();
        echo __METHOD__;
    }
}

$foo = new Foo(new Bar(new Bim()));
$foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething

简单的IoC容器

这里实现一个简单的IoC容器,使用了后期静态绑定。这个例子中把static换成self也是可行的。而laravel中的容器可以通过app()获得,它是一个Container对象。

class IoC
{
    protected static $registry = [];

    public static function bind($name, Callable $resolver)
    {
        static::$registry[$name] = $resolver;
    }

    public static function make($name)
    {
        if (isset(static::$registry[$name])) {
            $resolver = static::$registry[$name];
            return $resolver();
        }
        throw new Exception('Alias does not exist in the IoC registry.');
    }
}

IoC::bind('bim', function () {
    return new Bim();
});
IoC::bind('bar', function () {
    return new Bar(IoC::make('bim'));
});
IoC::bind('foo', function () {
    return new Foo(IoC::make('bar'));
});

// 从容器中取得Foo
$foo = IoC::make('foo');
assert($foo instanceof Foo);
$foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething

IoC容器的高级功能

真实的IoC容器会提供其他很多功能,比如自动绑定,延迟注入,绑定可选的接口/类/单例等。
下面的例子实现了IoC容器中的自动绑定功能(Autowiring)。

class Container
{
    private $s = array(); 
    public function __set($k, $c){
        $this->s[$k] = $c;
    }
    public function __get($k){
        // return $this->s[$k]($this);
        return $this->build($this->s[$k]);
    }
    public function build($className){
        // 如果是匿名函数(Anonymous functions),也叫闭包函数(closures)
        if ($className instanceof Closure) {
            // 执行闭包函数,并将结果
            return $className($this);
        }
        /** @var ReflectionClass $reflector */
        $reflector = new ReflectionClass($className);
        // 检查类是否可实例化, 排除抽象类abstract和对象接口interface
        if (!$reflector->isInstantiable()) {
            throw new Exception("Can't instantiate this.");
        }
        /** @var ReflectionMethod $constructor 获取类的构造函数 */
        $constructor = $reflector->getConstructor();
        // 若无构造函数,直接实例化并返回
        if (is_null($constructor)) {
            return new $className;
        }
        // 取构造函数参数,通过 ReflectionParameter 数组返回参数列表
        $parameters = $constructor->getParameters();
        // 递归解析构造函数的参数
        $dependencies = $this->getDependencies($parameters);
        // 创建一个类的新实例,给出的参数将传递到类的构造函数。
        return $reflector->newInstanceArgs($dependencies);
    }
    public function getDependencies($parameters){
        $dependencies = [];
        /** @var ReflectionParameter $parameter */
        foreach ($parameters as $parameter) {
            /** @var ReflectionClass $dependency */
            $dependency = $parameter->getClass();

            if (is_null($dependency)) {
                // 是变量,有默认值则设置默认值
                $dependencies[] = $this->resolveNonClass($parameter);
            } else {
                // 是一个类,递归解析
                $dependencies[] = $this->build($dependency->name);
            }
        }
        return $dependencies;
    }
    public function resolveNonClass($parameter){
        // 有默认值则返回默认值
        if ($parameter->isDefaultValueAvailable()) {
            return $parameter->getDefaultValue();
        }
        throw new Exception('I have no idea what to do here.');
    }
}

// ----
$c = new Container();
$c->bar = 'Bar';
$c->foo = function ($c) {
    return new Foo($c->bar);
};
// 从容器中取得Foo
$foo = $c->foo;
$foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething

// ----
$di = new Container();

$di->foo = 'Foo';

/** @var Foo $foo */
$foo = $di->foo;

var_dump($foo);
/*
Foo#10 (1) {
  private $bar =>
  class Bar#14 (1) {
    private $bim =>
    class Bim#16 (0) {
    }
  }
}
*/

$foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething

Service Container 和 Service Provider

没有任何关系。IOC容器的使用不依赖服务提供者。

$app->make('App\Models\Post');

new App\Models\Post();

效果一样。使用$app->make的时候并不需要特定的服务提供者。前提是使用完整的类名,而不是别名。

Service Provider 和 Contracts

服务提供者的作用是绑定接口(或者类,单例)到容器。

//1 给接口起一个别名
$this->app->bind('event_pusher', function($app){
    return new App\Contracts\EventPusher;
});

//2 指定接口应该解析的实例
$this->app->bind('App\Contracts\EventPusher', 'App\Services\RedisEventPusher');

之后就可以在容器中直接用别名来make了。

$app->make('event_pusher');

Service Provider 和 Facades

门面中,我们只需要在getFacadeAccessor方法中返回你要调用的实际的类名或者别名即可。注意别名也可以,类名全称也行。

门面的实际上是调用父类__callStatic方法,将服务提供者中绑定的类解析并返回出来并调用相应的方法。

比如Cache门面,我们在调用Cache::get('uid');的时候,实际上是调用Illuminate\Support\Facades\Cache这个门面,然后找不到这个get方法,就会__callStaticIlluminate\Cache\Repositoryget方法。

Facade的别名

config/app.php里,我们可以为门面注册他的别名alias。之后就可以use Cache;,而不用use Illuminate\Support\Facades\Cache;


例子部分选自这篇文章

回到顶部