依赖注入
概念:指服务依赖的其他服务不通过服务自己创建的方式, 而是由外部传入的方式。
如何实现的?答:通常来说使用反射实现的.。
能解决什么问题?答:降低服务模块之间的耦合度,编写代码时不用考虑外部服务的具体实现,只需要依据接口来使用服务即可。
当A类需要依赖于B类,也就是说需要在A类中实例化B类的对象来使用时候,如果B类中的功能发生改变,也会导致A类中使用B类的地方也要跟着修改,导致A类与B类高耦合。这个时候解决方式是,A类应该去依赖B类的接口,把具体的类的实例化交给外部。
就拿我们业务中常用的通知模块来说。
/**
* 定义了一个消息类
* Class Message
*/
class Message{
public function seed()
{
return 'seed email';
}
}
/*
* 订单产生的时候 需要发送消息
*/
class Order{
protected $messager = '';
function __construct()
{
$this->messager = new Message();
}
public function seed_msg()
{
return $this->messager->seed();
}
}
$Order = new Order();
$Order->seed_msg();上面的代码是我们传统的写法。首先由个消息发送的类。然后在我们需要发送消息的地方,调用发送消息的接口。有一天你需要添加一个发送短信的接口以满足不同的需求。那么你会发现你要再Message类里面做修改。同样也要再Order类里面做修改。这样就显得很麻烦。这个时候就有了依赖注入的思路。下面把代码做一个调整
/**
* 为了约束我们先定义一个消息接口
* Interface Message
*/
interface Message{
public function seed();
}
/**
* 有一个发送邮件的类
* Class SeedEmail
*/
class SeedEmail implements Message
{
public function seed()
{
return 'seed email';
// TODO: Implement seed() method.
}
}
/**
*新增一个发送短信的类
* Class SeedSMS
*/
class SeedSMS implements Message
{
public function seed()
{
return 'seed sms';
// TODO: Implement seed() method.
}
}
/*
* 订单产生的时候 需要发送消息
*/
class Order{
protected $messager = '';
function __construct(Message $message)
{
$this->messager = $message;
}
public function seed_msg()
{
return $this->messager->seed();
}
}
//我们需要发送邮件的时候
$message = new SeedEmail();
//将邮件发送对象作为参数传递给Order
$Order = new Order($message);
$Order->seed_msg();
//我们需要发送短信的时候
$message = new SeedSMS();
$Order = new Order($message);
$Order->seed_msg();这样我们就实现了依赖注入的思路,是不是很方便扩展了。
服务容器
我理解的服务容器就是一个自动产生类的工厂。
/**
* 为了约束我们先定义一个消息接口
* Interface Message
*/
interface Message{
public function seed();
}
/**
* 有一个发送邮件的类
* Class SeedEmail
*/
class SeedEmail implements Message
{
public function seed()
{
return 'seed email';
// TODO: Implement seed() method.
}
}
/**
*新增一个发送短信的类
* Class SeedSMS
*/
class SeedSMS implements Message
{
public function seed()
{
return 'seed sms';
// TODO: Implement seed() method.
}
}
/**
* 这是一个简单的服务容器
* Class Container
*/
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);
}
}
//创建一个消息工厂
$message = new Container();
//将发送短信注册绑定到工厂里面
$message->bind('SMS',function (){
return new SeedSMS();
});
//将发送邮件注册绑定到工厂
$message->bind('EMAIL',function (){
return new SeedEmail();
});
//需要发送短信的时候
$SMS = $message->make('SMS');
$SMS->seed();container是一个简单的服务容器里面有bind,make两个方法
bind是向容器中绑定服务对象。make则是从容器中取出对象。
bind
在bind方法中需要传入一个 concrete 我们可以传入一个实例对象或者是一个闭包函数。
可以看到我这全使用的是闭包函数,其实也可以这样写
$sms = new SeedSMS();
$message->bind('SMS',$sms);后面这种写法与闭包相比的区别就是我们需要先实例化对象才能往容易中绑定服务。而闭包则是我们使用这个服务的时候才去实例化对象。可以看出闭包是有很多的优势的。
make
make方法就从容器中出去方法。里面首先判断了instances变量中是否有当前以及存在的服务对象,如果有直接返回。如果没有那么会通过 call_user_func_array返回一个对象.
匿名函数(闭包函数)
匿名函数(Anonymous functions)就是没有函数名的函数,也叫闭包函数(closures),是在 php5.3 中新增一个特性。最常用的就是回调函数的参数值。(http://php.net/manual/zh/functions.anonymous.php )
php闭包 子函数可以使用父函数中的局部变量,这种行为叫做闭包 关键词 (use)
在php中 匿名函数也叫作闭包函数,允许临时创建一个没有指定名称的函数, 经常被用作回调函数
注意: 可以在闭包中使用 func_num_args(),func_get_arg() 和 func_get_args()。
#匿名函数的定义:
$closureFunc = function(){
....
};
#闭包函数也可以作为变量的值来使用。
$closureFunc = function($str){
echo $str;
};
$closureFunc("hello world!"); //输出: hello world!
#继承 $message
$message = 'hello';
$example = function () use ($message) {
var_dump($message);
};
$example();
#通过引用继承
$message = 'hello2';
$example = function () use (&$message) {
var_dump($message);
};
$example();
#闭包函数也可以接受常规参数
$message = 'world';
$example = function ($arg) use ($message) {
var_dump($arg . ' -:' . $message);
};
$example("hello");
#返回类型在 use 子句的后面
$example = function () use ($message): string {
return "hello $message";
};
var_dump($example());使用 use 关键字,闭包函数可以实现从父级作用域中继承变量
function demo() {
$website = 'C语言中文网<br>';
$url = 'http://c.biancheng.net/php/';
$func = function() use ($website) {
echo '$website = '.$website;
echo '$url = '.$url;
};
$func();
}
demo();总结:
1、闭包函数不能直接访问闭包外的变量,而是通过use 关键字来调用上下文变量(闭包外的变量),也就是说通过use来引用上下文的变量;
2、闭包内所引用的变量不能被外部所访问(即,内部对变量的修改,外部不受影响),若想要在闭包内对变量的改变从而影响到上下文变量的值,需要使用&的引用传参。
use所引用的是变量的复制(副本而),并不是完全引用变量。如果要达到引用的效果,就需要使用 & 符号,进行引用传递参数;
php闭包有什么用?
实现php闭包既可以读取函数内部的变量,同时还可以将变量始终保存在内存中,使得即使函数执行完毕,变量也将一直存在。
闭包的几个作用:
1、减少foreach的循环的代码
比如手册http://php.com/manual/en/functions.anonymous.php 中的例子Cart
/**
* 一个基本的购物车,包括一些已经添加的商品和每种商品的数量
*
*/
class Cart {
// 定义商品价格
const PRICE_BUTTER = 10.00;
const PRICE_MILK = 30.33;
const PRICE_EGGS = 80.88;
protected $products = array();
/**
* 添加商品和数量
*
* @access public
* @param string 商品名称
* @param string 商品数量
*/
public function add($item, $quantity) {
$this->products[$item] = $quantity;
}
/**
* 获取单项商品数量
*
* @access public
* @param string 商品名称
*/
public function getQuantity($item) {
return isset($this->products[$item]) ? $this->products[$item] : FALSE;
}
/**
* 获取总价
*
* @access public
* @param string 税率
*/
public function getTotal($tax) {
$total = 0.00;
$callback = function ($quantity2, $item2) use ($tax, &$total) {
$pricePerItem = constant(__CLASS__ . "::PRICE_" . strtoupper($item2)); //调用以上对应的常量
$total += ($pricePerItem * $quantity2) * ($tax + 1.0);
};
var_dump($this->products);
array_walk($this->products, $callback);
return round($total, 2);
}
}
$my_cart = new Cart;
// 往购物车里添加商品及对应数量
$my_cart->add('butter', 10);
$my_cart->add('milk', 3);
$my_cart->add('eggs', 12);
// 打出出总价格,其中有 3% 的销售税.
echo $my_cart->getTotal(0.03);//输出 1196.42、减少函数的参数
function html ($code ,$id="",$class=""){
if ($id !=="")$id =" id = \"$id\"" ;
$class = ($class !=="")?" class =\"$class\"":">";
$open ="<$code$id$class";
$close ="</$code>";
return function ($inner ="")use ($open,$close){
return "$open$inner$close";
};
}3、解除递归函数
$fib =function($n)use(&$fib) {
if($n == 0 || $n == 1) return 1;
return $fib($n - 1) + $fib($n - 2);
};
echo $fib(2) . "\n";// 2
$lie =$fib;
$fib =function(){die('error');};//rewrite $fib variable
echo $lie(5);// error because $fib is referenced by closure注意上题中的use使用了&,这里不使用&会出现错误n-1)是找不到function的(前面没有定义fib的类型)
所以想使用闭包解除循环函数的时候就需要使用这样的形式
<?php
$recursive =function ()use (&$recursive){
// The function is now available as $recursive
}4、关于延迟绑定
如果你需要延迟绑定use里面的变量,你就需要使用引用,否则在定义的时候就会做一份拷贝放到use中
<?php
$result = 0;
$one =function()
{
var_dump($result);
};
$two = function() use ($result)
{
var_dump($result);
};
$three = function() use (&$result)
{
var_dump($result);
};
$result++;
$one(); // outputs NULL: $result is not in scope
$two(); // outputs int(0): $result was copied
$three(); // outputs int(1)使用引用和不使用引用就代表了是调用时赋值,还是申明时候赋值
PHP8.0新特性之一注解 ,注解机制:
注解功能使得代码中的声明部分都可以添加结构化、机器可读的元数据, 注解的目标可以是类、方法、函数、参数、属性、类常量。 通过 反射API 可在运行时获取注解所定义的元数据。 因此注解可以成为直接嵌入代码的配置式语言。
说人话就是,注解实现的原理是反射,通过动态代理模式可以直接嵌入代码的配置,注解可以作用在类,方法,函数,参数,属性和常量上面。
使用注解可以在实现功能、使用功能相互解耦
框架可以基于元信息为代码提供各种额外功能,本质上注解就是配置的另外一种展现形式:比如:
通过注解的方式实现权限的控制,就比配置文件当中的配置要更加方便。
比如利用注解的方式配置路由、配置定时任务。
在php中,注解的语法是总是以 #[ 开头,以 ] 结尾来包围。内部则是一个或以逗号包含的多个注解,注解的名称使用命名空间,可以是非限定、限定、完全限定的名称。注解的参数是可以选的,以常见的括号()包围。 注解的参数可以是字面值或者常量表达式。 它同时接受位置参数和命名参数两种语法。
参考:
PHP注释AOP的实现,PHP实现AOP的雏形 , php注解定义,php8注解详解 , PHP8.0新特性之一注解
理解依赖注入(DI)和控制反转(IOC) 、容器、依赖注入、控制反转、反射各个概念的理解和使用
本文为崔凯原创文章,转载无需和我联系,但请注明来自冷暖自知一抹茶ckhttp://www.cksite.cn