增加对路由参数的支持

要在自定义的 PHP 框架中增加路由参数支持,使得路由能够接受动态参数(如 /user/123),需要对现有的路由系统进行一些增强。以下内容将详细介绍如何实现这一功能,包括修改路由器类、定义带参数的路由、在控制器中处理这些参数等步骤。

目录

  1. 理解路由参数

  2. 更新目录结构(可选)

  3. 修改路由器类以支持路由参数

  4. 定义带参数的路由

  5. 在控制器中处理路由参数

  6. 示例:添加动态用户路由

  7. 完整代码示例

  8. 测试新功能

  9. 总结


理解路由参数

路由参数允许在 URL 中捕获动态值,并将这些值传递给控制器的方法。例如:

  • /user/123:捕获用户 ID 123

  • /post/45/comment/67:捕获帖子 ID 45 和评论 ID 67

实现路由参数的关键在于:

  1. 定义带参数的路由模式:使用特定的语法(如 {id})来标识动态部分。

  2. 解析请求 URI:匹配请求 URI 与路由模式,并提取参数值。

  3. 传递参数给控制器:将提取的参数值作为方法参数传递给控制器。


更新目录结构(可选)

为了更好地组织代码,将控制器类放在 src/Controllers/ 目录下,并使用命名空间。以下是目录结构:

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

修改路由器类以支持路由参数

为了支持路由参数,我们需要增强现有的 Router 类,使其能够:

  1. 识别路由中的动态参数

  2. 匹配请求 URI 并提取参数值

  3. 将参数值传递给控制器方法

Router.php

更新 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}'
     * @param string $action 控制器和方法,例如 'UsersController@show'
     */
    public function add($method, $uri, $action)
    {
        // 转换 URI 模式为正则表达式,并提取参数名称
        $pattern = preg_replace('/\{([a-zA-Z0-9_]+)\}/', '(?P<\1>[a-zA-Z0-9_]+)', $uri);
        $pattern = '#^' . $pattern . '$#';

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

    /**
     * 分发请求到相应的控制器方法
     *
     * @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])) {
                            $params[$param] = $matches[$param];
                        }
                    }
                    $this->executeAction($route['action'], $params);
                    return;
                }
            }
        }
        // 如果没有匹配的路由,返回 404
        $this->sendNotFound();
    }

    /**
     * 执行控制器的方法并传递参数
     *
     * @param string $action 控制器和方法,例如 'UsersController@show'
     * @param array $params 路由参数
     */
    private function executeAction($action, $params = [])
    {
        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. 路由模式转换

    • 使用正则表达式将 URI 中的 {param} 转换为命名捕获组 (?P<param>[a-zA-Z0-9_]+)

    • 例如,/user/{id} 会转换为 #^/user/(?P<id>[a-zA-Z0-9_]+)$#

  2. 参数提取

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

    • dispatch 方法中,使用 preg_match 匹配请求 URI,并提取参数值。

  3. 执行控制器方法

    • 使用 call_user_func_array 调用控制器方法,并将参数值作为参数传递。


定义带参数的路由

index.php 中,定义带参数的路由。例如,添加一个路由 /user/{id},映射到 UsersController@show 方法:

<?php
// index.php

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

// 定义带参数的路由
$router->add('GET', '/user/{id}', 'UsersController@show');

// 其他路由
$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->dispatch($requestMethod, $requestUri);
?>

说明:

  • /user/{id} 表示 /user/ 后面跟着一个动态参数 id

  • 这个参数会被提取并传递给 UsersControllershow 方法。


在控制器中处理路由参数

现在,我们需要在控制器中创建 UsersController,并定义 show 方法来处理传递的参数。

UsersController.php

创建 src/Controllers/UsersController.php,并添加 show 方法:

<?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>";
        }
    }
}
?>

代码解释:

  1. 构造函数

    • 初始化 Monolog 日志记录器,将日志写入 logs/app.log 文件。

  2. list 方法

    • 显示用户列表。

  3. show 方法

    • 接受一个参数 $id,表示用户 ID。

    • 根据 ID 显示相应的用户详情。

    • 如果用户不存在,显示“用户未找到”。


示例:添加动态用户路由

现在,已经有了支持路由参数的路由系统和控制器方法。以下是如何添加一个动态用户路由的具体步骤:

  1. 定义路由

    index.php 中添加一个新的路由 /user/{id},映射到 UsersController@show 方法。

    <?php
    // index.php
    
    // ... 之前的代码 ...
    
    // 定义带参数的路由
    $router->add('GET', '/user/{id}', 'UsersController@show');
    
    // 其他路由
    $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->dispatch($requestMethod, $requestUri);
    ?>
    
  2. 访问路由

    • 访问 http://localhost:8000/users 显示用户列表。

    • 访问 http://localhost:8000/user/1 显示用户 ID 为 1 的详情。

    • 访问 http://localhost:8000/user/999 显示“用户未找到”。


完整代码示例

以下是更新后的完整框架代码,包括路由参数支持和控制器方法的实现。

目录结构

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 and route parameters",
    "type": "project",
    "require": {
        "monolog/monolog": "^2.0",
        "guzzlehttp/guzzle": "^7.0"
    },
    "autoload": {
        "psr-4": {
            "MyFramework\\": "src/"
        }
    }
}

