PHP反射模拟实现注解路由

在阅读TP框架源码中,发现TP框架支持使用注解方式定义路由(也称为注解路由),类似于Java中注解,默认关闭,如果需要开启在路由配置文件中设置:

1
2
// 开启注解路由
'route_annotation' => true,

然后只需要直接在控制器类的方法注释中定义,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
namespace app\controller;

class Index
{
/**
* @param string $name 数据名称
* @return mixed
* @route('hello/:name')
*/
public function hello($name)
{
return 'hello,'.$name;
}
}

@route(‘hello/:name’) 就是注解路由的内容,然后就使用下面的URL地址访问:

http://tp5.com/hello/thinkphp

页面输出:

hello,thinkphp

注解路由使路由定义更为方便,但PHP本身没有类似于Java的Annotation机制,那是如何实现的呢?这就不得不说到PHP的反射机制。

反射

PHP自5.0版本以后添加了反射机制,它提供了一套强大的反射API,允许你在PHP运行环境中,对类、接口、函数、方法和扩展进行逆向分析,经常用于高扩展的PHP框架,自动加载插件,自动生成文档等。

在TP5框架中,注解路由都是基于PHP反射来实现,大概思路如下:

  1. 通过反射类获取类的所有public方法;
  2. 分析每个public方法的文档注释,如果含有特定路由标志,则将该方法作为路由处理函数

注解路由实现

下面,我们模拟实现注解路由。先定义一个控制器类 controller/User.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php

class User
{

/**
* @route('/user/login')
* @return [type] [description]
*/
public function login($data)
{

echo "login: ". $data['name'];
}

/**
* @route('/user/info')
* @return [type] [description]
*/
public function info($data)
{

echo "info: ". $data['name'];
}
}

在User控制器中,定义了两个方法,对应两条路由@route('/user/login')@route('/user/info')。从语法层面来讲,这些都只是普通的文档注释,但是我们可以通过解析路由标志和控制器方法,将其关联起来,那我们就可以在特定路由下调用特定控制器方法来处理请求。例如:当请求index.php?s=/user/login时会执行User控制器中login方法。

通常情况下,我们都会将控制器类都统一放在某一个目录下,如controller, 我们分析controller目录下每个控制类中的public方法的文档注释,将含有@route路由标志的方法和路由信息关联起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?php
define('PHP_EXTENSION', '.php');

spl_autoload_register(function($className) {
require_once('controller'.DIRECTORY_SEPARATOR.$className.PHP_EXTENSION);
});

function initRoute()
{
$routes = [];
$controllers = glob('controller/*.php');
// 分析controller目录下所有控制器类
foreach ($controllers as $controller) {
$className = explode('/', $controller)[1];
$className = explode('.', $className)[0];

$classRef = new ReflectionClass($className);
$classMethods = $classRef->getMethods(ReflectionMethod::IS_PUBLIC);
foreach ($classMethods as $classMethod) {
// 分析路由标志
preg_match('/@route\(\'([\w\/]*)\'\)/', $classMethod->getDocComment(), $matches);
if (count($matches) > 1) {
$routes[$matches[1]] = [
'c' => $classRef->newInstance(),
'm' => $classMethod
];
}
}
}

return $routes;
}

$routes = initRoute();
$path = explode('?', $_GET['s']);
$uri = $path[0];
parse_str($path[1], $args);


foreach ($routes as $key => $value) {
if ($key == $uri) {
$value['m']->invoke($value['c'], $args);
}
}

?>

就这样,我们基本上模拟出了TP框架(URL兼容模式)下的注解路由实现。

弊端

Java由于本身语言特性导致非常适合用注解方式来开发,但PHP本身如果用注解路由,不得不使用反射。用反射的话,那性能自然就降低,不得不用cache(缓存路由信息,不然每次都得分析注解),同时也不便于统一管理URl,所以在TP框架中注解路由并不是默认开启。


参考文档

有用就打赏一下作者吧!