TOP-framework/framework/library/Router.php

468 lines
15 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace top\library;
use top\library\exception\RouteException;
use top\library\http\Request;
use top\middleware\ifs\MiddlewareIfs;
use top\traits\Instance;
/**
* 路由类
* @author topnuomi 2018年11月19日
*/
class Router
{
use Instance;
/**
* 路由配置
* @var array
*/
private $config = [];
/**
* 请求类
* @var Request
*/
private $request = null;
/**
* 是否绑定模块
* @var bool
*/
private $isBindModule = false;
/**
* 模块
* @var string
*/
private $module = '';
/**
* 类全限定名
* @var string
*/
private $controllerFullName = '';
/**
* 类名
* @var string
*/
private $controller = '';
/**
* 方法
* @var string
*/
private $method = '';
/**
* 参数
* @var array
*/
private $params = [];
/**
* 当前加载的路由
* @var array
*/
private $loadRuleParameters = [];
/**
* Router constructor.
* @param Request $request
*/
private function __construct(Request $request)
{
$this->request = $request;
}
/**
* 完整控制器名
* @return mixed
*/
public function controllerFullName()
{
return $this->controllerFullName;
}
/**
* 控制器名
* @return mixed
*/
public function controller()
{
return $this->controller;
}
/**
* 模块名
* @return mixed
*/
public function module()
{
return $this->module;
}
/**
* 方法名
* @return mixed
*/
public function method()
{
return $this->method;
}
/**
* 请求参数
* @return mixed
*/
public function params()
{
return $this->params;
}
/**
* 是否绑定模块
* @param bool $isBindModule
*/
public function isBindModule($isBindModule)
{
$this->isBindModule = $isBindModule;
}
/**
* 查找最适合的路由匹配
* @param $rules
* @param $uri
* @return mixed
*/
private function findRule($rules, $uri)
{
$result = [];
if ($uri == '/' && isset($rules['/'])) {
$result[] = '/';
} else {
// 如果不是首页则unset掉首页的规则避免参与计算导致出错
unset($rules['/']);
$keys = array_keys($rules);
foreach ($keys as $key) {
$pos = strpos($uri, $key);
if ($pos !== false) {
$endPos = $pos + strlen($key);
$result[$endPos] = $key;
}
}
}
if (!empty($result)) {
$max = max(array_keys($result));
$rest = str_replace($result[$max], '', $uri);
if (($result[$max] == '/' && $uri != '/') || ($rest != '' && substr($rest, 0, 1) != '/')) {
return false;
} else {
$this->loadRuleParameters = $rules[$result[$max]];
return [
'rule' => $result[$max],
'parameters' => $this->loadRuleParameters,
];
}
}
return false;
}
/**
* 解析路由规则
* @param $requestMethod
* @param $uri
* @return array|bool|mixed
*/
private function parseRouteRule($requestMethod, $uri)
{
// 获取所有路由配置(可能从缓存文件读取)
$routeConfig = $this->getRouteConfig();
$rule = [];
if (isset($routeConfig[$requestMethod])) { // 第一次去当前请求方法中查找
$rule = $this->findRule($routeConfig[$requestMethod], $uri);
}
if (empty($rule) && isset($routeConfig['any'])) { // 全部中查找
$rule = $this->findRule($routeConfig['any'], $uri);
}
return (!empty($rule)) ? $rule : false;
}
/**
* 普通路由处理
* @param $uri
* @return array
*/
private function parseRoute($uri)
{
// 普通处理
$uriArray = explode('/', trim($uri, '/'));
// 如果没有参数,则使用默认的控制器和方法
$uriArray[0] = (isset($uriArray[0]) && $uriArray[0]) ? $uriArray[0] : config('default_controller');
$uriArray[1] = (isset($uriArray[1]) && $uriArray[1]) ? $uriArray[1] : config('default_method');
$controller = ucfirst($uriArray[0]);
$rule['class'] = APP_NS . '\\' . $this->module . '\\controller\\' . $controller;
$rule['method'] = $uriArray[1];
return [
'rule' => $uriArray[0] . '/' . $uriArray[1],
'parameters' => $rule,
];
}
/**
* 解析请求参数
* @param $class
* @param $method
* @param $prefix
* @param $uri
* @return array
*/
private function parseParameters($class, $method, $prefix, $uri)
{
$paramsString = ltrim(substr_replace($uri, '', 0, strlen($prefix)), '/');
$paramsArray = explode('/', $paramsString);
$params = [];
if (config('complete_parameter')) { // 如果开启了完全参数名
for ($i = 0; $i < count($paramsArray); $i += 2) {
if (isset($paramsArray[$i + 1])) {
$_GET[$paramsArray[$i]] = $params[$paramsArray[$i]] = $paramsArray[$i + 1];
}
}
} else { // 未开启完全参数名,则利用反射得到参数名做映射
if (!empty($paramsArray)) {
$reflectionMethod = Application::getReflectionMethod($class, $method);
$index = 0;
foreach ($reflectionMethod->getParameters() as $parameter) {
$className = $parameter->getClass();
if (is_null($className)) {
$_GET[$parameter->name] = $params[$parameter->name] = $paramsArray[$index];
$index++;
}
}
}
}
return $params;
}
/**
* 执行应用
* @return mixed
* @throws RouteException
*/
public function execute()
{
try {
// 绑定模块优先
if ($this->isBindModule) {
$this->module = BIND_MODULE;
$uri = $this->request->uri();
} else {
// 如果没有直接绑定模块,则从链接获取
$rawUri = $this->request->uri();
if (!$rawUri) {
$this->module = DEFAULT_MODULE;
$uri = $rawUri;
} else {
// 取第一部分为模块名称
$pos = strpos($rawUri, '/');
if (false !== $pos) {
$uri = substr($rawUri, $pos + 1);
$this->module = substr($rawUri, 0, $pos);
} else {
$this->module = $uri = $rawUri;
}
}
}
define('CURRENT_MODULE', $this->module());
Application::afterRouter();
// 处理路由
$this->handler($uri);
} catch (RouteException $exception) {
if (!DEBUG) { // 非调试模式直接404
return \response()->code(404)->send();
} else throw $exception;
}
// 路由中间件处理
return $this->middleware(function () {
return Application::callMethod($this->controllerFullName, $this->method, $this->params);
});
}
/**
* 中间件处理
* @param \Closure $application
* @return mixed
*/
public function middleware(\Closure $application)
{
// 加载全局配置文件中配置的中间件
$middleware = array_reverse(config('middleware'));
// 配置中不执行的中间件
$exceptMiddlewareArray = [];
if (isset($this->loadRuleParameters['except_middleware'])
&& $this->loadRuleParameters['except_middleware'] != ''
) {
$exceptMiddlewareArray = $this->loadRuleParameters['except_middleware'];
}
// 配置中新增的中间件
if (isset($this->loadRuleParameters['accept_middleware'])
&& $this->loadRuleParameters['accept_middleware'] != ''
) {
$acceptMiddlewareArray = $this->loadRuleParameters['accept_middleware'];
foreach ($acceptMiddlewareArray as $acceptMiddleware) {
if (!in_array($acceptMiddleware, $middleware)) {
$middleware[] = $acceptMiddleware;
} else continue;
}
}
// 应用打包在在洋葱圈最里层
$next = $application;
foreach ($middleware as $value) {
if (!in_array($value, $exceptMiddlewareArray)) {
$next = function () use ($next, $value) {
$middleware = new $value;
if ($middleware instanceof MiddlewareIfs) {
return $middleware->handler($this->request, $next);
} else throw new RouteException('无效的中间件:' . $value);
};
}
}
return $next();
}
/**
* 处理URI
* @param $uri
* @throws RouteException
*/
public function handler($uri)
{
// 检查模块是否存在
if (!is_dir(APP_PATH . $this->module)) {
throw new RouteException('不存在的模块:' . $this->module);
}
// 如果为空则默认为/
$uri = $uri ? $uri : '/';
$defaultMethod = config('default_method');
$requestMethod = strtolower($this->request->requestMethod());
// 第一次用原始uri去做匹配第二次带上默认方法去做匹配
if (false === ($rule = $this->parseRouteRule($requestMethod, $uri))
&& false === ($rule = $this->parseRouteRule($requestMethod, $uri . '/' . $defaultMethod))
) {
// 如果开启强制路由,则抛异常
if (config('compel_route') === true) {
throw new RouteException('不支持的路由规则:' . strtoupper($requestMethod) . ' ' . $uri);
} else {
// 进行普通处理
$rule = $this->parseRoute($uri);
}
}
$ruleParameters = $rule['parameters'];
$this->controllerFullName = $ruleParameters['class'];
$this->controller = substr($this->controllerFullName, strrpos($ruleParameters['class'], '\\') + 1);
$this->method = $ruleParameters['method'];
// 此处还需要检查控制器和方法是否存在
if (!class_exists($this->controllerFullName)) {
throw new RouteException('不存在的控制器:' . $this->controllerFullName);
}
if (!method_exists($this->controllerFullName, $this->method)) {
throw new RouteException('不存在的方法:' . $this->method);
}
$this->params = $this->parseParameters($ruleParameters['class'], $ruleParameters['method'], $rule['rule'], $uri);
}
/**
* 创建路由配置缓存文件
* @return array|mixed
*/
public function getRouteConfig()
{
$fileName = './runtime/' . $this->module . '_route_cache.php';
if (!DEBUG && is_file($fileName)) {
return require $fileName;
} else {
$result = [];
$controllerPath = APP_PATH . $this->module . '/controller/';
$namespace = APP_NS . '\\' . $this->module . '\\controller';
$files = scandir($controllerPath);
for ($i = 2; $i < count($files); $i++) {
$className = $namespace . '\\' . pathinfo($files[$i])['filename'];
$reflectionClass = Application::getReflectionClass($className);
foreach ($reflectionClass->getMethods() as $method) {
if ($method->class == $className && substr($method->name, 0, 1) != '_') {
$annotation = Annotation::getMethodAnnotation($className, $method->name);
$requestMethod = (isset($annotation['requestMethod'])) ? $annotation['requestMethod'] : 'any';
if (isset($annotation['route'])) {
$requestUri = $annotation['route'];
} else continue;
$requestMethod = strtolower($requestMethod);
$rule = ($requestUri == '/') ? $requestUri : trim($requestUri, '/');
$result[$requestMethod][$rule] = [
'class' => $className,
'method' => $method->name,
'except_middleware' => [],
'accept_middleware' => [],
];
if (isset($annotation['exceptMiddleware']) && $annotation['exceptMiddleware'] != '') {
foreach (explode('|', $annotation['exceptMiddleware']) as $exceptMiddleware) {
$result[$requestMethod][$rule]['except_middleware'][] = $exceptMiddleware;
}
}
if (isset($annotation['acceptMiddleware']) && $annotation['acceptMiddleware'] != '') {
foreach (explode('|', $annotation['acceptMiddleware']) as $acceptMiddleware) {
$result[$requestMethod][$rule]['accept_middleware'][] = $acceptMiddleware;
}
}
}
}
}
// 加载配置文件中的路由配置
$routeConfigFile = CONFIG_DIR . 'route.php';
if (is_file($routeConfigFile)) {
$routeConfig = require $routeConfigFile;
foreach ($routeConfig as $key => $value) {
if (isset($result[$key])) { // 存在当前请求方法的配置就检查含有的路由配置
foreach ($value as $uri => $config) {
$uri = ($uri == '/') ? $uri : trim($uri, '/');
if (isset($result[$key][$uri])) { // 如果已经存在这个路由配置,可能不完全,直接合并覆盖已有项
$result[$key][$uri] = array_merge($result[$key][$uri], $config);
} else {
$result[$key][$uri] = $config;
}
}
} else {
$result[$key] = $value;
}
}
}
// 写入文件
ob_start();
var_export($result);
$content = ob_get_contents();
ob_clean();
file_put_contents($fileName, "<?php\nreturn " . $content . ';');
return $result;
}
}
}