miladrahimi / phprouter
一个功能强大、轻量级且非常快速的 PHP 项目 HTTP URL 路由器。
Requires
- php: >=7.1
- ext-json: *
- ext-mbstring: *
- laminas/laminas-diactoros: ^2.2
- miladrahimi/phpcontainer: ^5.3.1
Requires (Dev)
- phpunit/phpunit: ^7|^8|^9
README
PhpRouter
PhpRouter 是一个功能全面且非常快速的 HTTP URL 路由器,适用于 PHP 项目。
一些提供的功能
- 路由参数
- 预定义的路由参数模式
- 中间件
- 闭包和类控制器/中间件
- 路由分组(按前缀、中间件和域名)
- 路由命名(以及通过名称生成路由)
- PSR-7 请求和响应
- 视图(简单的 PHP/HTML 视图)
- 多个(子)域名(使用正则表达式模式)
- 自定义 HTTP 方法
- 与 IoC 容器集成(PhpContainer)
- Request、Route、Url 等的自动注入方法和构造函数
当前版本需要 PHP v7.4
或更高版本,包括 v8.*
。
内容
版本
- v5.x.x(当前,受支持)
- v4.x.x
- v3.x.x
- v2.x.x
- v1.x.x
文档
安装
安装 Composer 并在项目根目录中运行以下命令
composer require miladrahimi/phprouter "5.*"
配置
首先,您需要配置您的 Web 服务器以使用单个 PHP 文件(如 index.php
文件)处理所有 HTTP 请求。以下为 NGINX 和 Apache HTTP 服务器配置示例。
-
NGINX 配置示例
location / { try_files $uri $uri/ /index.php?$query_string; }
-
Apache
.htaccess
配置示例<IfModule mod_rewrite.c> <IfModule mod_negotiation.c> Options -MultiViews </IfModule> RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)/$ /$1 [L,R=301] RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [L] </IfModule>
入门
与 PhpRouter 一起工作非常简单!只需看一下以下示例。
-
JSON API 示例
use MiladRahimi\PhpRouter\Router; use Laminas\Diactoros\Response\JsonResponse; $router = Router::create(); $router->get('/', function () { return new JsonResponse(['message' => 'ok']); }); $router->dispatch();
-
视图示例
use MiladRahimi\PhpRouter\Router; use MiladRahimi\PhpRouter\View\View $router = Router::create(); $router->setupView('/../views'); $router->get('/', function (View $view) { return $view->make('profile', ['user' => 'Jack']); }); $router->dispatch();
HTTP 方法
以下示例说明如何为不同的 HTTP 方法声明不同的路由。
use MiladRahimi\PhpRouter\Router; $router = Router::create(); $router->get('/', function () { /* ... */ }); $router->post('/', function () { /* ... */ }); $router->put('/', function () { /* ... */ }); $router->patch('/', function () { /* ... */ }); $router->delete('/', function () { /* ... */ }); $router->dispatch();
您可以使用 define()
方法为其他 HTTP 方法,如下例所示
use MiladRahimi\PhpRouter\Router; $router = Router::create(); $router->define('GET', '/', function () { /* ... */ }); $router->define('OPTIONS', '/', function () { /* ... */ }); $router->define('CUSTOM', '/', function () { /* ... */ }); $router->dispatch();
如果您不关心 HTTP 动词,可以使用 any()
方法。
use MiladRahimi\PhpRouter\Router; $router = Router::create(); $router->any('/', function () { return 'This is Home! No matter what the HTTP method is!'; }); $router->dispatch();
控制器
闭包控制器
use MiladRahimi\PhpRouter\Router; $router = Router::create(); $router->get('/', function () { return 'This is a closure controller!'; }); $router->dispatch();
类方法控制器
use MiladRahimi\PhpRouter\Router; class UsersController { function index() { return 'Class: UsersController & Method: index'; } function handle() { return 'Class UsersController.'; } } $router = Router::create(); // Controller: Class=UsersController Method=index() $router->get('/method', [UsersController::class, 'index']); // Controller: Class=UsersController Method=handle() $router->get('/class', UsersController::class); $router->dispatch();
路由参数
一个 URL 可能有一个或多个变量部分,例如购物网站上产品 ID。我们称之为路由参数。您可以通过控制器方法参数捕获它们,如下例所示。
use MiladRahimi\PhpRouter\Router; $router = Router::create(); // Required parameter $router->get('/post/{id}', function ($id) { return "The content of post $id"; }); // Optional parameter $router->get('/welcome/{name?}', function ($name = null) { return 'Welcome ' . ($name ?: 'Dear User'); }); // Optional parameter, Optional / (Slash)! $router->get('/profile/?{user?}', function ($user = null) { return ($user ?: 'Your') . ' profile'; }); // Optional parameter with default value $router->get('/roles/{role?}', function ($role = 'guest') { return "Your role is $role"; }); // Multiple parameters $router->get('/post/{pid}/comment/{cid}', function ($pid, $cid) { return "The comment $cid of the post $pid"; }); $router->dispatch();
路由参数模式
默认情况下,路由参数可以具有任何值,但您可以定义正则表达式模式来限制它们。
use MiladRahimi\PhpRouter\Router; $router = Router::create(); // "id" must be numeric $router->pattern('id', '[0-9]+'); $router->get('/post/{id}', function (int $id) { /* ... */ }); $router->dispatch();
请求和响应
PhpRouter 使用 laminas-diactoros(以前称为 zend-diactoros)包(v2)向您的控制器和中间件提供 PSR-7 请求和响应对象。
请求
您可以在控制器中捕获请求对象,如下例所示
use MiladRahimi\PhpRouter\Router; use Laminas\Diactoros\ServerRequest; use Laminas\Diactoros\Response\JsonResponse; $router = Router::create(); $router->get('/', function (ServerRequest $request) { $method = $request->getMethod(); $uriPath = $request->getUri()->getPath(); $headers = $request->getHeaders(); $queryParameters = $request->getQueryParams(); $bodyContents = $request->getBody()->getContents(); // ... }); $router->dispatch();
响应
以下示例说明了内置的响应。
use Laminas\Diactoros\Response\RedirectResponse; use MiladRahimi\PhpRouter\Router; use Laminas\Diactoros\Response\EmptyResponse; use Laminas\Diactoros\Response\HtmlResponse; use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\Response\TextResponse; $router = Router::create(); $router->get('/html/1', function () { return '<html>This is an HTML response</html>'; }); $router->get('/html/2', function () { return new HtmlResponse('<html>This is also an HTML response</html>', 200); }); $router->get('/json', function () { return new JsonResponse(['error' => 'Unauthorized!'], 401); }); $router->get('/text', function () { return new TextResponse('This is a plain text...'); }); $router->get('/empty', function () { return new EmptyResponse(204); }); $router->get('/redirect', function () { return new RedirectResponse('https://miladrahimi.com'); }); $router->dispatch();
视图
您可能需要创建一个使用视图的经典样式服务器端渲染(SSR)网站。PhpRouter提供了一个简单功能来处理PHP/HTML视图。请看以下示例。
use MiladRahimi\PhpRouter\Router; use MiladRahimi\PhpRouter\View\View $router = Router::create(); // Setup view feature and set the directory of view files $router->setupView(__DIR__ . '/../views'); $router->get('/profile', function (View $view) { // It looks for a view with path: __DIR__/../views/profile.phtml return $view->make('profile', ['user' => 'Jack']); }); $router->get('/blog/post', function (View $view) { // It looks for a view with path: __DIR__/../views/blog/post.phtml return $view->make('blog.post', ['post' => $post]); }); $router->dispatch();
还有一些要点
- 视图文件必须具有".phtml"扩展名(例如
profile.phtml
)。 - 您可以使用点
.
来分隔目录(例如blog.post
对应于blog/post.phtml
)。
视图文件是纯PHP或与HTML混合。您应该在视图文件中使用模板风格的PHP语言。这是一个示例视图文件
<h1><?php echo $title ?></h1> <ul> <?php foreach ($posts as $post): ?> <li><?php echo $post['content'] ?></li> <?php endforeach ?> </ul>
路由分组
您可以将路由分组。组可以具有一些公共属性,如中间件、域名或前缀。以下示例展示了如何分组路由
use MiladRahimi\PhpRouter\Router; $router = Router::create(); // A group with uri prefix $router->group(['prefix' => '/admin'], function (Router $router) { // URI: /admin/setting $router->get('/setting', function () { return 'Setting Panel'; }); }); // All of group attributes together! $attributes = [ 'prefix' => '/admin', 'domain' => 'shop.example.com', 'middleware' => [AuthMiddleware::class], ]; $router->group($attributes, function (Router $router) { // URL: http://shop.example.com/admin/users // Domain: shop.example.com // Middleware: AuthMiddleware $router->get('/users', [UsersController::class, 'index']); }); $router->dispatch();
组属性将在本文档的后续部分进行解释。
您还可以使用Attributes枚举。
中间件
PhpRouter支持中间件。您可以使用它进行不同的目的,如身份验证、授权、节流等。中间件在控制器前后运行,可以检查和操作请求和响应。
在此,您可以看到考虑了一些中间件的请求生命周期
[Request] ↦ Router ↦ Middleware 1 ↦ ... ↦ Middleware N ↦ Controller
↧
[Response] ↤ Router ↤ Middleware 1 ↤ ... ↤ Middleware N ↤ [Response]
要声明中间件,您可以使用闭包和类,就像控制器一样。要使用中间件,您必须分组路由并在组属性中提及中间件。注意!组中的中间件属性接受一个中间件数组,而不是单个中间件。
use MiladRahimi\PhpRouter\Router; use Psr\Http\Message\ServerRequestInterface; use Laminas\Diactoros\Response\JsonResponse; class AuthMiddleware { public function handle(ServerRequestInterface $request, Closure $next) { if ($request->getHeader('Authorization')) { // Call the next middleware/controller return $next($request); } return new JsonResponse(['error' => 'Unauthorized!'], 401); } } $router = Router::create(); // The middleware attribute takes an array of middleware, not a single one! $router->group(['middleware' => [AuthMiddleware::class]], function(Router $router) { $router->get('/admin', function () { return 'Admin API'; }); }); $router->dispatch();
如您所见,中间件捕获了请求和$next
闭包。闭包调用下一个中间件或控制器(如果没有中间件),中间件必须返回一个响应。中间件可以中断生命周期并返回一个响应,或者它可以调用$next
闭包以继续生命周期。
域名和子域名
您的应用程序可能在不同的域名或子域名上提供不同的服务。在这种情况下,您可以指定路由的域名或子域名。请看以下示例
use MiladRahimi\PhpRouter\Router; $router = Router::create(); // Domain $router->group(['domain' => 'shop.com'], function(Router $router) { $router->get('/', function () { return 'This is shop.com'; }); }); // Subdomain $router->group(['domain' => 'admin.shop.com'], function(Router $router) { $router->get('/', function () { return 'This is admin.shop.com'; }); }); // Subdomain with regex pattern $router->group(['domain' => '(.*).example.com'], function(Router $router) { $router->get('/', function () { return 'This is a subdomain'; }); }); $router->dispatch();
路由名称
您可以为您的路由分配名称并在代码中使用它们,而不是使用硬编码的URL。请看以下示例
use MiladRahimi\PhpRouter\Router; use Laminas\Diactoros\Response\JsonResponse; use MiladRahimi\PhpRouter\Url; $router = Router::create(); $router->get('/about', [AboutController::class, 'show'], 'about'); $router->get('/post/{id}', [PostController::class, 'show'], 'post'); $router->get('/links', function (Url $url) { return new JsonResponse([ 'about' => $url->make('about'), /* Result: /about */ 'post1' => $url->make('post', ['id' => 1]), /* Result: /post/1 */ 'post2' => $url->make('post', ['id' => 2]) /* Result: /post/2 */ ]); }); $router->dispatch();
当前路由
您可能在控制器或中间件中需要获取有关当前路由的信息。此示例展示了如何获取这些信息。
use MiladRahimi\PhpRouter\Router; use Laminas\Diactoros\Response\JsonResponse; use MiladRahimi\PhpRouter\Routing\Route; $router = Router::create(); $router->get('/{id}', function (Route $route) { return new JsonResponse([ 'uri' => $route->getUri(), /* Result: "/1" */ 'name' => $route->getName(), /* Result: "sample" */ 'path' => $route->getPath(), /* Result: "/{id}" */ 'method' => $route->getMethod(), /* Result: "GET" */ 'domain' => $route->getDomain(), /* Result: null */ 'parameters' => $route->getParameters(), /* Result: {"id": "1"} */ 'middleware' => $route->getMiddleware(), /* Result: [] */ 'controller' => $route->getController(), /* Result: {} */ ]); }, 'sample'); $router->dispatch();
IoC容器
PhpRouter使用PhpContainer为包本身和应用程序的依赖项提供IoC容器。
PhpRouter如何使用容器?
PhpRouter绑定路由参数、HTTP请求、路由(当前路由)、URL(URL生成器)、容器本身。控制器方法或构造函数可以解析这些依赖项并捕获它们。
您的应用程序如何使用容器?
只需查看以下示例。
use MiladRahimi\PhpContainer\Container; use MiladRahimi\PhpRouter\Router; $router = Router::create(); $router->getContainer()->singleton(Database::class, MySQL::class); $router->getContainer()->singleton(Config::class, JsonConfig::class); // Resolve directly $router->get('/', function (Database $database, Config $config) { // Use MySQL and JsonConfig... }); // Resolve container $router->get('/', function (Container $container) { $database = $container->get(Database::class); $config = $container->get(Config::class); }); $router->dispatch();
有关此强大IoC容器的更多信息,请参阅PhpContainer。
错误处理
应用程序通过Router::dispatch()
方法运行。您应该将其放在try
块中并捕获异常。它抛出应用程序和PhpRouter异常。
use MiladRahimi\PhpRouter\Router; use MiladRahimi\PhpRouter\Exceptions\RouteNotFoundException; use Laminas\Diactoros\Response\HtmlResponse; $router = Router::create(); $router->get('/', function () { return 'Home.'; }); try { $router->dispatch(); } catch (RouteNotFoundException $e) { // It's 404! $router->getPublisher()->publish(new HtmlResponse('Not found.', 404)); } catch (Throwable $e) { // Log and report... $router->getPublisher()->publish(new HtmlResponse('Internal error.', 500)); }
PhpRouter抛出以下异常
RouteNotFoundException
如果PhpRouter找不到任何与用户请求匹配的路由。InvalidCallableException
如果PhpRouter无法调用控制器或中间件。
RouteNotFoundException
应视为404 Not found
错误。
许可
PhpRouter最初由Milad Rahimi创建,并按照MIT许可证发布。