支持多个和可选参数

为了进一步增强 PHP 框架,使其支持 多个参数可选参数,我们需要对现有的路由系统进行一些改进。

目录

  1. 理解多个参数和可选参数

  2. 更新路由定义语法

  3. 修改 Router 类以支持多个参数和可选参数

  4. 在控制器中处理多个参数和可选参数

  5. 示例:添加带有多个参数和可选参数的路由

  6. 完整代码示例

  7. 测试新功能

  8. 总结


理解多个参数和可选参数

多个参数

多个参数 允许路由捕获 URL 中的多个动态部分。例如:

  • /user/{id}/profile:捕获用户 ID。

  • /post/{post_id}/comment/{comment_id}:分别捕获帖子 ID 和评论 ID。

可选参数

可选参数 允许某些路由部分在 URL 中是可选的。例如:

  • /search/{query}/{page?}page 参数是可选的,用户可以访问 /search/keyword/search/keyword/2

注意:在路由定义中,通常使用 {param?} 语法表示可选参数。


更新路由定义语法

为了支持多个参数和可选参数,我们需要更新路由定义的语法。例如:

$router->add('GET', '/user/{id}/profile', 'UsersController@profile');
$router->add('GET', '/post/{post_id}/comment/{comment_id}', 'PostsController@comment');
$router->add('GET', '/search/{query}/{page?}', 'SearchController@results');
  • {id}{post_id} 是必需参数。

  • {comment_id}{page?} 是可选参数。


修改 Router 类以支持多个参数和可选参数

Router.php

我们需要修改 Router.php 类,以便它能够:

  1. 识别多个参数:在 URL 中捕获多个动态部分。

  2. 识别可选参数:允许某些参数在 URL 中是可选的。

  3. 提取参数并传递给控制器方法

以下是更新后的 Router.php 文件:

<?php
// src/Router.php

namespace MyFramework;

class Router
{
    private $routes = [];

    /**
     * 添加路由规则
     *
     * @param string $method HTTP 方法(GET, POST, etc.)
     * @param string $uri 请求的 URI,支持参数,例如 '/user/{id}/profile'
     * @param string $action 控制器和方法,例如 'UsersController@showProfile'
     */
    public function add($method, $uri, $action)
    {
        // 转换 URI 模式为正则表达式,并提取参数名称
        $pattern = preg_replace_callback('/\{([a-zA-Z0-9_]+)(\?)?\}/', function ($matches) {
            $param = $matches[1];
            $optional = isset($matches[2]) && $matches[2] === '?';
            if ($optional) {
                return '(?P<' . $param . '>[a-zA-Z0-9_-]+)?';
            } else {
                return '(?P<' . $param . '>[a-zA-Z0-9_-]+)';
            }
        }, $uri);

        // 支持可选参数后的斜杠
        $pattern = preg_replace('#//+#', '/', $pattern);
        $pattern = '#^' . $pattern . '(/)?$#';

        // 提取参数名称
        $params = $this->extractParams($uri);

        $this->routes[] = [
            'method'    => strtoupper($method),
            'pattern'   => $pattern,
            'action'    => $action,
            'params'    => $params
        ];
    }

    /**
     * 分发请求到相应的控制器方法
     *
     * @param string $requestMethod HTTP 方法
     * @param string $requestUri 请求的 URI
     */
    public function dispatch($requestMethod, $requestUri)
    {
        foreach ($this->routes as $route) {
            if ($route['method'] === strtoupper($requestMethod)) {
                if (preg_match($route['pattern'], $requestUri, $matches)) {
                    // 提取命名参数
                    $params = [];
                    foreach ($route['params'] as $param) {
                        if (isset($matches[$param]) && $matches[$param] !== '') {
                            $params[$param] = $matches[$param];
                        }
                    }
                    $this->executeAction($route['action'], $params);
                    return;
                }
            }
        }
        // 如果没有匹配的路由,返回 404
        $this->sendNotFound();
    }

