登录  /  注册
博主信息
博文 31
粉丝 0
评论 2
访问量 26288
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
5月28日作业——对容器注入的理解以及门面Facade的认识
钱光照的博客
原创
1434人浏览过

一、容器注入背景知识

1.容器和依赖注入概念

5.1版本正式引入了容器的概念,用来更方便的管理类依赖及运行依赖注入。

5.0版本已经支持依赖注入的,依赖注入和容器没有必然关系。

容器类的工作由think\Container类完成,但大多数情况我们只需要通过app助手函数即可完成大部分操作。

依赖注入其实本质上是指对类的依赖通过构造器完成自动注入,例如在控制器架构方法和操作方法中一旦对参数进行对象类型约束则会自动触发依赖注入,由于访问控制器的参数都来自于URL请求,普通变量就是通过参数绑定自动获取,对象变量则是通过依赖注入生成。

<?php

namespace app\index\controller;

use app\index\model\User;

class Index{

    protected $user;

     public function __construct(User $user)

    {

        $this->user = $user;

    }

     public function hello()

    {

        return 'Hello,' . $this->user->name . '!';

    }

}

依赖注入的对象参数支持多个,并且和顺序无关。

支持使用依赖注入的场景包括(但不限于):· 控制器架构方法;· 控制器操作方法;· 数据库和模型事件方法;

· 路由的闭包定义;· 行为类的方法;

在ThinkPHP的设计中,think\App类虽然自身不是容器,但却是一个容器管理类,可以完成容器的所有操作。

V5.1.14+版本开始,应用类自身就是一个容器实例。

2.绑定

依赖注入的类统一由容器进行管理,你可以随时绑定类到容器中,支持多种绑定方式。

(1)绑定类标识

可以对已有的类库绑定一个标识(唯一),便于快速调用。

// 绑定类库标识

bind('cache','think\Cache');// 快速调用(自动实例化)

$cache = app('cache');

调用和绑定的标识必须保持一致(包括大小写)

容器中已经调用过的类会自动使用单例,除非你使用下面的方式强制重新实例化。

// 每次调用都会重新实例化

$cache = app('cache',true);

你可以绑定一个类到容器中(第一个参数直接传入类名):

bind('app\common\Test');

但实际上这个操作是多余的,因为只要调用过一次后就会自动绑定

app('app\common\Test');

绑定的类标识可以自己定义(只要不冲突)。

(2)绑定闭包

可以把一个闭包方法绑定到容器中

bind('sayHello', function ($name) {

    return 'hello,' . $name;

});echo app('sayHello',['thinkphp']);

(3)绑定类的实例

也可以直接绑定一个类的实例

$cache = new think\Cache;// 绑定类实例

bind('cache',$cache);// 快速调用类的实例

$cache = app('cache');

(4)绑定至接口实现

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

// 绑定think\LoggerInterface接口实现到think\Logbind('think\LoggerInterface','think\Log');

使用接口作为依赖注入的类型

<?php

namespace app\index\controller;

use think\LoggerInterface;

class Index{

    public function hello(LoggerInterface $log)

    {

     $log->record('hello,world!');

    }

}

(5)批量绑定

如果传入一个数组的话,就表示进行批量绑定,例如:

bind([

    'route'      => \think\Route::class,

    'session'    => \think\Session::class,

    'url'        => \think\Url::class,

]);

可以在应用或者模块目录下面定义provider.php文件(返回一个数组),系统会自动批量绑定类库到容器中。

return [

    'route'      => \think\Route::class,

    'session'    => \think\Session::class,

    'url'        => \think\Url::class,

];

绑定标识调用的时候区分大小写,系统已经内置绑定了核心常用类库,无需重复绑定

系统内置绑定到容器中的类库包括


系统类库                        容器绑定标识   



think\Build                        build   



think\Cache                      cache   



think\Config                     config   



think\Cookie                    cookie   



think\Debug                    debug   



