thinkphp容器和依赖注入-理解与使用

发布于 2023-07-03  510 次阅读


内容纲要

thinkphp5引入了容器的概念,通过高性能的容器和依赖注入来管理类之间的关系。

容器存在的意义,就是为了解决面向对象开发中高度耦合的问题,即实现高内聚,低耦合。

首先需要了解容器和依赖以及注入的概念。

容器是什么?

在thinkphp中,容器可以看做一系列类的集合,即他是存放类的容器,容器是一个比较抽象的概念。实际开发中,我们认为容器就是把项目依赖的一系列类注册进一个统一的类中进行保存,这个保存类的类就是类的容器。

什么是依赖?

对于容器用来封装类,但是类最终是要实现业务的,将类注册进容器就是为了在实现业务逻辑中方便使用。所以容器中注册的类都是会被继承或者调用的类,调用这些类的类就对容器封装的类产生了依赖关系。

例如:

<?php
namespace app\controller;
use app\model\User;
class Index{
public function reg(){
$user=new User();
$user->insert($data);
}
}

?>

上面的代码使用到了User类下的insert方法,所以reg这个方法的实现就需要用到User的代码,因此Index依赖User类。

实际开发中,一个类可能依赖很多类,类与类之间又存在错综复杂的依赖关系,如何处理这些类之间的关系就成了一个重要的问题,以及在后续的业务扩展中需要对某个类的代码进行重构优化,依赖这个类的类都要做相应的改动,这样就产生了工作效率的问题。

所以对于依赖关系就需要通过容器进行优化,即将所有被依赖的类注册进容器中,在需要调用的时候进行取得实例,而不是对每个依赖的类都注册依赖类,这样可以很好的降低耦合度。

什么是注入?

明白了什么是容器和依赖就很好解决注入的问题,注入实际上就是产生实例,将被依赖的类实例注入到使用的类中,浅显的理解就是实例化,不过注入是通过容器实现自动实例化的过程。

thinkphp的容器和依赖注入

thinkPHP对容器统一在think\Container类中处理,通过think\Container类的方法即可完成对类的依赖和注入,但是大多数情况只需要使用助手函数即可完成依赖注入。

thinkphp实现依赖注入有三种方法:

  • 通过Container类绑定
  • 通过约束参数类型为对象自动绑定
  • 通过助手函数绑定

1.container类手动注册与注入

首先是通过Container类绑定,Container类的路径是:thinkphp\library\think\Container.php

在Container.php中对每个方法做了详细的介绍,这里不再一一介绍,给出两种常用的方法示例:

1.set() 将类注册进容器

/think/Container::set('类别名','包含命名空间的类名');

例如将Member类注册进容器:

<?php
namespace app\common\controller;
class Base{
public function __construct(){
\think\Container::set('member','app\common\model\Member');
}
}
?>

通过 set即可将类注册进容器,以供接下来从容器中取得类的实例。

2.get() 将类的实例从容器中取出

\think\get('注册类的别名','实例化构造参数');

例如对上面的member类取出实例:

<?php
namespace app\common\controller;
class Base{
public function __construct(){
\think\Container::set('member','app\common\model\Member');
$member=\think\Container::get('member');
}
}

?>

如果user类有构造方法,并且约定了构造方法的参数,就需要在取出实例时传入构造参数,以键值对的形式。

<?php
namespace app\common\model;
class Member{
public function __construct($username){
echo $username;
}
}

?>

如果member类的结构是如上的话,在get实例时:

