用 PHP 8 的 Attribute 实现 Spring 中的 FactoryBean
去年写了一年多 Java,今年重新回归 PHP,在使用 Java 的过程中,有一些非常方便的特性,比如依赖自动注入,在 Java 中只需要一个简单的 @Resource
注解,即可注入所需要的依赖。
public class UserService{
@Resource
private UserDAO userDAO;
public UserEntity getUserById(Long id){
return userDAO.queryUserById(id);
}
}
而在 PHP 中,现框架基本都是依赖 __construct
方法注入的,属性的类型定义也是 PHP 7.4 版本才支持的。
class UserService{
private $userQuery;
public function __construct(UserQuery $userQuery){
$this->userQuery = $userQuery;
}
public function getUserById($id){
return $this->userQuery->queryUserById($id);
}
}
动态代理
Spring 容器中的对象基本都是代理对象,之前有研究过 Java 动态代理的原理:博客:CGLIB代理
后面我根据 Java 中的动态代理,在 PHP 中实现了一番,发现连反射都用不上。
<?php
namespace Minor\Proxy\Main;
class ProxyFactory
{
/**
* 被代理的对象
* @var string
*/
private $class;
/**
* 拦截器
* @var InterceptorInterface
*/
private $interceptor;
public function __construct($class, InterceptorInterface $interceptor)
{
$this->class = $class;
$this->interceptor = $interceptor;
}
public function __call($name, $args)
{
// 调用代理类的方法时,转发到拦截器中
return $this->interceptor->intercept($this->class, new MethodCaller($name), $args);
}
public static function create($class, InterceptorInterface $interceptor)
{
return new static($class, $interceptor);
}
}
<?php
namespace Minor\Proxy\Main;
// 拦截器 interface
interface InterceptorInterface
{
public function intercept($class, MethodInterface $method, array $args);
}
<?php
namespace Minor\Proxy\Main;
// 方法对象,这对象优点多余,在拦截器调用 call_user_func_array 就可以了
class MethodCaller
{
/**
* @var string
*/
private $name;
/**
* MethodCaller constructor.
* @param $name
*/
public function __construct($name)
{
$this->name = $name;
}
public function invoke($class, array $args)
{
return call_user_func_array([$class, $this->name], $args);
}
public function __toString()
{
return $this->name;
}
}
调用:
class A {
public function run(){
return "this is function A";
}
}
class Interceptor implements InterceptorInterface{
private function before(){
echo "before A run\n";
}
private function after(){
echo "after A run\n";
}
public function intercept($class, MethodInterface $method, array $args){
$this->before();
$result = $method->invoke($class,$args);
$this->after();
return $result;
}
}
$a = ProxyFactory::create(new A(),new Interceptor());
$a->run();
以上是我之前仿照 Java 写的动态代理,动态代理某个类之后,这个类就可以对他进行切面,在 PHP 的框架底层设计都有切面相关的实现,具体切面作用就不展开讲了。接下来就是在动态代理的基础上实现属性的自动注入(@Resource
)功能。
自动注入
class A {
private B $b;
public function run(){
return $b->run();
}
}
class B{
public function run(){
return "B run";
}
}
在以上代码中 A
依赖 B
,但是并没有对 $b
赋值,这个时候调用 run 方法肯定会报错,回到文章开头,因为类的属性类型定义是 PHP 7.4 才支持的,所以大部分框架还是依赖 __construct
进行依赖注入,现在我们修改一下 ProxyFactory
的代码,通过反射的方式实现自动注入。
先创建一个属性注入的工具类。
<?php
namespace Minor\Proxy\Proxy;
class PropertyInjector
{
public function inject($class)
{
// 传入被代理对象,获取被代理对象的反射
$reflectionClass = new \ReflectionClass($class);
// 获取被代理对象的所有 property
$properties = $reflectionClass->getProperties();
foreach ($properties as $property) {
// 获取 property 的 type,该方法 PHP 7.4 才支持
$type = $property->getType();
// 如果没有定义 type ,先跳过,只针对定义了 type 的属性进行注入
if (null === $type) {
continue;
}
// 获取 type 名字,即 classname
$name = $type->getName();
// 这里图省事,直接 new 了这个 type,后续可加入依赖注入
$propertyObject = new $name(); //todo di
// 把创建好的对象赋值给 property
$this->setProperty($property, $class, $propertyObject);
}
return $class;
}
/**
* @param \ReflectionProperty $property
* @param $class
* @param mixed $propertyObject
*/
protected function setProperty(\ReflectionProperty $property, $class, mixed $propertyObject): void
{
// 设置当前属性为可处理的
$property->setAccessible(true);
// 赋值
$property->setValue($class, $propertyObject);
}
}
ProxyFactory
public function __construct($class, InterceptorInterface $interceptor = null)
{
// 增加了一个属性注入的过程
$this->propertyInjector = new PropertyInjector();
$this->class = $this->propertyInjector->inject($class);
$this->interceptor = $interceptor ?? self::getGlobalInterceptor();
}
以上是实现自动注入的简单过程,下面是注入工厂的实现。
注入工厂
就是在注入属性时,可自定义实现创建属性对象的功能。
比方说以下场景,A 依赖 B,B 是一个接口,并没有真正的实现类,只有在调用的时候才需要实现(比如RPC调用)
class A {
private B $b;
public function run(){
return $this->b->run();
}
}
interface B {
public funtion run();
}
修改 PropertyInjector
public function inject($class)
{
foreach ($properties as $property) {
... 省略使用 type 自动注入的代码
// 利用 PHP 8 的 Attribute ,实现 PropertyFactory
if ($this->setPropertyByAttribute($property, $class)) {
continue;
}
}
return $class;
}
/**
* @param \ReflectionProperty $property
* @param $class
*/
protected function setPropertyByAttribute(\ReflectionProperty $property, $class,$type): bool
{
// getAttributes 方法是 PHP 8 新增的
$attributes = $property->getAttributes();
if (count($attributes) > 0) {
// 获取 factory 的实例
// 图省事,直接取第一个属性,后续完善需要遍历,newInstance 可能需要依赖注入,还没深入研究
$factory = $attributes[0]->newInstance(); //todo di
// PropertyFactory 是我们定义的一个工厂接口,里面有一个 create($class) 方法,返回属性对象
if ($factory instanceof PropertyFactory) {
// 给属性赋值
$this->setProperty($property, $class, $factory->create($type));
}
return true;
}
return false;
}
修改 A
class A {
#[RpcProperty]
private B $b;
public function run(){
return $this->b->run();
}
}
class RpcProperty implements PropertyFactory{
public function create($class){
// 这一步需要创建一个继承于 $class 的类,这里省略,具体实现可查看我仓库的源码
return new RpcProxy($class);
}
}
class RpcProxy {
private $target;
public function __construct($target){
$this-target = $target;
}
public function __call($method,$args){
// 伪代码,将被调用的类、方法名、参数,通过 RPC 发送
return rpc_call($this->targetClass,$method,$args);
}
}
源码:github.com/minororange/function-pr...
本作品采用《CC 协议》,转载必须注明作者和本文链接