    /**
     * 执行控制器的方法并传递参数
     *
     * @param string $action 控制器和方法,例如 'UsersController@showProfile'
     * @param array $params 路由参数
     */
    private function executeAction($action, $params = [])
    {
        if (strpos($action, '@') === false) {
            // 如果动作中没有 '@',则假设是控制器的默认方法
            $controllerName = $action;
            $method = 'index';
        } else {
            list($controllerName, $method) = explode('@', $action);
        }

        $fullControllerName = "MyFramework\\Controllers\\$controllerName";
        if (class_exists($fullControllerName)) {
            $controller = new $fullControllerName();
            if (method_exists($controller, $method)) {
                // 调用方法并传递参数
                call_user_func_array([$controller, $method], $params);
                return;
            }
        }
        // 如果控制器或方法不存在,返回 404
        $this->sendNotFound();
    }

    /**
     * 发送 404 响应
     */
    private function sendNotFound()
    {
        header("HTTP/1.0 404 Not Found");
        echo "404 Not Found";
    }

    /**
     * 提取路由中的参数名称
     *
     * @param string $uri 路由 URI
     * @return array 参数名称列表
     */
    private function extractParams($uri)
    {
        preg_match_all('/\{([a-zA-Z0-9_]+)(\?)?\}/', $uri, $matches);
        return $matches[1];
    }
}
?>

代码解释:

  1. 正则表达式转换

    • 使用 preg_replace_callback 处理 {param}{param?} 语法。

    • {param} 被转换为 (?P<param>[a-zA-Z0-9_-]+),即必需参数。

    • {param?} 被转换为 (?P<param>[a-zA-Z0-9_-]+)?,即可选参数。

  2. 路由模式

    • 使用 #^...$# 界定符确保整个 URI 与模式匹配。

    • 允许可选参数后面有一个斜杠 (/)?,支持 /user/1/profile/

  3. 参数提取

    • extractParams 方法提取路由中的参数名称(如 id, comment_id)。

    • dispatch 方法中,根据正则匹配结果提取参数值并传递给控制器方法。

  4. 执行控制器方法

    • 使用 call_user_func_array 将参数数组展开并传递给控制器方法。

    • 支持控制器方法有默认参数,例如 public function search($query, $page = 1)


在控制器中处理多个参数和可选参数

现在,我们需要在控制器中定义方法来接受多个参数和可选参数。

UsersController.php

更新 UsersController.php,添加新的方法来处理多个参数和可选参数。

<?php
// src/Controllers/UsersController.php

namespace MyFramework\Controllers;

use MyFramework\Controller;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

class UsersController extends Controller
{
    private $logger;

    public function __construct()
    {
        // 初始化日志
        $this->logger = new Logger('users');
        $this->logger->pushHandler(new StreamHandler(__DIR__ . '/../../logs/app.log', Logger::DEBUG));
    }

    /**
     * 用户列表方法
     */
    public function list()
    {
        $this->logger->info("请求用户列表");

        try {
            // 示例:从外部 API 获取用户数据
            // 这里为了简单使用静态数据
            $users = [
                ['id' => 1, 'name' => '张三', 'email' => 'zhangsan@example.com'],
                ['id' => 2, 'name' => '李四', 'email' => 'lisi@example.com'],
                ['id' => 3, 'name' => '王五', 'email' => 'wangwu@example.com'],
            ];

            echo "<h1>用户列表</h1>";
            echo "<ul>";
            foreach ($users as $user) {
                echo "<li>" . htmlspecialchars($user['name']) . " (" . htmlspecialchars($user['email']) . ")</li>";
            }
            echo "</ul>";
        } catch (\Exception $e) {
            $this->logger->error("获取用户列表失败", ['error' => $e->getMessage()]);
            echo "<h1>无法获取用户列表</h1>";
        }
    }

    /**
     * 显示单个用户的方法
     *
     * @param string $id 用户ID
     */
    public function show($id)
    {
        $this->logger->info("请求用户详情", ['id' => $id]);

        try {
            // 示例:根据ID获取用户数据
            // 这里为了简单使用静态数据
            $users = [
                1 => ['id' => 1, 'name' => '张三', 'email' => 'zhangsan@example.com'],
                2 => ['id' => 2, 'name' => '李四', 'email' => 'lisi@example.com'],
                3 => ['id' => 3, 'name' => '王五', 'email' => 'wangwu@example.com'],
            ];

            if (isset($users[$id])) {
                $user = $users[$id];
                echo "<h1>用户详情</h1>";
                echo "<p>ID: " . htmlspecialchars($user['id']) . "</p>";
                echo "<p>姓名: " . htmlspecialchars($user['name']) . "</p>";
                echo "<p>邮箱: " . htmlspecialchars($user['email']) . "</p>";
            } else {
                echo "<h1>用户未找到</h1>";
            }
        } catch (\Exception $e) {
            $this->logger->error("获取用户详情失败", ['error' => $e->getMessage()]);
            echo "<h1>无法获取用户详情</h1>";
        }
    }

