概念
-
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度, 是一种编程思想,能让我们设计出更为优良的程序。
-
依赖注入(DI) 是实现IOC的一种方式,通过该方式可以将存在对象内部的依赖转移到外部,动态的将依赖注入到对象内部,实现代码的解耦。
-
先看下以下代码有什么问题:
/** * 用户类 */ class User { private $name = '张三'; public function name() { return $this->name; } } /** * 应用类 */ class App { protected $user; public function __construct() { $this->user = new User(); } public function printUserName() { echo $this->user->name(); } } $app = new App(); $app->printUserName();
代码中定义了一个
User
类和App
类, 在App
内部使用new User()
创建了一个user实例
,如果我想给User
类名改成复数Users
, 同时还需要去修改App
类中内部的代码, 违反了开放封闭原则,这时候如果我们将依赖转移到外部,则可以不用修改内部代码,如下:<?php /** * 用户类 */ class User { private $name = '张三'; public function name() { return $this->name; } } /** * 应用类 */ class App { protected $user; public function __construct(User $user) { $this->user = $user; } public function printUserName() { echo $this->user->name(); } } $app = new App(new User()); $app->printUserName();
App
类在构造方法中以参数的形式传入,只需要在外部把实例化好的User
对象传入。
反射
-
先来阅读我项目中的
UserController
控制器的部分代码:class UserController extends Controller { public function __construct(UserService $service) { $this->service = $service; } /** * 用户登录 * @param LoginRequest $request * @return false|string */ public function login(LoginRequest $request) { return $this->service->login($request->validated()); } /** * 用户注册 * @param RegisterRequest $request * @return false|string * @throws Exception */ public function register(RegisterRequest $request) { return $this->service->register($request->validated()); } }
可以看到在
控制器
的构造方法中,注入了一个UserService
对象,但是我们根本没有做任何实例化操作,这是Laravel内部帮我们是实现的,那Laravel是如何实现的呢?可能有读者已经想到了,没错,就是反射。 -
反射具有对类、接口、函数、方法和扩展进行反向工程的能力,没使用过的可以参考PHP官方文档-反射。
-
先看一下以下这段反射代码,方便后面理解laravel的ioc。如下:
<?php /** * 用户类 */ class User { private $name; public function __construct($name='ClassmateLin') { $this->name = $name; } public function name() { return $this->name; } } // 获取User的reflectionClass对象 $reflector = new reflectionClass(User::class); // 拿到User的构造函数 $constructor = $reflector->getConstructor(); // 拿到User的构造函数的所有依赖参数, 返回的是一个数组 $dep_params = $constructor->getParameters(); // 创建user对象,不传递参数 $user = $reflector->newInstance(); echo $user->name() . PHP_EOL; // 输出默认值ClassmateLin $name = $dep_params[0]->name; // 拿到构造函数参数数组的第一个参数的参数名。 // 创建user对象,需要传递参数的 $user = $reflector->newInstanceArgs($dep_params=[$name => 'XXX']); echo $user->name() . PHP_EOL; // 输出XXX
可以看出:
- 通过reflectionClass
传入User
类,可以拿到一个反射实例。
- 通过反射实例可以拿到构造函数。
- 通过构造函数可以拿到参数列表。
- 通过反射对象可以创建实例,分为带参数和不带参数两种形式。
简单容器
- 通过了解上文的内容,我们可以创建一个简单的容器来实现对象的实例化。
- 比如你的系统用户登录时,依赖一个日志对象, 通过上述内容,你已经可以写出以下代码:
<?php
/**
* 日志接口
*/
interface Log
{
public function log($msg);
}
/**
* 文件日志的实现
*/
class FileLog implements Log
{
/**
* 将log进行一个简单的输出
* @param $msg
* @return Log|void
*/
public function log($msg)
{
echo '文件日志记录: ' . $msg . PHP_EOL;
}
}
/**
* 数据库日志的实现
*/
class DbLog implements Log
{
public function log($msg)
{
echo '数据库日志记录:' . $msg . PHP_EOL;
}
}
class User
{
private $log;
/**
* @param FileLog $log
*/
public function __construct(FileLog $log)
{
$this->log = $log;
}
/**
* 简单的登录操作
* @param string $username
*/
public function login($username='ClassmateLin')
{
echo '用户:' . $username . '登录成功!';
$this->log->log('日志: 用户:' . $username . '登录成功!');
}
}
该代码中定义了一个日志操作的接口Log
, 并且提供了文件方式和数据库方式的日志具体实现。
但是你可能注意到User
类中为什么不是注入接口,注入接口Log
的化,那不是既可以传入FileLog
实例,也可以传入DbLog
实例,像这样:
class User
{
private $log;
/**
* @param FileLog $log
*/
public function __construct(Log $log)
{
$this->log = $log;
}
/**
* 简单的登录操作
* @param string $username
*/
public function login($username='ClassmateLin')
{
echo '用户:' . $username . '登录成功!';
$this->log->log('日志: 用户:' . $username . '登录成功!');
}
}
$user = new User(new DbLog());
$user = new User(new FileLog());
原因在于反射是不能动态创建接口的,如果想实现的话可以做一个容器绑定,这会在下一篇文章中进行描述。本文先来实现一个简单的容器。
- 定义一个容器:
Application
, 提供一个make
方法通过传入类名参数,进行实例化:
class Application
{
function make(string $class_name)
{
$reflector = new reflectionClass($class_name); // 拿到反射实例
$constructor = $reflector->getConstructor(); // 拿到构造函数
if (is_null($constructor)) { // 如果写构造函数,得到的constructor是null。
return $reflector->newInstance(); // 进行无参数实例化
}
// 拿到构造函数依赖的参数
$dependencies = $constructor->getParameters();
// 这时候我们依赖的参数可能也有参数,通过递归的去获取当前类的参数。
$instance = $this->getDependencies($dependencies);
// 进行带参数的实例化
return $reflector->newInstanceArgs($instance);
}
/**获取依赖
* @param $params
* @return array
*/
private function getDependencies($params)
{
$dependencies = [];
// array_walk相等于foreach, for 的作用,据说速度是最快的,我也没去验证,只是喜欢闭包。
array_walk($params, function ($param) use (&$dependencies) {
$class_name = $param->getClass()->name; // 获取类名
$dependencies[] = $this->make($class_name); // 调用make函数创建实例
});
return $dependencies;
}
}
类中定义了两个函数make
和getDependencies
,通过递归的形式进行拿构造函数参数进行实例化。递归的出口就是当一个类不需要参数的时候。
- 创建一个
User
的实例:
$app = new Application();
$user = $app->make('User'); // 通过传入类名,遍得到了一个user实例
$user->login();
完整代码
如果读完这篇文章有所收获,不妨帮忙点个赞,谢谢老板。
<?php
/**
* 日志接口
*/
interface Log
{
public function log($msg);
}
/**
* 文件日志的实现
*/
class FileLog implements Log
{
/**
* 将log进行一个简单的输出
* @param $msg
* @return Log|void
*/
public function log($msg)
{
echo '文件日志记录: ' . $msg . PHP_EOL;
}
}
/**
* 数据库日志的实现
*/
class DbLog implements Log
{
public function log($msg)
{
echo '数据库日志记录:' . $msg . PHP_EOL;
}
}
class User
{
private $log;
/**
* @param FileLog $log
*/
public function __construct(FileLog $log)
{
$this->log = $log;
}
/**
* 简单的登录操作
* @param string $username
*/
public function login($username='ClassmateLin')
{
echo '用户:' . $username . '登录成功!' . PHP_EOL;
$this->log->log('日志: 用户:' . $username . '登录成功!');
}
}
class Application
{
function make(string $class_name)
{
$reflector = new reflectionClass($class_name); // 拿到反射实例
$constructor = $reflector->getConstructor(); // 拿到构造函数
if (is_null($constructor)) { // 如果写构造函数,得到的constructor是null。
return $reflector->newInstance(); // 进行无参数实例化
}
// 拿到构造函数依赖的参数
$dependencies = $constructor->getParameters();
// 这时候我们依赖的参数可能也有参数,通过递归的去获取当前类的参数。
$instance = $this->getDependencies($dependencies);
// 进行带参数的实例化
return $reflector->newInstanceArgs($instance);
}
/**获取依赖
* @param $params
* @return array
*/
private function getDependencies($params)
{
$dependencies = [];
// array_walk相等于foreach, for 的作用,据说速度是最快的,我也没去验证,只是喜欢闭包。
array_walk($params, function ($param) use (&$dependencies) {
$class_name = $param->getClass()->name; // 获取类名
$dependencies[] = $this->make($class_name); // 调用make函数创建实例
});
return $dependencies;
}
}
$app = new Application();
$user = $app->make('User');
$user->login();