think\Env                         env   



think\Hook                      hook   



think\Lang                       lang   



think\Log                         log   



think\Request                  request   



think\Response               response   



think\Route                     route   



think\Session                  session   



think\Url                         url   



think\Validate                 validate   



think\View                      view   


3.解析

(1)助手函数方式

使用app助手函数进行容器中的类解析调用,对于已经绑定的类标识,会自动快速实例化

app('cache');

上面的app助手函数相当于调用了

Container::get('cache');

带参数实例化调用

app('cache',['file']);

对于没有绑定的类,也可以直接解析

app('org\utils\ArrayItem');

(2)对象化调用

使用app助手函数获取容器中的对象实例(支持依赖注入)。

$app = app();// 判断对象实例是否存在

isset($app->cache);

// 注册容器对象实例

$app->cache = think\Cache::class;

// 获取容器中的对象实例

$cache = $app->cache;

不带任何参数调用app助手函数其实是实例化think\App类,可以方便的操作容器、绑定和调用对象实例。

// 绑定类到容器app()->test = new Test;// 实例调用$test = app()->test;

也就是说,你可以在任何地方使用app()方法调用容器中的任何类。

// 调用配置类app()->config->get('app_name');// 调用session类app()->session->get('user_name');

(3)自动注入

容器的更多使用主要用于依赖注入,和5.0自动注入的方式有所区别,类的绑定操作不再使用Request对象而是直接注册到容器中,并且支持模型事件和数据库事件的依赖注入,依赖注入会首先检查容器中是否注册过该对象实例,如果有的话就会自动注入,例如:

我们可以给路由绑定模型对象实例

Route::get('user/:id','index/Index/hello')->model('\app\index\model\User');

然后在操作方法中自动注入User模型

<?php

namespace app\index\controller;

use app\index\model\User;

use think\Controller;

class Index extends Controller{ 

    public function hello(User $user)

    {

        return 'Hello,'.$user->name;

    } 

}

(4)自定义实例化

V5.1.13+版本开始,容器中的对象实例化支持自定义,可以在你的容器中需要依赖注入的对象中增加__make方法定义,例如:如果你希望User模型类在依赖注入的时候 使用自定义实例化的方式,可以用下面的方法。

<?php

namespace app\index\model;

use think\Model;

use think\db\Query;

class User extends Model{

public static function __make(Query $query)

    {

     return (new self())->setQuery($query');

    }

}

  

二、容器注入具体实例演示

 源码如下:

实例

<?php
//使用容器,最大限度简化外部对象调用
//1.创建容器:将类与它的实现绑定到一个关联数组
//2.服务注册:初始化这个关联数组,将工具绑定到容器中
//3.容器依赖:调用的时候直接传一个容器
//
//下面是工具类
//数据库操作类
class Db
{
	//数据库连接
	public function connect()
	{
		return '数据库连接成功<br>';
	}
}

//数据验证类
class Validate
{
	//数据验证
	public function check()
	{
		return '数据验证成功<br>';
	}
}

//视图类
class View
{
	//内容输出
	public function display()
	{
		return '用户登录成功';
	}
}
/***********************************************************************/
//一、创建容器类
class Container
{
	//
	protected $instance = [];

	//将需要实例化的类与它的实现方法进行绑定:将对象容器初始化
	public function bind($abstract,Closure $process)
	{
		$this->instance[$abstract] = $process;
	}