说明:

  • monolog/monolog:用于日志记录。

  • guzzlehttp/guzzle:用于发送 HTTP 请求。

  • autoload:配置 PSR-4 自动加载,MyFramework\ 命名空间对应 src/ 目录。

2. index.php

更新后的 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->dispatch($requestMethod, $requestUri);
?>

3. src/Router.php

更新后的 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}'
     * @param string $action 控制器和方法,例如 'UsersController@show'
     */
    public function add($method, $uri, $action)
    {
        // 转换 URI 模式为正则表达式,并提取参数名称
        $pattern = preg_replace('/\{([a-zA-Z0-9_]+)\}/', '(?P<\1>[a-zA-Z0-9_]+)', $uri);
        $pattern = '#^' . $pattern . '$#';

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

    /**
     * 分发请求到相应的控制器方法
     *
     * @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])) {
                            $params[$param] = $matches[$param];
                        }
                    }
                    $this->executeAction($route['action'], $params);
                    return;
                }
            }
        }
        // 如果没有匹配的路由,返回 404
        $this->sendNotFound();
    }

    /**
     * 执行控制器的方法并传递参数
     *
     * @param string $action 控制器和方法,例如 'UsersController@show'
     * @param array $params 路由参数
     */
    private function executeAction($action, $params = [])
    {
        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
// src/Controllers/UsersController.php

namespace MyFramework\Controllers;

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

class UsersController extends Controller
{
    private $logger;
    private $client;

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

        // 初始化 Guzzle 客户端
        $this->client = new Client();
    }

    /**
     * 用户列表方法
     */
    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>";
        }
    }
}
?>

说明:

  • show 方法:接受一个参数 $id,用于显示指定用户的详情。

  • 安全处理:使用 htmlspecialchars 防止 XSS 攻击。


示例:添加动态用户路由

现在,可以通过以下步骤测试带参数的路由:

  1. 启动本地服务器

    在项目根目录下运行:

    php -S localhost:8000
    
  2. 访问用户列表

    打开浏览器,访问 http://localhost:8000/users,将看到用户列表。

  3. 访问单个用户详情


完整代码示例

以下是所有相关文件的完整代码。

1. composer.json

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

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->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}'
     * @param string $action 控制器和方法,例如 'UsersController@show'
     */
    public function add($method, $uri, $action)
    {
        // 转换 URI 模式为正则表达式,并提取参数名称
        $pattern = preg_replace('/\{([a-zA-Z0-9_]+)\}/', '(?P<\1>[a-zA-Z0-9_]+)', $uri);
        $pattern = '#^' . $pattern . '$#';

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

    /**
     * 分发请求到相应的控制器方法
     *
     * @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])) {
                            $params[$param] = $matches[$param];
                        }
                    }
                    $this->executeAction($route['action'], $params);
                    return;
                }
            }
        }
        // 如果没有匹配的路由,返回 404
        $this->sendNotFound();
    }

    /**
     * 执行控制器的方法并传递参数
     *
     * @param string $action 控制器和方法,例如 'UsersController@show'
     * @param array $params 路由参数
     */
    private function executeAction($action, $params = [])
    {
        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

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

namespace MyFramework\Controllers;

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

class UsersController extends Controller
{
    private $logger;
    private $client;

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

        // 初始化 Guzzle 客户端
        $this->client = new Client();
    }

    /**
     * 用户列表方法
     */
    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>";
        }
    }
}
?>

7. 创建日志目录

确保有一个 logs/ 目录,并且 PHP 有权限写入日志文件:

mkdir logs
chmod 755 logs

测试新功能

  1. 启动本地服务器

    在项目根目录下运行:

    php -S localhost:8000
    
  2. 访问带参数的路由

  3. 检查日志

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

    [2024-04-27 14:23:01] users.INFO: 请求用户详情 {"id":"1"} []
    [2024-04-27 14:23:05] users.INFO: 请求用户详情 {"id":"2"} []
    [2024-04-27 14:23:09] users.INFO: 请求用户详情 {"id":"999"} []
    

总结

通过以上步骤,我们已经成功地在自定义的 PHP 框架中增加了路由参数支持。具体实现包括:

  1. 路由器类的增强:通过正则表达式匹配路由模式,提取参数值,并将其传递给控制器方法。

  2. 定义带参数的路由:在 index.php 中使用 {param} 语法定义动态路由。

  3. 控制器方法的调整:在控制器中定义接受参数的方法,并在方法中使用这些参数。

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

进一步的扩展建议:

  1. 支持可选参数

    修改路由匹配逻辑,允许某些参数可选。例如,/user/{id}?,以支持 /user/user/123

  2. 高级路由匹配

    支持正则表达式中的更复杂的匹配,如限制参数类型(数字、字母等)。

  3. 中间件机制

    实现中间件,处理认证、授权、日志记录等功能。

  4. 命名路由和反向路由

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

  5. 错误处理和异常管理

    增强错误处理机制,提供更友好的错误页面和日志记录。

  6. 支持多种 HTTP 方法

    除了 GET 和 POST,支持 PUT、DELETE 等其他 HTTP 方法。

  7. 集成模板引擎

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