ddn/router

Web应用的路由

dev-main 2023-07-10 07:28 UTC

This package is auto-updated.

Last update: 2024-09-10 09:53:32 UTC


README

一个用于简化PHP Web应用开发的库。

安装

待定

composer require ddn/router dev-main

文档

基本Web应用

基本思想是提供处理类似以下URL的工具

https://my.server/app/path/to/op?token=12345

服务器将URL https://my.server/app 指向 app 目录。在该目录中,我们需要一个名为 index.php 的文件,这是应用的入口点。在目录中我们还需要一个文件 .htaccess,用于配置对应用的访问。

.htaccess 的示例内容

RewriteEngine On
RewriteBase /
RewriteRule ^/index\.php$ - [L]
RewriteRule ^/favicon\.ico$ - [L]

# If it is a php file, but it is not index.php, pass it as _OPERATION parameter
RewriteCond %{REQUEST_URI} ^(.*)\.php$
RewriteCond %{REQUEST_FILENAME} !index.php$
RewriteRule ^(.*)$ index.php?_OPERATION=$1 [L,QSA]

# If it is other file that exists, serve it
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^(.*)$ - [L,QSA]

# Finally, if the file does not exist nor is a folder, pass it as _OPERATION parameter
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?_OPERATION=$1 [L,QSA]

.htaccess 片段配置路由器使用 index.php 文件作为入口点。任何不是 index.php 的文件都将作为 _OPERATION GET 参数传递给 index.php 文件。

现在我们需要一个名为 index.php 的文件

use ddn\api\Router;
use ddn\api\Renderer;
use ddn\api\Router\Op;

require_once('vendor/autoload.php');

// First create the router for our application; it is a simple router thet we must manage
$router = new ddn\api\Router("_OPERATION");

if (defined('__CUSTOM_ROUTES') && (is_callable(__CUSTOM_ROUTES)))
    call_user_func_array(__CUSTOM_ROUTES, [ $router ]);

class SayHello extends ddn\api\Router\Op {
    function _default_op() {
        echo "executed the default op";
    }
}

ddn\api\Renderer::set_default("template/renderer.pug");
$router->add("/", 'SayHello', 'template/example.pug');
$ops = $router->exec();
if ($ops === false) {
    header("HTTP/1.0 404 Not Found");
    echo "Not found";
    exit;
}

$ops->render();

Web应用模型

一个Web应用包括

  • 一组 端点,这些端点定义了将要服务的Web应用中的路径。
  • 一组操作(即从 Op 类派生出的类)实现了端点上的动作(例如创建或删除对象、登录用户等)。
  • 一组 视图,用于显示操作的执行结果。
  • 一组 渲染器,用于显示视图。

渲染器视图 分离,可以轻松创建统一的Web应用。可以将 渲染器 理解为Web应用的 布局,而 视图 则是Web操作的严格输出。

如果不需要这种区分,可以将每个 视图 作为整体输出渲染,并忽略 渲染器(即定义渲染器为 ($view) => { echo $view; })。

最后

  • 一个 视图 是一个HTML片段,用于显示操作的输出。
  • 一个 渲染器 是一个HTML布局,其中放置了视图。

工作流程

当一个请求到达Web服务器时

  1. 路由器评估是否定义了路由
  2. 如果没有匹配URI的路由,则结束。
  3. 从URI中检索参数并实例化实现操作的 Op 类。
  4. 调用 Op_do 函数
  5. _do 将检查是否定义了子操作;如果没有,它将调用 _do_default_operation。如果有子操作,它将被执行。
  6. 为操作生成视图
    • pug 文件:可用变量 _OP_HANDLER
    • 其他文件或函数:全局变量 $_OP_HANDLER 可用
  7. 渲染最终布局
    • pug 文件:变量 _OP_HANDLER_VIEW 可用。
    • 其他文件或函数:全局变量 $_OP_HANDLER$_VIEW 可用

(*) 除非调用 exec() 的结果是渲染的(即调用方法 render()),否则不会生成视图。

路由器

路由器是Web应用的主要类。

要创建Web应用,需要创建一个 路由器 并设置默认渲染器