	//创建特定类的实例
	public function make($abstract,$params=[])
	{
		//
		return call_user_func_array($this->instance[$abstract],[]);
	}
}
/***********************************************************************/
//二、服务注册:调用容器的 bind()将对象注册到容器中
$container = new Container();

//将Db类绑定到容器中
$container->bind('db',function(){
	return new Db();
});
//将Validate类绑定到容器中
$container->bind('validate',function(){
	return new validate();
});
//将View类绑定到容器中
$container->bind('view',function(){
	return new view();
});
/***********************************************************************/
//三、容器注入:容器依赖,以所有用的的对象,以容器的方式注入到当前的工作类中
//用户类:工作类
//
class User
{
	public function login(Container $container)
	{
		echo $container->make('db')->connect();
		echo $container->make('validate')->check();
		echo $container->make('view')->display();
	}
}

//用依赖容器实现解耦
echo '<h3>用依赖容器实现解耦</h3>';
$user = new User();
echo $user->login($container);

运行实例 »

点击 "运行实例" 按钮查看在线实例

 

三、门面Facade背景知识

1.门面(Facade)概念

门面为容器中的类提供了一个静态调用接口,相比于传统的静态方法调用, 带来了更好的可测试性和扩展性,你可以为任何的非静态类库定义一个facade类。系统已经为大部分核心类库定义了Facade,所以你可以通过Facade来访问这些系统类,当然也可以为你的应用类库添加静态代理。

下面是一个示例,假如我们定义了一个app\common\Test类,里面有一个hello动态方法。

<?php

namespace app\common;

class Test{

    public function hello($name)

    {

        return 'hello,' . $name;

    }

}

 调用hello方法的代码应该类似于:

$test = new \app\common\Test;echo $test->hello('thinkphp'); // 输出 hello,thinkphp

接下来,我们给这个类定义一个静态代理类app\facade\Test(这个类名不一定要和Test类一致,但通常为了便于管理,建议保持名称统一)。

<?php

namespace app\facade;

use think\Facade;

class Test extends Facade{

    protected static function getFacadeClass()

    {

     return 'app\common\Test';

    }

}

只要这个类库继承think\Facade,就可以使用静态方式调用动态类app\common\Test的动态方法,例如上面的代码就可以改成:

// 无需进行实例化 直接以静态方法方式调用hello

echo \app\facade\Test::hello('thinkphp');

结果也会输出 hello,thinkphp。

说的直白一点,Facade功能可以让类无需实例化而直接进行静态方式调用。

如果没有通过getFacadeClass方法显式指定要静态代理的类,可以在调用的时候进行动态绑定:

<?php

namespace app\facade;

use think\Facade;

class Test extends Facade

{

}

use app\facade\Test;use think\Facade;

 

Facade::bind('app\facade\Test', 'app\common\Test');echo Test::hello('thinkphp');

bind方法支持批量绑定,因此你可以在应用的公共函数文件中统一进行绑定操作,例如:

Facade::bind([

    'app\facade\Test' => 'app\common\Test',

    'app\facade\Info' => 'app\common\Info',

]);

2.核心Facade类库

系统给内置的常用类库定义了Facade类库,包括:


(动态)类库                          Facade类   



think\App                              think\facade\App   



think\Build                             think\facade\Build   



think\Cache                           think\facade\Cache   



think\Config                          think\facade\Config   



think\Cookie                          think\facade\Cookie   



think\Debug                          think\facade\Debug   



think\Env                               think\facade\Env   



think\Hook                            think\facade\Hook   



think\Lang                             think\facade\Lang   



think\Log                               think\facade\Log   



think\Middleware                  think\facade\Middleware   



think\Request                        think\facade\Request   



think\Response                     think\facade\Response   



think\Route                           think\facade\Route   



think\Session                         think\facade\Session   



think\Url                                 think\facade\Url   



think\Validate                         think\facade\Validate   



think\View                              think\facade\View   


所以你无需进行实例化就可以很方便的进行方法调用,例如:

use think\facade\Cache;

Cache::set('name','value');

echo Cache::get('name');

think\Db类的实现本来就类似于Facade机制,所以不需要再进行静态代理就可以使用静态方法调用(确切的说Db类是没有方法的,都是调用的Query类的方法)。

在进行依赖注入的时候,请不要使用Facade类作为类型约束,而是建议使用原来的动态类,下面是错误的用法:

<?php

namespace app\index\controller;

use think\facade\App;

class Index{

