bssanchez/phprouter

PHPRouter (http://miladrahimi.github.io/phprouter) 版本 2.3 的分支

dev-master 2019-07-16 18:37 UTC

This package is auto-updated.

Last update: 2024-09-17 06:31:18 UTC


README

免费PHP URL路由器,适用于整洁和强大的项目!

概述

PHPRouter 是一个免费、整洁、强大的独立 URL 路由器,适用于 PHP 项目。

它受到 Laravel 路由系统的启发,适用于无框架项目和全新的框架。

URL 路由

URL 路由是指将 URL 映射到控制器。

对于每个基于 MVC 架构的项目/框架,URL 路由都是一个必要部分。

PHPRouter 作为 URL 路由器提供漂亮的 URL。

以下是一个由弱路由器提供的 URL 示例

http://example.com/index.php?section=blog&page_type=post&id=93

与以下由 PHPRouter 提供的 URL 进行比较

http://example.com/blog/post/93

此外,PHPRouter 还提供了漂亮的 API 和易于使用的函数。

安装

使用 Composer(推荐)

如果您不熟悉 Composer,请阅读 如何在 PHP 项目中使用 composer 文章。

在您的项目根目录中运行以下命令

composer require miladrahimi/phprouter

手动

只要您的自动加载器遵循 PSR-0PSR-4 标准,您就可以使用自己的自动加载器。只需将 src 目录的内容放入您的 vendor 目录。

入门

您所有应用程序请求都必须由一个 PHP 文件(如 index.php)处理。在您的 .htaccess 文件中放置以下指令以实现此目标

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^.*$ index.php [PT,L]

现在您可以在 PHP 文件(此处为 index.php)中使用 Router 类来路由您的应用程序

// Use this namespace
use MiladRahimi\PHPRouter\Router;

// Create brand new Router instance
$router = new Router();

// Map this function to home page
$router->get("/", function () {
    return "This is home page!";
});

// Dispatch all matched routes and run!
$router->dispatch();
  • 在您的服务器根目录中尝试以下示例(不要将其放入子目录中)。
  • 在文档的其余部分,您将了解如何在子目录中使用 PHPRouter。

基本路由

为了方便起见,本文档中的示例中大多数控制器都被定义为闭包。当然,PHPRouter 支持多种控制器类型,您将在下一节中阅读。

以下是一些简单的路由。

use MiladRahimi\PHPRouter\Router;

$router = new Router();

// Map this function to home page (GET method)
$router->get("/", function () {
    return "This is home page!";
});

// Map this function to /blog URI (GET method)
$router->get("/blog", function () {
    return "This is blog!";
});

// Map this function to /submit URI (POST method)
$router->post("/submit", function () {
    return "I'm supposed to catch posted data!";
});

// Dispatch routes and run!
$router->dispatch();
  • 上述所有控制器都是闭包函数。
  • get() 方法将控制器映射到 GET 请求方法。
  • post() 方法将控制器映射到 POST 请求方法。

请求方法

Router 类中的 get()post() 方法都是将控制器映射到 GETPOST 请求的快捷方式。超方法是 map(),它将请求方法作为其第一个参数捕获。

use MiladRahimi\PHPRouter\Router;

$router = new Router();

// Alias: $router->get();
$router->map("GET", "/", function () {
    return "This is Home!";
});

// Alias: $router->post();
$router->map("POST", "/post_data", function () {
    return "Data updated!";
});

// PUT Request method
$router->map("PUT", "/put_data", function () {
    return "Data uploaded!";
});

// DELETE Request method
$router->map("DELETE", "/delete_data", function () {
    return "Data deleted!";
});

// Dispatch routes and run!
$router->dispatch();

多个请求方法

如以下示例所示,控制器可以映射到多个请求方法。

use MiladRahimi\PHPRouter\Router;

$router = new Router();

// Map to GET, POST and DELETE request methods
$router->map(["GET", "POST", "DELETE"], "/", function () {
    return "Homepage for GET, POST and DELETE request methods!";
});

$router->dispatch();
  • PHP >= 5.4 支持 [] 数组 语法。您也可以使用旧的 array() 语法。

任何请求方法

如果控制器可以以任何请求方法响应路由,您可以使用此方法

use MiladRahimi\PHPRouter\Router;

$router = new Router();

// Respond to all request methods (GET, POST, PUT, etc)
$router->any("/", function () {
    return "This is Home! No matter what the request method is!";
});

$router->dispatch();

多个路由

也支持路由数组。如果控制器可以响应多个路由,以下方法可能对您有用。

use MiladRahimi\PHPRouter\Router;

$router = new Router();

$router->map(["GET", "POST"], ["/", "/home"], function () {
    return "Homepage for GET, POST and DELETE requests!";
});

$router->dispatch();
  • 上述控制器将响应以下请求方法和路由
    • 方法:GET 路由:/
    • 方法:GET 路由:/home
    • 方法:POST 路由:/
    • 方法:POST 路由:/home

控制器

我个人不喜欢使用闭包作为控制器。我认为控制器绝对必须是一个方法。然而,以下代码展示了如何使用各种控制器。

use MiladRahimi\PHPRouter\Router;

$router = new Router();

// Closure
$router->get("/1", function () {
    return "Closure as controller";
});

// Stored closure
$closure = function() {
    return "Stored closure as controller";
};
$router->get("/2", $closure);

// Function
function func() {
    return "Function as controller";
}
$router->get("/3", "func");

// Method (Recommended)
class Controller
{
    function method()
    {
        return "Method as controller";
    }
}
$router->get("/4", "Controller@method");

$router->dispatch();

控制器类命名空间

由于MVC模式,开发者通常使用命名空间来声明控制器。

PHPRouter不识别“已使用命名空间”,因此您必须通过类名传递它们。

请看以下示例,Post类使用了Blog命名空间。

use MiladRahimi\PHPRouter\Router;

$router = new Router();

$router->get("/blog/posts", 'Controllers\Blog\Post@getAll');

$router->dispatch();

接下来是Post

<?php namespace Controllers\Blog;

class Post {
    function getAll() {
        return "All posts";
    }
}

路由参数

PHPRouter是为了帮助开发者更轻松地处理动态路由而创建的。让我们回到第一个示例,即这个示例

http://example.com/blog/post/93

文章ID(例如上面的93)是可变的。实际上,我们只需要一个控制器来处理具有以下模式的全部路由

http://example.com/blog/post/{id}

不用担心!因为PHPRouter以最简单的方式处理它。看看以下示例

use MiladRahimi\PHPRouter\Router;

$router = new Router();

$router->get("/blog/post/{id}", function ($id) {
    return "Show content of post " . $id;
});

$router->dispatch();

或者您可以使用一个方法作为控制器

class Blog
{
    function getPost($id)
    {
        return "Show content of post " . $id;
    }
}

use MiladRahimi\PHPRouter\Router;

$router = new Router();

$router->get("/blog/post/{id}", "Blog@getPost");

$router->dispatch();
  • 您可以使用{}字符来指定路由参数。
  • 路由器将提取它们,并将它们的值作为参数传递给控制器。
  • 控制器参数必须与路由参数名称相同。
  • PHPRouter按名称传递参数,而不是按顺序。

可选参数

您可以考虑一个或多个参数为可选。

在这个例子中,id是可选的。如果请求URI有ID,它会返回捕获到的ID的页面。如果请求URI没有ID(例如/page/),它会返回所有页面!

use MiladRahimi\PHPRouter\Router;

$router = new Router();

$router->get("/page/{id?}", function ($id) {
    if($id == null)
        return "All pages!";
    return "Page $id";
});

$router->dispatch();
  • 当没有提供可选参数时,它将是null

在上面的示例中,要查看所有页面,用户必须输入/page/ URI。您也可以考虑使用/page来实现这个目的。为了达到这个目标,您可以使用一组路由,或者只需使用以下技巧

use MiladRahimi\PHPRouter\Router;

$router = new Router();

$router->get("/page/?{id?}", function ($id) {
    if($id == null)
        return "All pages!";
    return "Page $id";
});

$router->dispatch();
  • ?使前面的字符可选。

更定制化的参数

默认情况下,路由参数可以是任何内容([^/]+正则表达式模式)。有时路由参数必须是数字。有时您需要它们遵循复杂的模式。没问题!您可以定义某些或所有路由参数的正则表达式模式。还有一些预定义的模式您可以使用。

use MiladRahimi\PHPRouter\Router;

$router = new Router();

// 'id' must be a number (number pattern id predefined)
$router->define("id", Router::NUMERIC);

// 'username' must be an alphanumeric, - is allowed too
$router->define("username", "[a-z0-9\-]+");

$router->get("/blog/post/{id}", function ($id) {
    return "Show content of post " . $id;
});

$router->get("/user/{username}", function ($username) {
    return "Show info of user with username: " . $username;
});

$router->dispatch();
  • 不需要检查ID是否是数字,它会是一个数字,我保证!
  • 为了避免不期望的结果,模式组字符(())被禁用。
  • 有三个预定义的模式:NUMERICALPHABETICALPHANUMERIC

请求和响应对象

路由器将两个名为$request$response的对象传递给路由控制器。当然,控制器也必须考虑捕获它们。这些对象在某些情况下很有帮助。要获取这些对象,您必须在控制器参数中给它们命名(一个或两个)。不用担心,PHPRouter足够灵活,不会关心控制器参数的顺序!

请求对象

URL

http://example.com/blog/post/93?section=comments

应用程序路由部分

use MiladRahimi\PHPRouter\Request;
use MiladRahimi\PHPRouter\Router;

$router = new Router();

$router->get("/blog/post/{id}", function ($id, Request $request) {
    echo $id . "<br>";
    echo $request->getUrl() . "<br>";
    echo $request->getUri() . "<br>";
    echo $request->getWebsite() . "<br>";
    echo $request->getPage() . "<br>";
    echo $request->getQueryString() . "<br>";
    echo $request->getBaseUri() . "<br>";
    echo $request->getLocalUri() . "<br>";
    echo $request->getMethod() . "<br>";
    echo $request->getProtocol() . "<br>";
    echo $request->getIP() . "<br>";
    echo $request->getPort() . "<br>";
});

$router->dispatch();

输出

93
example.com/blog/post/93?section=comments
/blog/post/93?section=comments
example.com
/blog/post/93
section=comments

/blog/post/93
GET
HTTP/1.1
127.0.0.1
14889

其他方法

// Return Previous URL (HTTP_REFERRER)
$request->referer();
// Following methods will be discussed more!
$request->get();
$request->get("key");
$request->post();
$request->post("key");
$request->cookie();
$request->cookie("name");

响应对象

响应对象操作应用程序对客户端的HTTP响应。以下是一个示例

use MiladRahimi\PHPRouter\Response;
use MiladRahimi\PHPRouter\Router;

$router = new Router();

$router->get("/fa", function (Response $response) {
    $response->render("test.php");
    $response->publish("Here is the published content!");
});

$router->dispatch();

输出

This is test.php file content!
Here is the published content!

方法

$response->publish($content);       // Publish string, array or object content
$response->cookie($name,$value);    // Set cookie value (like PHP native setcookie() function)
$response->redirect($to);           // Redirect to the new URL ($to)
$response->render($file);           // Render PHP file (like PHP include syntax)
$response->contents();              // Return current output 

访问 $_GET、$_POST 和 $_COOKIE

当然,您可以使用相同的$_GET$_POST$_COOKIE数组。但使用$request方法可能更整洁、更美观。

运行以下示例:http://example.com/user?id=93

use MiladRahimi\PHPRouter\Router;

$router = new Router();

$router->get("/user", function ($request) {
    return $request->get("id"); // will publish "93"
});

$router->dispatch();

GET

$request对象中的get()方法用于访问GET参数。以下示例在$key参数不存在时返回$_GET数组。

$request->get();

但这次,如果存在,它将返回id参数的值

$request->get("id");

POST

$request对象中的post()方法用于访问POST参数。以下示例在$key参数不存在时返回$_POST数组。

$request->post();

但这次,如果存在,它将返回id参数的值

$request->post("id");

COOKIE

$request对象中的cookie()方法用于访问cookies。以下示例在$name参数不存在时返回所有cookies(作为数组)。

$request->cookie();

但这次,如果存在,它将返回cookies中的id

$request->cookie("id");

$response对象中的cookie()方法用于操作或写入cookies。以下示例显示了其工作方式

$response->cookie("language","persian");
  • $response->cookie()参数:$name$value$expire$path$domain$secure$httponly

重定向

您可以使用PHP的header()函数重定向请求。但如果您对使用PHPRouter方法感兴趣,您可以尝试这样做

use MiladRahimi\PHPRouter\Router;

$router = new Router();

$router->get("/old-page", function ($response) {
    $response->redirect("/new-page");
});

$router->dispatch();
  • redirect()方法将考虑Base URI
  • 如果您省略了$to参数,则默认情况下为"/",并将客户端重定向到主页。

渲染PHP文件

有时您想要返回的结果不是纯文本或编译后的PHP代码,而是一个PHP文件。没问题!您可以使用$response对象中的render()方法,如下所示

use MiladRahimi\PHPRouter\Router;

$router = new Router();

$router->get("/", function ($response) {
    $response->render("test.php");
});

$router->dispatch();
  • 建议使用返回HTML内容的模板引擎

中间件

中间件是一个函数或任何可调用的对象,它在您的控制器之前运行。它可以控制访问,如身份验证或您想要的任何内容。您可以为任何路由考虑一个或多个中间件。实际上,$middleware参数可以是可调用的、方法名称或它们的数组。

use MiladRahimi\PHPRouter\Router;

$router = new Router();

// Middleware
function authenticate($id, $response)
{
    if(/* User with this id is not allowed to access */)
        $response->redirect("/login");
}

// Controller
function profile($id)
{
    return "The info of user with this ID:" . $id;
}

$router->get("/user/{id}", "profile", "authenticate");

$router->dispatch();
  • 路由器传递给控制器的所有参数也将传递给中间件。
  • 在这个层面上,中间件似乎并不非常有用。阅读文档的其余部分,您会发现它非常有用!

分组

这是最激动人心的部分!您可以分组您的路由,并为他们设置常见的选项。在这个版本中,PHPRouter支持常见的middlewaredomainsubdomainprefixpostfix

例如,所有用户控制面板页面都需要身份验证。因此,您可以分组所有这些页面。

use MiladRahimi\PHPRouter\Router;

$router = new Router();

// Middleware
function authenticate($id, $response)
{
    if(/* User with this id is not allowed to access */)
        $response->redirect("/login");
}

$router->group("authenticate", function ($router) {

    $router->get("/user/{id}/profile", function ($id) {
        return "User profile page!";
    });

    $router->get("/user/{id}/setting", function ($id) {
        return "User setting page!";
    });

    $router->get("/user/{id}/messages", function ($id) {
        return "User messages page!";
    });

});

$router->dispatch();
  • group()方法的第一个参数是常见的选项。您可以将所有公共选项作为一个数组传递。
  • 如果您传递一个可调用的函数或函数或方法的名称,它将被视为中间件。
  • group()方法的第二个参数是声明组体(路由)的函数或方法。
  • 主体必须以某种方式访问$router对象,如果您考虑了$router参数,它就会这样做。
  • 您可以使用PHP的use语句代替在组体中放置$router参数。

您可以在中间件之外使用prefix选项。

use MiladRahimi\PHPRouter\Router;
$router = new Router();

// Middleware
function authenticate($id, $response)
{
    if(/* User with this id is not allowed to access */)
        $response->redirect("/login");
}

$options = ["middleware" => "authenticate", "prefix" => "/user"]; // This line!

$router->group($options, function (Router $router) { // And this line too!

    $router->get("/{id}/profile", function ($id) {
        return "User $id profile page!";
    });

    $router->get("/{id}/setting", function ($id) {
        return "User $id setting page!";
    });

    $router->get("/{id}/messages", function ($id) {
        return "User $id messages page!";
    });

});

$router->dispatch();
  • middleware元素可以是可调用的函数、函数或方法的名称,或它们的数组。
  • prefixpostfix元素是字符串,不允许是它们的数组。
  • 您可以将$router数据类型(Router)放入组体参数中以帮助您的IDE。
  • 如果您以这种方式将路由器对象注入到组体中,该对象在主体中的名称始终为$router

您可以使用PHP的use语法来访问路由器对象。这特别有用,特别是当路由器对象的名称不是$router时!

use MiladRahimi\PHPRouter\Router;

$my_router = new Router();

// Middleware
function authenticate($id, $response)
{
    if (is_permitted_user($id) == false) {
        $response->redirect("/forbidden");
    }
}

$options = ["middleware" => "authenticate", "prefix" => "/user"];

$my_router->group($options, function () use ($my_router) { // This line!

    $my_router->get("/{id}/profile", function ($id) {
        return "User $id profile page!";
    });

    // ...

});

$router->dispatch();
  • 当您使用use语法时,您可以在组体中保持路由器对象的名称。

域名

你可以在一个主机上拥有多个不同的网站!group()方法的第一个参数中的domain元素将帮助你。查看以下示例。

use MiladRahimi\PHPRouter\Router;

$router = new Router();

$router->group(["domain" => "domain1.com"], function () use ($router) {
    $router->get("/", function () {
        return "Homepage of domain1.com";
    });
});

$router->group(["domain" => "domain1.com"], function () use ($router) {
    $router->get("/", function () {
        return "Homepage of domain2.com";
    });
});

$router->dispatch();
  • 除了域名选项外,你还可以使用其他常见选项,如中间件。

子域名

使用PHPRouter管理子域名非常简单。在group()方法的第一个参数中的domain元素将帮助你。如上所述,该元素用于管理域名,但这次我们处理的是子域名。

静态子域名

以下示例展示了如何处理blogforum子域名。

use MiladRahimi\PHPRouter\Router;

$router = new Router();

$router->group(["domain" => "blog.example.com"], function () use ($router) {
    $router->get("/", function () {
        return "Blog home page!";
    });
});

$router->group(["domain" => "forum.example.com"], function () use ($router) {
    $router->get("/", function () {
        return "Forum home page!";
    });
});

$router->dispatch();

动态子域名

你可以像捕获路由参数一样捕获子域名。查看以下示例,这是一个非常不错的功能。

use MiladRahimi\PHPRouter\Router;

$router = new Router();

$router->group(["domain" => "{subdomain}.example.com"], function () use ($router) {
    $router->get("/", function ($subdomain) {
        return "This is " . $subdomain;
    });
});

$router->dispatch();
  • 你可以在域名中设置参数,并在控制器参数中捕获它们。
  • 如果路由参数和子域名的名称相同,则路由参数会覆盖子域名。
  • 你可以在域名选项旁边使用其他常见选项,如中间件。
  • 与路由参数一样,中间件可以访问子域名。

基本URI和子目录中的项目

你可以使用上述工具处理子目录中项目的路由。但有一个工具可以使这项工作更容易。例如,你正在处理一个博客,所有的文件都在blog这样的目录中。你可以设置它的基本URI来更容易地路由你的博客。在某些情况下很有用,比如重定向,$response对象中的redirect()在目标中添加基本URI。

use MiladRahimi\PHPRouter\Router;

// You can determine the base URI with Router constructor
$router = new Router("/blog");

$router->get("/post/{id}", function ($id) {
    return "Show post " . $id;
});

$router->dispatch();
  • 你只能在构造函数中设置基本URI,以保持整个应用程序中的唯一性。
  • 有一个名为getBaseURI()的方法,但没有setBaseURI方法!

错误处理

为了更整洁,当你调用dispatch()方法时,所有的错误(异常)都会被抛出。所以你必须用try-catch包装这个方法。

use MiladRahimi\PHPRouter\Router;
use MiladRahimi\PHPRouter\HttpError;
use MiladRahimi\PHPRouter\PHPRouterError;

$router = new Router();

$router->get("/", function() {
    return "Homepage!";
});

try {
    $router->dispatch();
} catch(HttpError $e) {
    if($e->getMessage() == "404")
        $router->publish("Error 404! Not found!");
    //...

} catch(Exception $e) {
    // Log details...
    $router->publish("Sorry, there is an internal error, we will fix it asap!");
}
  • 你可以使用publish()方法或PHP的echo()或任何你喜欢的方式来向用户发布详细信息。

HttpError异常

当发生如错误404未找到这样的HTTP错误时,将抛出此异常。异常消息包含其代码,如404

你的应用程序异常

PHPRouter不会操作你的应用程序异常,所以你可以像捕获HttpError异常一样捕获它们。

模板引擎

当你为应用程序使用路由器时,你很快会发现你需要一个模板引擎。模板引擎通常会返回HTML输出,所以你可以在控制器中返回它。我也创建了一个模板引擎包,你可以在这里下载它:[PHPTemplate](https://github.com/miladrahimi/phptemplate)。

许可证

PHPRouter由Milad Rahimi创建,并使用MIT许可证发布。