// _OPERATION is the GET parameter in which the operations are included. If using the suggested setup, apache (or nginx) will set this parameter to the accessed path; so if requesting URI https://my.server/path/to/my/op?v=1, '_OPERATION' will be set to path/to/my/op, and 'v' will still be 1.
$router = new ddn\api\Router("_OPERATION");

// This is the default renderer, which contains the default layout for out application (it can be either a function, a php file, an html file or a pug file, which will be interpreted)
ddn\api\Renderer::set_default("template/renderer.pug");

然后我们需要定义我们的路由

// In path /hello we will have an operation, which will be served by class 'SayHello' and, after executing _do method of that class, we will show the view 'template/example.pug'. The renderer is the default (so we set it to null).
$router->add("/hello", 'SayHello', 'template/example.pug', null);

一旦定义,我们需要根据URI执行操作并渲染输出

$ops = $router->exec();
if ($ops === false) {
    header("HTTP/1.0 404 Not Found");
    echo "Not found";
    exit;
}

$ops->render();

方法

  • __construct($path_varname):构建对象
    • 变量名 $path_varname:是从“_GET”数组中获取路径的变量名
  • add($url, $classname, $template):向路由器添加路由
    • $url:是匹配路由的路径定义。可以使用参数来定义路由。

      /path/to/<parameter>/<?可选参数=默认值>/end

    • $classname:是实现操作的类(它是 Op 子类,或必须实现 _do 函数)。如果为 null,则不会执行任何操作。
    • $template:是显示操作输出的模板。
  • exec():检查是否有 URI 终端被访问,如果是,则执行操作并返回一个 Renderer 对象以便渲染输出。

Op

Op 类用于在服务器的终端实现操作。例如,如果我们定义以下路由: $router->add("/", 'SayHello', 'template/example.pug');,如果访问的终端匹配 /,则会创建一个 SayHello 类的实例。然后调用其类的 _do 方法。

操作的执行可能因用户设置的动作值而异。这就是为什么 Op 类还包括根据 $_GET$_POST 超变量的值执行不同函数的辅助工具。

这样,同一个类就可以用于多个函数。例如,可以有一个实现 loginlogoutupdate_dataOpUsers 类,这取决于 $_POST 变量是否设置及其值。

以下是一个类的示例:

class OpUser extends ddn\api\Router\Op {
    const _FUNCTIONS = [
        "login" => "_login",
        "logout" => "_logout",
        "update" => [
            "" => "_update_data",
            "password" => "_update_password",
        ]
    ];

    function _login() {
        echo "executed the login op";
    }
    function _logout() {
        echo "executed the logout op";
    }
    function _update_data() {
        $_POST["name"] = $_POST["name"]??null;
        $_POST["email"] = $_POST["email"]??null;
        echo "would set:<br>\n- user to {$_POST['name']}<br>\n- email to {$_POST['email']}<br>";
        echo "executed the update data op<br>";
    }
    function _update_password() {
        echo "executed the update password op";
    }
    function _default_op() {
        echo "executed the default op";
    }
}

_FUNCTIONS 的读取

  • 如果使用 _POST["login"] 执行此类,则执行函数 _login
  • 如果使用 _POST["logout"] 执行此类,则执行函数 _login
  • 如果使用 _POST["update"] 设置为 password,则执行函数 _update_password
  • 如果使用 _POST["update"] 设置为除 password 之外的其他内容,则执行函数 _update_password
  • 否则,执行 _default_op

权限

可以定义常量数组 _PERMS,它定义了执行此操作的所需权限。

如果已定义,则检查权限,如果不符合要求,则执行此操作的 forbidden 函数。

权限依赖于类 ACLManager 的使用以及根据使用操作的用户的组成员资格定义的 ACL 条目。

权限还依赖于操作(类或对象)的实现中的多个函数

  • get_app_user:返回使用操作的用户。用户必须实现 ACLUser 接口
  • is_owner:返回用户是否是处理操作的对象的所有者
  • forbidden:通知用户操作访问被禁止

可以定义权限,例如:“如果用户是所有者则授予访问权限”、“如果用户已登录则授予访问权限”、“如果用户属于管理员组则授予访问权限”等。

(*) 权限的定义是一个丰富的自动化功能,但描述起来很复杂。因此,目前最好检查代码而不是解释。