    /**
     * 显示用户的某个具体信息(示例:用户的文章)
     *
     * @param string $id 用户ID
     * @param string $info 信息类型(可选)
     */
    public function info($id, $info = null)
    {
        $this->logger->info("请求用户信息", ['id' => $id, 'info' => $info]);

        try {
            // 示例:根据ID和信息类型获取用户数据
            $users = [
                1 => ['id' => 1, 'name' => '张三', 'email' => 'zhangsan@example.com', 'articles' => ['Article 1', 'Article 2']],
                2 => ['id' => 2, 'name' => '李四', 'email' => 'lisi@example.com', 'articles' => ['Article A']],
                3 => ['id' => 3, 'name' => '王五', 'email' => 'wangwu@example.com', 'articles' => []],
            ];

            if (isset($users[$id])) {
                $user = $users[$id];
                echo "<h1>用户信息</h1>";
                echo "<p>ID: " . htmlspecialchars($user['id']) . "</p>";
                echo "<p>姓名: " . htmlspecialchars($user['name']) . "</p>";
                echo "<p>邮箱: " . htmlspecialchars($user['email']) . "</p>";

                if ($info === 'articles') {
                    echo "<h2>用户的文章</h2>";
                    if (!empty($user['articles'])) {
                        echo "<ul>";
                        foreach ($user['articles'] as $article) {
                            echo "<li>" . htmlspecialchars($article) . "</li>";
                        }
                        echo "</ul>";
                    } else {
                        echo "<p>暂无文章。</p>";
                    }
                } else {
                    echo "<p>请选择查看的具体信息。</p>";
                }
            } else {
                echo "<h1>用户未找到</h1>";
            }
        } catch (\Exception $e) {
            $this->logger->error("获取用户信息失败", ['error' => $e->getMessage()]);
            echo "<h1>无法获取用户信息</h1>";
        }
    }
}
?>

代码解释:

  1. show 方法

    • 接受一个必需参数 $id,用于显示单个用户的详情。

  2. info 方法

    • 接受一个必需参数 $id 和一个可选参数 $info

    • 例如,/user/1/info/articles 将传递 id=1info=articles

    • 如果 info 参数未提供,则默认值为 null

注意:控制器方法的参数顺序应与路由中参数的顺序一致。


示例:添加带有多个参数和可选参数的路由

以下是如何在框架中添加支持多个参数和可选参数的路由的具体示例。

1. 定义新路由

index.php 中添加新的路由规则,例如:

<?php
// index.php

// ... 之前的代码 ...

// 定义带有多个参数和可选参数的路由
$router->add('GET', '/user/{id}/info/{info?}', 'UsersController@info');

// 其他路由
$router->add('GET', '/', 'HomeController@index');
$router->add('GET', '/about', 'HomeController@about');
$router->add('GET', '/contact', 'HomeController@contact');
$router->add('POST', '/submit', 'HomeController@submit');
$router->add('GET', '/users', 'UsersController@list');
$router->add('GET', '/user/{id}', 'UsersController@show');

// 处理请求
$router->dispatch($requestMethod, $requestUri);
?>

2. 访问新路由

  • 访问单个参数路由

    • URL: http://localhost:8000/user/1

    • 路由:/user/{id}

    • 控制器方法:UsersController@show

    • 结果:显示用户 ID 为 1 的详情。

  • 访问带有多个参数和可选参数的路由

    • URL: http://localhost:8000/user/1/info/articles

    • 路由:/user/{id}/info/{info?}

    • 控制器方法:UsersController@info

    • 结果:显示用户 ID 为 1 的文章列表。

    • URL: http://localhost:8000/user/2/info

    • 路由:/user/{id}/info/{info?}

    • 控制器方法:UsersController@info

    • 结果:显示用户 ID 为 2 的基础信息(未指定 info 参数)。


