mindplay / walkway
PHP 的优雅、模块化路由器
Requires
- php: >=5.3.0
Requires (Dev)
- container-interop/container-interop: ^1.1
- mindplay/testies: ^0.2.0
- phpunit/php-code-coverage: 2.*@dev
Suggests
- container-interop/container-interop: provides interop with various DI containers (via InteropInvoker)
README
PHP 的优雅、模块化路由 - 受 vlucas/vlucas/bulletphp 启发。
支持通过 container-interop/container-interop 使用 PHP-DI、Aura.DI、Unbox 以及许多其他 DI 容器。
请注意,这不是一个框架,也不是一个微框架 - 该库仅处理路由,并且故意不提供任何类型的控制器、请求/响应或控制器/动作抽象、错误处理或其他框架类功能。
这使得库非常灵活 - 您可以使用路由功能将任何您想要的内容(任何类似于路径的内容)路由到任何您想要的地方。(例如,控制器、其他脚本、另一个框架或 CMS、任何内容。)
与大多数使用此样式/方法的路由器不同,这个路由器是功能性的 - 这意味着路由实际上是在解析器“逐级遍历”时定义的,这实际上是可以无限扩展的。
这个路由器也是模块化的 - 这意味着一组路由可以自包含,并且可以重用,这进一步帮助在大数量路由的应用程序中提高可扩展性,因为未在解析路由时访问的模块根本不会被加载或初始化。
代码库非常小,非常简单,非常灵活 - 您可以用这个库做善事或坏事。
要了解如何充分利用它,请阅读下面的文档。
定义路由
这部分很有趣!
您通过使用数组语法来配置回调函数来定义模式,这些回调函数可以定义嵌套子模式等。
一组路由称为模块 - 您可以创建一个实例,并像这样配置它来处理路径 'hello/world'
:
$module = new Module; $module['hello'] = function (Route $route) { $route['world'] = function (Route $route) { $route->get = function() { echo '<h1>Hello, World!</h1>'; }; }; };
路由模式是(PCRE)正则表达式 - 您可以使用子串捕获并结合函数来定义路由参数
$module['archive'] = function(Route $route) { $route['<year:int>-<month:int>'] = function (Route $route) { $route->get = function ($year, $month) { echo "<h1>Archive for $month / $year</h1>"; }; }; };
请注意,表达式 <year:int>-<month:int>
是预处理的,并且内部转换为 PCRE 正则表达式 (?<year>\d+)-(?<month>\d+)
,这不太易读。
模块可以(可选)预处理模式 - 默认模式允许您使用上面显示的简化模式语法,并识别一些符号如 int
和 slug
,这些只是正则表达式模式的命名缩写。您可以根据需要添加或删除预处理函数。
模块
要创建可重用的模块,您可以从模块派生自己的专用类 - 这也为您提供了创建 URL 函数的自然位置
class HelloWorldModule extends Module { public function init() { parent::init(); $this['hello'] = function (Route $route) { $route['world'] = function (Route $route) { $route->get = function() { echo '<h1>Hello, World!</h1>'; }; }; }; } public function hello_url($world = 'world') { return "/hello/$world"; } }
将路由封装在模块中提供了模块化 - 要将控制从模块委托给另一个模块,请调用路由对象的 delegate() 方法
$route['comments'] = function (Route $route) { $route->delegate(new CommentModule()); };
请参阅 "test.php" 脚本以创建和路由到嵌套模块的示例。
请注意,URL 创建不是本库的一部分 - 这在本文档的末尾解释。
评估路由
模块是一组路由的根。
要解析路径并找到由您的模块定义的路由,请这样做
$route = $module->resolve('archive/2012-08');
请注意,如果路由未解析,则此操作将返回 null
。
要执行与路由关联的HTTP方法处理器,请这样做
$result = $route->execute('GET');
返回的$result
是你选择在处理器中返回的内容,这可能是一个控制器或HTML内容,或者什么都没有——如果你更喜欢直接输出内容,并且没有发出返回语句,返回值是布尔值true
或false
,表示成功或失败。
模型/视图/控制器
前一个示例中的execute()
方法在成功时返回true
,除非HTTP方法处理器本身返回其他内容。在上面的示例中,HTTP方法处理器不提供返回值,但你可以不使用框架实现一个简单的MVC风格的控制器/动作抽象。
$module['posts'] = function ($route) { $controller = new PostsController(); $route['<post_id:int>'] = function (Route $route) use ($controller) { $route->get = function ($post_id) use ($controller) { return $controller->showPost($post_id); }; $route['edit'] = function (Route $route) use ($controller) { $route->get = function ($post_id) use ($controller) { return $controller->editPost($post_id); }; $route->post = function ($post_id) use ($controller) { return $controller->updatePost($post_id); }; }; }; }; $result = $module->resolve('posts/42/edit')->execute('get');
容器集成
如果你希望与依赖注入容器集成,可以实现InvokerInterface,并通过Module的构造函数参数注入自己的调用者。
提供了一个调用者以启用通过container-interop与各种DI容器的直接集成。
如果你有一个服务容器或需要从路由内部轻松访问的其他框架/应用程序组件,同时避免在函数层次结构中需要use()
语句,你可以在init()
期间(或任何时刻)将值插入到Route::$vars
中——这个集合存储了解决路由时捕获的值,这些值用于注入路由定义和动作方法的函数参数。
IDE支持
为了获得完整的IDE支持,包括自动完成和静态分析,确保你的代码使用类型提示——例如
$this['hello'] = function (Route $route) { $route->_ // <- auto-completes! };
这还提供了额外的安全性,以防不小心混淆参数/类型。如果你使用DI容器,这同样至关重要;请参见上文。
创建URL
这个库没有提供URL创建(在各种框架中通常称为“命名路由”)的抽象,有以下几个原因。
首先,“命名路由”通常以IDE支持为代价。它还迫使你预先加载和定义所有路由,这可能会效率低下。
但更重要的是,它没有真正的价值——因为创建URL和解析URL的任务真正共有的只有一件事:路由的名称。既然你永远不会为了任何其他目的(除了生成URL)而按名称引用路由本身,因此按名称引用就没有意义。通常,URL的其他部分都是可变的,你最终不得不以无法验证的方式重复参数名称。
对比以下虚构的语法
$url = $module->create('show_archive', array('year' => '2013', 'month' => '04));
与以下真正的美
$url = $module->show_archive_url('2013', '04');
后者是打字量的一半,更容易阅读——IDE可以提供自动完成,如果你必须更改名称或参数,还可以执行检查(静态分析)。
此外,因为这是一个真正的函数,而不是某种抽象,所以你可以使用任何必要的代码来创建URL,在不同的条件下创建不同的URL,使用不同类型的参数(甚至实体,如果需要),等等。
URL创建不受甚至最好的、最复杂的抽象的限制的优点太多,不容忽视——最后,试着将URL创建视为它真正是的东西:一个字符串模板。你正在创建一个字符串。你真的需要框架来做这件事吗?请简单解决简单问题!
创建简单的前端控制器
您可以使用Walkway作为中间件与Conduit一起使用。
要使用Walkway作为裸骨前控制器,创建一个类似于以下内容的"index.php"文件:
// get the path and HTTP request method: $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); $method = $_SERVER['REQUEST_METHOD']; // create your module and resolve the path: $router = new YourAwesomeModule(); $route = $router->resolve($path); // generate a 404 if the path did not resolve: if ($route->$method === null) { header("HTTP/1.0 404 No Route"); } // dispatch the get/head/post/put/delete function: $result = $route->execute($method); // optionally do something clever with $result here...
然后创建一个".htaccess"文件,将传入的请求路由到您的"index.php"。
RewriteEngine on
# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# otherwise forward it to index.php
RewriteRule . index.php
设置完成!
享受吧!
欢迎反馈和pull请求 :-)