magnus / router
模块化框架的路由器。
This package is not auto-updated.
Last update: 2024-09-26 00:10:56 UTC
README
====================== Magnus 对象路由器
© 2016 John Mark Purtle and contributors.
..
https://github.com/jmpurtle/Router
简介
路由是将某个起点和路径结合起来,然后解析该路径所引用的对象作为处理器的过程。这个过程几乎适用于每个Web应用框架(将URL转换为控制器)、RPC系统和甚至是文件系统外壳。此过程的其它术语包括:“遍历”、“调度”或“查找”。
对象路由器是路由过程的一种变体,它尝试将路径元素解析为对象属性链。这与PHP Web框架中使用的典型路由过程形成对比,该过程涉及使用正则表达式匹配。这种正则表达式匹配的主要成本是O(n)的最坏情况性能,在某些情况下,路由器会继续寻找更具体的路由,导致每个路由至少被评估一次。在404的情况下,这可能会变得特别棘手。某些路由器实现将尝试将此过程强制转换为类似树的结构,以获得性能提升,但会以可读性为代价。使用对象路由时,最佳和最坏情况都是O(depth)。如果需要发出404,它可以在第一个评估的对象处终止。
此路由器基于“调度协议 <https://github.com/marrow/Webcore/wiki/Dispatch-Protocol>”,并不打算直接使用,而是作为框架的一部分。这并不意味着路由器不能单独使用。
安装
目前,您必须将文件复制到您的项目中,最好是在 vendors/Magnus/Router/ 目录下,然后根据需要修改自动加载器。或者,您可以通过composer安装magnus/router包。您还需要创建一个类似以下内容的上下文对象。
使用方法
本节分为几个部分,以涵盖集成和此路由器为用户提供交互。
框架使用
首先,必须包含自动加载器,这将允许您解析路由器本身和控制器。
require_once 'vendor/autoload.php';
$router = new \Magnus\Router\Object\ObjectRouter();
现在,路由器的调用需要两个参数,一个是上下文对象,一个是开始路由的基础对象或对象名称。上下文对象是您提供路径、日志、控制器前缀以及可能需要的任何其他内容的对象,以正确实例化对象。
上下文对象的最小要求是
namespace Magnus\Request;
class Context {
protected $requestURI;
protected $baseURI;
protected $requestPath;
protected $appMode;
protected $logger;
protected $controllerPrefix;
public function __construct(Array $config) {
$this->documentRoot = isset($config['documentRoot'])
? $this->normalizeURI($config['documentRoot'])
: $this->normalizeURI($_SERVER['DOCUMENT_ROOT']);
$this->requestURI = isset($config['requestURI'])
? $this->normalizeURI($config['requestURI'])
: '/';
$this->baseURI = isset($config['baseURI'])
? str_replace($this->documentRoot, '', $this->normalizeURI($config['baseURI']))
: '/';
$this->assetRoot = isset($config['assetRoot'])
? $this->normalizeURI($config['assetRoot'])
: rtrim($this->baseURI, '/') . '/src/assets';
$this->appMode = isset($config['appMode'])
? $config['appMode']
: 'DEVELOPMENT';
$this->logger = isset($config['logger'])
? $config['logger']
: null;
$this->controllerPrefix = isset($config['controllerPrefix'])
? $config['controllerPrefix']
: 'Magnus\\Controllers\\';
$this->requestPath = explode('/', $this->requestURI);
if (end($this->requestPath) === '') {
array_pop($this->requestPath);
}
if ($this->requestPath[0] === '') {
array_shift($this->requestPath);
}
}
public function normalizeURI($uri) {
return strtolower(str_replace('\\', '/', $uri));
}
public function getRequestURI() {
return $this->requestURI;
}
public function getAssetRoot() {
return $this->assetRoot;
}
public function setRequestURI($uri) {
$this->requestURI = $this->normalizeURI($uri);
}
public function getBaseURI() {
return $this->baseURI;
}
public function setBaseURI($uri) {
$this->baseURI = $this->normalizeURI($uri);
}
public function getRequestPath() {
return $this->requestPath;
}
public function getAppMode() {
return $this->appMode;
}
public function getLogger() {
return $this->logger;
}
public function getControllerPrefix() {
return $this->controllerPrefix;
}
}
上下文对象处理路径的规范化并将路径分割为数组以供路由过程使用。规范化过程的精确实现完全取决于您。如果将null作为上下文传递给路由器,则它将尝试调用根对象,否则发出404。
第二个参数的基础可以是可解析的类名,例如'RootController',给定 $context->getControllerPrefix() = 'Magnus\Controllers\'
或它可以是已实例化的对象。这为您提供了使用“元路由器”或路由器中间件在路由器之间切换的灵活性。例如,如果无法通过对象路由找到资源,则可以触发路由器事件,切换到可以正确解析请求的不同类型的路由器。
开始路由过程
foreach ($router($context, $root) as $signal) {
list($object, $chunk, $path, $isEndpoint) = $signal;
if ($isEndpoint) {
//Your chosen handling process for obtaining the server response data from $object
}
}
建议您将路由过程包裹在try/catch/finally块中,这样您就可以在出现异常(如\Magnus\Exceptions\HTTPNotFound)的情况下适当地处理渲染过程。
控制器推荐的响应是一个数组,以便它们可以被转换成JSON或通过模板引擎转换成视图。
可路由对象
每个可路由对象至少需要有一个 __invoke($args = [])
方法。以下是一个非常基础的例子
namespace Magnus\Controllers;
class RootController {
public function __invoke($args = []) {
return [];
}
}
为了进一步细化,可以向控制器添加一个字符串或对象属性。
namespace Magnus\Controllers;
class RootController {
public $user = 'UsersController';
public function __invoke($args = []) {
return [];
}
}
如果您需要一些初始设置,比如实例化该属性的对象或设置访问控制级别,请在控制器的构造函数中完成这些操作。
namespace Magnus\Controllers;
class RootController {
public $user;
public function __construct($context) {
$this->user = new UsersController();
}
public function __invoke($args = []) {
return [];
}
}
"但是,如果我有一些受保护或私有的属性/方法呢?尝试访问这些属性和方法时,PHP不会抛出错误吗?" 这是一个好问题。不用担心,对象路由器使用 get_class_methods 和 get_object_vars 来发现属性。这意味着被标记为受保护或私有的任何内容,在处理过程中对象路由器对此一无所知。对于路由器来说,它几乎不存在。这提供了一个很好的副作用,既不会泄露任何信息用于时间攻击,也不需要使用命名约定,例如在名称前添加下划线。
路由器首先尝试尝试实例化当前对象(您的基对象或根控制器),如果它还不是对象的话。
下一步是检查潜在端点。端点是您在控制器中定义的方法,如果发现了一个端点,则路由器将结束在此处,并将端点以及剩余的路径元素提供给您处理。
如果没有找到匹配当前路径元素的方法,则路由器将尝试找到一个同名属性,并继续进行下一个路由步骤。
如果上述条件都不适用(没有找到属性或路径元素是数字),则路由器将检查是否存在一个 'lookup' 方法,并将任何剩余的路径元素和上下文传递给它。一个带有查找功能的可调用示例
namespace Magnus\Controllers;
class UsersController {
public function __invoke($args = []) {
return [];
}
public function lookup($path, $context) {
$userID = $path[0];
$userProfile = new UserController($userID);
return [$userProfile, [$path[0]]];
}
}
查找方法必须返回一个当前对象和一个包含已消耗元素的数组。然后路由器将从剩余路径的开头删除已消耗元素的数量减一的元素。我们不删除最后一个已消耗的元素,因为它自然会在 routeIterator 迭代结束时弹出,这防止我们从路径元素中过度消耗。例如
$chunk = 27; $path = [27, 'modify']; 将变成 $chunk = '/27'; $path = [27, 'modify'];
$chunk = 'projects'; $path = ['projects', 'subdomain', 'user', '27', 'modify']; 将变成 $chunk = '/projects/subdomain'; $path = ['subdomain', 'user', '27', 'modify'];
如果查找抛出异常,则将抛出 \Magnus\Exceptions\HTTPNotFound 异常。
如果之前的检查不适用,则将抛出 \Magnus\Exceptions\HTTPNotFound 异常。
当没有更多的路径元素可以消耗时,如果尚未实例化,调度器将尝试实例化当前类。然后,它将检查当前对象是否可以作为一个函数 __invoke($args)
来调用,并在成功的情况下提供它。否则,检查父对象是否可以调用,并提供它。
未来更新
移除路由器和上下文对象/日志机制之间的耦合。理想情况下,上下文将是一个自己的包,而路由器应减少对上下文对象提供的特定函数的依赖。
尽可能接近100%的测试覆盖率。
集成Travis以实现自动构建。
许可协议
对象路由器已在MIT开源许可下发布。
MIT许可协议
版权所有 © 2015 John Mark Purtle 及其贡献者。
在此特此授予任何获得此软件及其相关文档文件(“软件”)副本的任何人免费处理该软件的权利,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或出售软件副本的权利,并允许向软件提供方提供软件的人这样做,前提是受以下条件的约束
上述版权声明和本许可声明应包含在软件的所有副本或主要部分中。
软件按“原样”提供,不提供任何形式的保证,无论是明示的、暗示的,包括但不限于适销性、适用于特定目的和非侵权的保证。在任何情况下,作者或版权持有人不对任何索赔、损害或其他责任承担责任,无论源于、因之或与软件的使用或其他方式有关,包括合同行为、侵权或其他行为。