完整代码示例

以下是包含多个参数和可选参数支持的完整框架代码。

目录结构

my_framework/
├── composer.json
├── composer.lock
├── vendor/
├── index.php
├── src/
│   ├── Router.php
│   └── Controller.php
├── src/Controllers/
│   ├── HomeController.php
│   └── UsersController.php
└── logs/
    └── app.log

1. composer.json

确保 composer.json 包含必要的依赖和自动加载配置:

{
    "name": "yourname/my_framework",
    "description": "A simple PHP framework example with Composer integration, multiple parameters, and optional parameters",
    "type": "project",
    "require": {
        "monolog/monolog": "^2.0",
        "guzzlehttp/guzzle": "^7.0"
    },
    "autoload": {
        "psr-4": {
            "MyFramework\\": "src/"
        }
    }
}

安装依赖和生成自动加载文件

在项目根目录运行:

composer install

2. index.php

<?php
// index.php

// 启用错误报告(开发阶段使用,生产环境请关闭)
ini_set('display_errors', 1);
error_reporting(E_ALL);

// 自动加载 Composer 及框架类
require __DIR__ . '/vendor/autoload.php';

// 使用命名空间
use MyFramework\Router;

// 获取请求的 URI 和方法
$requestUri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$requestMethod = $_SERVER['REQUEST_METHOD'];

// 实例化路由器并添加路由
$router = new Router();

// 定义路由规则
$router->add('GET', '/', 'HomeController@index');
$router->add('GET', '/about', 'HomeController@about');
$router->add('GET', '/contact', 'HomeController@contact');
$router->add('POST', '/submit', 'HomeController@submit');
$router->add('GET', '/users', 'UsersController@list');
$router->add('GET', '/user/{id}', 'UsersController@show');
$router->add('GET', '/user/{id}/info/{info?}', 'UsersController@info');

// 处理请求
$router->dispatch($requestMethod, $requestUri);
?>

3. src/Router.php

<?php
// src/Router.php

namespace MyFramework;

class Router
{
    private $routes = [];

    /**
     * 添加路由规则
     *
     * @param string $method HTTP 方法(GET, POST, etc.)
     * @param string $uri 请求的 URI,支持参数,例如 '/user/{id}/profile'
     * @param string $action 控制器和方法,例如 'UsersController@showProfile'
     */
    public function add($method, $uri, $action)
    {
        // 转换 URI 模式为正则表达式,并提取参数名称
        $pattern = preg_replace_callback('/\{([a-zA-Z0-9_]+)(\?)?\}/', function ($matches) {
            $param = $matches[1];
            $optional = isset($matches[2]) && $matches[2] === '?';
            if ($optional) {
                return '(?P<' . $param . '>[a-zA-Z0-9_-]+)?';
            } else {
                return '(?P<' . $param . '>[a-zA-Z0-9_-]+)';
            }
        }, $uri);

        // 支持可选参数后的斜杠
        $pattern = preg_replace('#//+#', '/', $pattern);
        $pattern = '#^' . $pattern . '(/)?$#';

        // 提取参数名称
        $params = $this->extractParams($uri);

        $this->routes[] = [
            'method'    => strtoupper($method),
            'pattern'   => $pattern,
            'action'    => $action,
            'params'    => $params
        ];
    }

    /**
     * 分发请求到相应的控制器方法
     *
     * @param string $requestMethod HTTP 方法
     * @param string $requestUri 请求的 URI
     */
    public function dispatch($requestMethod, $requestUri)
    {
        foreach ($this->routes as $route) {
            if ($route['method'] === strtoupper($requestMethod)) {
                if (preg_match($route['pattern'], $requestUri, $matches)) {
                    // 提取命名参数
                    $params = [];
                    foreach ($route['params'] as $param) {
                        if (isset($matches[$param]) && $matches[$param] !== '') {
                            $params[$param] = $matches[$param];
                        }
                    }
                    $this->executeAction($route['action'], $params);
                    return;
                }
            }
        }
        // 如果没有匹配的路由,返回 404
        $this->sendNotFound();
    }