    public function index(App $app)

    {

    }

}

 应当使用下面的方式:

<?php

namespace app\index\controller;

use think\App;

class Index{

    public function index(App $app)

    {

    }

}

 为了更加方便的使用系统类库,系统还给这些常用的核心类库的Facade类注册了类库别名,当进行静态调用的时候可以直接使用简化的别名进行调用。


别名类                            对应Facade类   



App                                think\facade\App   



Build                              think\facade\Build   



Cache                             think\facade\Cache   



Config                            think\facade\Config   



Cookie                            think\facade\Cookie   



Db                                   think\Db   



Debug                             think\facade\Debug   



Env                                  think\facade\Env   



Hook                               think\facade\Hook   



Lang                                think\facade\Lang   



Log                                  think\facade\Log   



Middleware                     think\facade\Middleware   



Request                           think\facade\Request   



Response                         think\facade\Response   



Route                               think\facade\Route   



Session                             think\facade\Session   



Url                                     think\facade\Url   



Validate                             think\facade\Validate   



View                                  think\facade\View   


因此前面的代码可以改成

\Cache::set('name','value');

echo \Cache::get('name');

 

四、Facade具体实例演示

1.container.php源码如下:


实例

<?php

//下面是工具类
//数据库操作类
class Db
{
	//数据库连接
	public function connect()
	{
		return '数据库连接成功<br>';
	}
}

//数据验证类
class Validate
{
	//数据验证
	public function check()
	{
		return '数据验证成功<br>';
	}
}

//视图类
class View
{
	//内容输出
	public function display()
	{
		return '用户登录成功';
	}
}
/***********************************************************************/
//一、创建容器类
class Container
{
	//
	protected $instance = [];

	//将需要实例化的类与它的实现方法进行绑定:将对象容器初始化
	public function bind($abstract,Closure $process)
	{
		$this->instance[$abstract] = $process;
	}

	//创建特定类的实例
	public function make($abstract,$params=[])
	{
		//
		return call_user_func_array($this->instance[$abstract],[]);
	}
}
/***********************************************************************/
//二、服务注册:调用容器的 bind()将对象注册到容器中
$container = new Container();

//将Db类绑定到容器中
$container->bind('db',function(){
	return new Db();
});
//将Validate类绑定到容器中
$container->bind('validate',function(){
	return new validate();
});
//将View类绑定到容器中
$container->bind('view',function(){
	return new view();
});

运行实例 »

点击 "运行实例" 按钮查看在线实例

2.demo.php代码:

实例

<?php
/*
    facade:门面模式,也叫外观模式
    就是将一些操作进行封装,对外提供一个统一的接口
 */
//导入容器
require 'container.php';

/**
 * facade类实现三个功能:
 * 1.连接数据库
 * 2.数据验证
 * 3.输出提示
 */
class Facade
{
	//保存容器对象
	protected static $container=null;

	//创建初始化方法,给容器对象赋值
	public static function initialize(Container $container)
	{
		static::$container = $container;
	}

	//1.连接数据库
	public static function connect()
	{
		return static::$container->make('db')->connect();
	}

	//2.数据验证
	public static function check()
	{
		return static::$container->make('validate')->check();
	}

	//3.输出提示
	public static function display()
	{
		return static::$container->make('view')->display();
	}
}

//客户端调用
echo '<h3>(简化)使用外观模式Facade门面统一调用:</h3>';
Facade::initialize($container);
echo Facade::connect();
echo Facade::check();
echo Facade::display();

运行实例 »

点击 "运行实例" 按钮查看在线实例





批改状态:未批改

老师批语:
本博文版权归博主所有,转载请注明地址!如有侵权、违法,请联系admin@php.cn举报处理!
全部评论 文明上网理性发言,请遵守新闻评论服务协议
0条评论
作者最新博文
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2024 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号

  • 登录PHP中文网,和优秀的人一起学习!
    全站2000+教程免费学