<?php
namespace app\common\controller;
class Base{
public function __construct(){
\think\Container::set('member','app\common\model\Member');
$member=\think\Container::get('member',',['username'=>'admin']);
}
}

?>

以上例程输出admin

实际上我们很少甚至不会考虑使用\think\Container类来进行手动注册和依赖注入,因为thinkphp提供了强大的自动加载机制,通过手动绑定的方法,必须首先对需要依赖的类进行注册,然后在使用时进行取出实例,如过没有在此之前注册依赖,那么就无法注入依赖。

2.约束参数自动注入

通过对方法的参数进行约束即可实现自动注入,这一过程通过Container类自动完成。

因为thinkphp在处理业务类的时候,对类的参数通过URL参数获取,而如果对参数约束列对象类型,那么就通过依赖注入生成变量。即对于类方法的参数,如果不约束其类型,thinkphp会通过URL参数来生成变量,如果约束为类,则根据约束的依赖类自动注入生成变量。

<?php
namespace app\index\controller;
class Index{
public function index($word){
echo $word;
}
}
?>

对于上面的index方法存在一个没有指定约束的$word参数,他就是通过url参数获取的,如果访问URL:

http://127.0.0.1/index.php/index/index/index/words/你好

或者:

http://127.0.01/index.php/index/index/index?word=你好

都会在页面中输出你好。

如果对参数进行约束:

<?php
namespace app\index\controller;
use app\common\controller\Words;
class Index{
public function index(Words $word){
var_dump($word)
}
}

?>

此时$words被约束为Words类的实例,thinkPHP会自动完成依赖注入,但是仍需要对其进行use引入。

3.助手函数完成依赖注入

thinkphp提供了两个助手函数用于完成对类的注册和依赖注入:

bind() 将依赖类注册(绑定)到容器

bind('类标识','类路径');

例如将user类注册进容器:

Use类:

<?php
namespace app\model;
class User{
public function __construct($user){
var_dump($user);}
}

?>

Index类:

<?php
namespace app\controller;
class Index{
public function index(){
bind('U','app\model\User');
}
}

?>

在执行index类的index方法即将User注册到容器,别名为U,对类进行注册后就可以从容器中调用类的实例。

app() 取得类的实例

app('类的标识或类路径');

对U取得实例:

<?php
namespace app\controller;
class Index{
public function index(){
bind('U','app\model\User');
$u=app('U')
}
}

?>

实际上对上述的步骤可以简化为:

app('app\model\User');

app助手函数在完成依赖注入之前会对类进行绑定,因此完全可以省略bind的步骤。

单例模式

对于同一个生命周期下的同一个依赖类的实例,使用的是单例模式,即所有对象共享一个内存。

看一下例子:

Message类:

<?php
namespace app\index\model;
use think\model;

class Message{
    public $name="小杜";
    public function index(){
        echo 6666;
    }
}


?>

Index类:

<?php
namespace app\index\controller;
use app\common\controller\Base;
use think\Db;
use think\facade\Env;
class Index extends Base{
    public function initialize()
    {
        $m=app('app\index\model\Message');
        $m->name="Proking";
        $m->age=20;
        parent::initialize(); // TODO: Change the autogenerated stub
    }

    public function index(){
        $s=app('app\index\model\Message');
        var_dump($s);
    }
}


?>

其中在Index中构造方法首先对Message进行依赖注入,并且对对象属性进行修改和增加,然后在index方法中再次对类进行依赖注入,观察一下结果:

object(app\index\model\Message)#33 (2) { ["name"]=> string(7) "Proking" ["age"]=> int(20) }

即使是在index中方法输出的变量名为$s的变量,但是他的结果仍然是和构造方法中的对象一直,所以默认情况下依赖注入使用的时单例模式,如果业务需要重新实例化对象,对app助手函数第二个参数取true即可:

$s=app('app\index\model\Message',true);

此时输出的结果:

object(app\index\model\Message)#36 (1) { ["name"]=> string(6) "小杜" }

绑定闭包

bind不仅可以将依赖类注册到容器,也可以将闭包注册到容器。

bind('闭包名',闭包体);

将闭包注册后即可通过app调出闭包:

 public function pack(){
        bind('add',function(){
            echo "我是一个闭包";
        });
        $func=app('add');
    }

绑定类的实例

容器也可以注入类的实例

public function index(){
        $user=new User;
        bind('user',$user);
        $u=app('user');
        var_dump($u);
    }

绑定接口实现

对于依赖注入使用接口类的情况,我们需要告诉系统使用哪个具体的接口实现类来进行注入,这个使用可以把某个类绑定到接口

bind('think\Env');
        $env=app('env');
        var_dump($env);

同时支持依赖注入的约束类型:

public function env(Env $env){
        var_dump($env);
}

批量绑定

如果对bind 方法传入一个数组即进行批量绑定(注册)

bind([
'别名'=>'类路径'
]);

例如批量绑定接口:

bind([
    'route'      => \think\Route::class,
    'session'    => \think\Session::class,
    'url'        => \think\Url::class,
]);

provider.php

通过在应用或者模块根目录下创建provider.php即可实现自动批量绑定,系统会自动将文件中的类注入到容器中,不需要在手动绑定,直接使用就可以。

provider.php需要返回一个包含键值对的数组,与上面的批量绑定键值对用法相同。

return [
    'route'      => \think\Route::class,
    'session'    => \think\Session::class,
    'url'        => \think\Url::class,
];

届ける言葉を今は育ててる
最后更新于 2023-07-03