    /**
     * 执行控制器的方法并传递参数
     *
     * @param string $action 控制器和方法,例如 'UsersController@showProfile'
     * @param array $params 路由参数
     */
    private function executeAction($action, $params = [])
    {
        if (strpos($action, '@') === false) {
            // 如果动作中没有 '@',则假设是控制器的默认方法
            $controllerName = $action;
            $method = 'index';
        } else {
            list($controllerName, $method) = explode('@', $action);
        }

        $fullControllerName = "MyFramework\\Controllers\\$controllerName";
        if (class_exists($fullControllerName)) {
            $controller = new $fullControllerName();
            if (method_exists($controller, $method)) {
                // 调用方法并传递参数
                call_user_func_array([$controller, $method], $params);
                return;
            }
        }
        // 如果控制器或方法不存在,返回 404
        $this->sendNotFound();
    }

    /**
     * 发送 404 响应
     */
    private function sendNotFound()
    {
        header("HTTP/1.0 404 Not Found");
        echo "404 Not Found";
    }

    /**
     * 提取路由中的参数名称
     *
     * @param string $uri 路由 URI
     * @return array 参数名称列表
     */
    private function extractParams($uri)
    {
        preg_match_all('/\{([a-zA-Z0-9_]+)(\?)?\}/', $uri, $matches);
        return $matches[1];
    }
}
?>

4. src/Controller.php

保持不变:

<?php
// src/Controller.php

namespace MyFramework;

class Controller
{
    // 在这里可以添加公共的方法或属性
}
?>

5. src/Controllers/HomeController.php

保持不变:

<?php
// src/Controllers/HomeController.php

namespace MyFramework\Controllers;

use MyFramework\Controller;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

class HomeController extends Controller
{
    private $logger;

    public function __construct()
    {
        // 创建一个日志通道
        $this->logger = new Logger('home');
        $this->logger->pushHandler(new StreamHandler(__DIR__ . '/../../logs/app.log', Logger::DEBUG));
    }

    /**
     * 主页方法
     */
    public function index()
    {
        $this->logger->info("访问主页");
        echo "<h1>欢迎来到主页!</h1>";
    }

    /**
     * 关于页面方法
     */
    public function about()
    {
        $this->logger->info("访问关于页面");
        echo "<h1>关于我们</h1><p>这是关于页面。</p>";
    }

    /**
     * 联系我们页面方法
     */
    public function contact()
    {
        $this->logger->info("访问联系我们页面");
        echo "<h1>联系我们</h1><p>这是联系我们页面。</p>";
    }

    /**
     * 处理表单提交的方法
     */
    public function submit()
    {
        // 处理 POST 数据
        $data = $_POST;
        $this->logger->info("表单提交", $data);
        echo "<h1>表单已提交</h1>";
        echo "<pre>";
        print_r($data);
        echo "</pre>";
    }
}
?>

6. src/Controllers/UsersController.php

更新后的 UsersController.php 支持多个参数和可选参数:

<?php
// src/Controllers/UsersController.php

namespace MyFramework\Controllers;

use MyFramework\Controller;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

class UsersController extends Controller
{
    private $logger;

    public function __construct()
    {
        // 初始化日志
        $this->logger = new Logger('users');
        $this->logger->pushHandler(new StreamHandler(__DIR__ . '/../../logs/app.log', Logger::DEBUG));
    }

    /**
     * 用户列表方法
     */
    public function list()
    {
        $this->logger->info("请求用户列表");

        try {
            // 示例:从外部 API 获取用户数据
            // 这里为了简单使用静态数据
            $users = [
                ['id' => 1, 'name' => '张三', 'email' => 'zhangsan@example.com'],
                ['id' => 2, 'name' => '李四', 'email' => 'lisi@example.com'],
                ['id' => 3, 'name' => '王五', 'email' => 'wangwu@example.com'],
            ];

            echo "<h1>用户列表</h1>";
            echo "<ul>";
            foreach ($users as $user) {
                echo "<li>" . htmlspecialchars($user['name']) . " (" . htmlspecialchars($user['email']) . ")</li>";
            }
            echo "</ul>";
        } catch (\Exception $e) {
            $this->logger->error("获取用户列表失败", ['error' => $e->getMessage()]);
            echo "<h1>无法获取用户列表</h1>";
        }
    }

    /**
     * 显示单个用户的方法
     *
     * @param string $id 用户ID
     */
    public function show($id)
    {
        $this->logger->info("请求用户详情", ['id' => $id]);

        try {
            // 示例:根据ID获取用户数据
            // 这里为了简单使用静态数据
            $users = [
                1 => ['id' => 1, 'name' => '张三', 'email' => 'zhangsan@example.com'],
                2 => ['id' => 2, 'name' => '李四', 'email' => 'lisi@example.com'],
                3 => ['id' => 3, 'name' => '王五', 'email' => 'wangwu@example.com'],
            ];

            if (isset($users[$id])) {
                $user = $users[$id];
                echo "<h1>用户详情</h1>";
                echo "<p>ID: " . htmlspecialchars($user['id']) . "</p>";
                echo "<p>姓名: " . htmlspecialchars($user['name']) . "</p>";
                echo "<p>邮箱: " . htmlspecialchars($user['email']) . "</p>";
            } else {
                echo "<h1>用户未找到</h1>";
            }
        } catch (\Exception $e) {
            $this->logger->error("获取用户详情失败", ['error' => $e->getMessage()]);
            echo "<h1>无法获取用户详情</h1>";
        }
    }

    /**
     * 显示用户的具体信息(可选参数)
     *
     * @param string $id 用户ID
     * @param string|null $info 信息类型(可选)
     */
    public function info($id, $info = null)
    {
        $this->logger->info("请求用户信息", ['id' => $id, 'info' => $info]);

        try {
            // 示例:根据ID和信息类型获取用户数据
            // 这里为了简单使用静态数据
            $users = [
                1 => [
                    'id' => 1,
                    'name' => '张三',
                    'email' => 'zhangsan@example.com',
                    'articles' => ['文章1', '文章2'],
                    'profile' => '这是张三的个人简介。'
                ],
                2 => [
                    'id' => 2,
                    'name' => '李四',
                    'email' => 'lisi@example.com',
                    'articles' => ['文章A'],
                    'profile' => '这是李四的个人简介。'
                ],
                3 => [
                    'id' => 3,
                    'name' => '王五',
                    'email' => 'wangwu@example.com',
                    'articles' => [],
                    'profile' => '这是王五的个人简介。'
                ],
            ];

            if (isset($users[$id])) {
                $user = $users[$id];
                echo "<h1>用户信息</h1>";
                echo "<p>ID: " . htmlspecialchars($user['id']) . "</p>";
                echo "<p>姓名: " . htmlspecialchars($user['name']) . "</p>";
                echo "<p>邮箱: " . htmlspecialchars($user['email']) . "</p>";

                if ($info === 'articles') {
                    echo "<h2>用户的文章</h2>";
                    if (!empty($user['articles'])) {
                        echo "<ul>";
                        foreach ($user['articles'] as $article) {
                            echo "<li>" . htmlspecialchars($article) . "</li>";
                        }
                        echo "</ul>";
                    } else {
                        echo "<p>暂无文章。</p>";
                    }
                } elseif ($info === 'profile') {
                    echo "<h2>用户简介</h2>";
                    echo "<p>" . htmlspecialchars($user['profile']) . "</p>";
                } else {
                    echo "<p>请选择查看的具体信息(例如 'articles' 或 'profile')。</p>";
                }
            } else {
                echo "<h1>用户未找到</h1>";
            }
        } catch (\Exception $e) {
            $this->logger->error("获取用户信息失败", ['error' => $e->getMessage()]);
            echo "<h1>无法获取用户信息</h1>";
        }
    }
}
?>

代码解释:

  1. info 方法

    • 接受一个必需参数 $id 和一个可选参数 $info

    • 根据 $info 参数的值(例如 articles, profile),显示不同类型的用户信息。

    • 如果 $info 未提供或不匹配,提示用户选择具体信息类型。

  2. 参数默认值

    • 在方法定义中,$info = null 表示 $info 是可选的,如果未提供,则默认为 null


测试新功能

1. 启动本地服务器

在项目根目录下运行以下命令启动 PHP 内置服务器:

php -S localhost:8000

2. 访问不同的路由

a. 访问用户列表

  • URL: http://localhost:8000/users

  • 路由: /users

  • 控制器方法: UsersController@list

  • 输出:

    <h1>用户列表</h1>
    <ul>
        <li>张三 (zhangsan@example.com)</li>
        <li>李四 (lisi@example.com)</li>
        <li>王五 (wangwu@example.com)</li>
    </ul>
    

b. 访问单个用户详情

c. 访问带有多个参数和可选参数的路由

  • URL: http://localhost:8000/user/1/info/articles

  • 路由: /user/{id}/info/{info?}

  • 控制器方法: UsersController@info

  • 输出:

    <h1>用户信息</h1>
    <p>ID: 1</p>
    <p>姓名: 张三</p>
    <p>邮箱: zhangsan@example.com</p>
    <h2>用户的文章</h2>
    <ul>
        <li>文章1</li>
        <li>文章2</li>
    </ul>
    
  • URL: http://localhost:8000/user/2/info

  • 路由: /user/{id}/info/{info?}

  • 输出:

    <h1>用户信息</h1>
    <p>ID: 2</p>
    <p>姓名: 李四</p>
    <p>邮箱: lisi@example.com</p>
    <p>请选择查看的具体信息(例如 'articles' 或 'profile')。</p>
    
  • URL: http://localhost:8000/user/3/info/profile

  • 路由: /user/{id}/info/{info?}

  • 输出:

    <h1>用户信息</h1>
    <p>ID: 3</p>
    <p>姓名: 王五</p>
    <p>邮箱: wangwu@example.com</p>
    <h2>用户简介</h2>
    <p>这是王五的个人简介。</p>
    

3. 检查日志

查看 logs/app.log 文件,确认日志记录:

[2024-04-27 14:30:00] users.INFO: 请求用户列表 []
[2024-04-27 14:31:00] users.INFO: 请求用户详情 {"id":"1"}
[2024-04-27 14:32:00] users.INFO: 请求用户详情 {"id":"2"}
[2024-04-27 14:33:00] users.INFO: 请求用户详情 {"id":"999"}
[2024-04-27 14:34:00] users.INFO: 请求用户信息 {"id":"1","info":"articles"}
[2024-04-27 14:35:00] users.INFO: 请求用户信息 {"id":"2","info":null}
[2024-04-27 14:36:00] users.INFO: 请求用户信息 {"id":"3","info":"profile"}

总结

通过以上步骤,已经成功地扩展了自定义 PHP 框架的路由系统,使其支持 多个参数可选参数。具体实现包括:

  1. 路由定义语法:使用 {param}{param?} 语法定义动态参数和可选参数。

  2. Router 类增强:修改路由器类以解析和匹配多个参数和可选参数,并将其传递给控制器方法。

  3. 控制器方法调整:在控制器中定义接收多个参数和可选参数的方法,并根据参数处理不同的业务逻辑。

  4. 日志记录:使用 Monolog 记录请求和参数,便于调试和监控。

进一步扩展建议

  1. 支持正则表达式中的参数约束

    • 例如,限制某个参数只能是数字或特定格式。

    • 修改 Router.php 中的参数匹配规则。

  2. 中间件机制

    • 添加中间件支持,例如认证、授权、日志记录等。

    • 实现中间件堆栈,在路由匹配和控制器方法执行之间插入中间件。

  3. 命名路由和反向路由

    • 支持为路由命名,并根据名称生成 URL。

    • 这对于生成链接和重定向非常有用。

  4. 集成模板引擎

    • 使用 Twig 或 Blade 等模板引擎,将视图与控制器逻辑分离。

    • 提高视图的可维护性和可复用性。

  5. 依赖注入容器

    • 实现或集成依赖注入容器,提高代码的可测试性和可维护性。

    • 管理控制器和其他类的依赖关系。

  6. 错误和异常处理

    • 实现统一的错误和异常处理机制,提供更友好的错误页面和日志记录。

    • 捕获控制器中的异常,并进行适当的处理。

  7. 支持多种 HTTP 方法

    • 除了 GET 和 POST,支持 PUT、DELETE 等其他 HTTP 方法,以满足 RESTful API 的需求。

  8. 路由缓存

    • 实现路由缓存,提高路由匹配的性能,特别是在路由数量较多时。