使用Composer实现自动加载类

本文介绍如何在自定义 PHP 框架中集成 Composer,并使控制器类能够调用 Composer 中的类和方法。

目录

  1. 初始化 Composer 并安装所需的依赖

  2. 配置自动加载(Autoloading)

  3. 调整框架目录结构

  4. 在控制器中使用 Composer 包

  5. 完整示例代码

  6. 总结

以下将详细介绍每个步骤。


1. 初始化 Composer 并安装所需的依赖

首先,确保的开发环境中已安装 Composer。如果尚未安装,可以参考 Composer 官方安装指南 进行安装。

步骤:

  1. 初始化 Composer 项目

    在的项目根目录(例如 my_framework/)下运行以下命令以初始化 Composer 项目:

    cd my_framework
    composer init
    

    该命令会引导您完成 composer.json 文件的创建过程。

  2. 安装所需的 Composer 包

    假设在控制器中使用一个日志库,例如 Monolog。运行以下命令来安装 Monolog:

    composer require monolog/monolog
    

    这将下载 Monolog 并将其添加到 composer.jsonrequire 部分,同时生成或更新 vendor/ 目录和 autoload.php 文件。


2. 配置自动加载(Autoloading)

Composer 提供了强大的自动加载功能,可以根据 composer.json 中的配置自动加载框架类和控制器类。通过使用命名空间(Namespace),可以更好地组织和管理代码。

步骤:

  1. 定义命名空间

    假设框架命名空间为 MyFramework,控制器命名空间为 MyFramework\Controllers

  2. 更新 composer.json

    打开项目根目录下的 composer.json 文件,并添加 autoload 部分:

    {
        "name": "yourname/my_framework",
        "description": "A simple PHP framework example",
        "type": "project",
        "require": {
            "monolog/monolog": "^2.0"
        },
        "autoload": {
            "psr-4": {
                "MyFramework\\": ""
            }
        }
    }
    

    说明:

    • psr-4:PSR-4 是一种自动加载标准,允许基于命名空间和目录结构自动加载类。

    • "MyFramework\\": "":表示命名空间 MyFramework 对应项目根目录。

  3. 生成自动加载文件

    运行以下命令以更新 Composer 的自动加载配置:

    composer dump-autoload
    

    这将生成或更新 vendor/autoload.php 文件,确保框架类和控制器类可以被自动加载。


3. 调整框架目录结构

为了更好地支持命名空间和自动加载,调整框架目录结构如下:

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

说明:

  • src/:存放框架的核心类。

  • src/Controllers/:存放控制器类。

  • 命名空间对应目录结构MyFramework 对应 src/MyFramework\Controllers 对应 src/Controllers/


4. 在控制器中使用 Composer 包

现在,可以在控制器中使用通过 Composer 安装的包。以使用 Monolog 作为示例。

步骤:

  1. 修改 index.php 以使用 Composer 自动加载

    <?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', '/', 'Controllers\HomeController@index');
    $router->add('GET', '/about', 'Controllers\HomeController@about');
    $router->add('POST', '/submit', 'Controllers\HomeController@submit');
    
    // 处理请求
    $router->dispatch($requestMethod, $requestUri);
    ?>
    
  2. 更新 Router.php 以使用命名空间

    创建 src/Router.php 并添加命名空间:

    <?php
    // src/Router.php
    
    namespace MyFramework;
    
    class Router
    {
        private $routes = [];
    
        /**
         * 添加路由规则
         *
         * @param string $method HTTP 方法(GET, POST, etc.)
         * @param string $uri 请求的 URI
         * @param string $action 控制器和方法,例如 'HomeController@index'
         */
        public function add($method, $uri, $action)
        {
            $this->routes[] = [
                'method'  => strtoupper($method),
                'uri'     => $uri,
                'action'  => $action
            ];
        }
    
        /**
         * 分发请求到相应的控制器方法
         *
         * @param string $requestMethod HTTP 方法
         * @param string $requestUri 请求的 URI
         */
        public function dispatch($requestMethod, $requestUri)
        {
            foreach ($this->routes as $route) {
                if ($route['method'] === strtoupper($requestMethod) && $route['uri'] === $requestUri) {
                    $this->executeAction($route['action']);
                    return;
                }
            }
            // 如果没有匹配的路由,返回 404
            $this->sendNotFound();
        }
    
        /**
         * 执行控制器的方法
         *
         * @param string $action 控制器和方法,例如 'HomeController@index'
         */
        private function executeAction($action)
        {
            list($controllerName, $method) = explode('@', $action);
            $fullControllerName = "MyFramework\\Controllers\\$controllerName";
            if (class_exists($fullControllerName)) {
                $controller = new $fullControllerName();
                if (method_exists($controller, $method)) {
                    call_user_func([$controller, $method]);
                    return;
                }
            }
            // 如果控制器或方法不存在,返回 404
            $this->sendNotFound();
        }
    
        /**
         * 发送 404 响应
         */
        private function sendNotFound()
        {
            header("HTTP/1.0 404 Not Found");
            echo "404 Not Found";
        }
    }
    ?>
    
  3. 更新 Controller.php 以使用命名空间

    创建 src/Controller.php 并添加命名空间:

    <?php
    // src/Controller.php
    
    namespace MyFramework;
    
    class Controller
    {
        // 在这里可以添加公共的方法或属性
    }
    ?>
    
  4. 更新控制器类以使用 Composer 包

    创建 src/Controllers/HomeController.php 并使用 Monolog:

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

    说明:

    • Monolog 使用:在控制器构造函数中初始化 Monolog 记录器,并将日志写入 logs/app.log 文件。

    • 日志记录:在每个方法中记录访问日志和表单提交数据。

  5. 创建日志目录

    为了让 Monolog 能够写入日志文件,需要创建一个 logs/ 目录并确保 PHP 有写入权限。

    mkdir logs
    chmod 755 logs
    

5. 完整示例代码

以下是调整后的完整框架代码,包括 Composer 集成和使用 Monolog 的控制器。

目录结构

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

1. composer.json

初始化后可能类似于:

{
    "name": "yourname/my_framework",
    "description": "A simple PHP framework example with Composer integration",
    "type": "project",
    "require": {
        "monolog/monolog": "^2.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->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
     * @param string $action 控制器和方法,例如 'HomeController@index'
     */
    public function add($method, $uri, $action)
    {
        $this->routes[] = [
            'method'  => strtoupper($method),
            'uri'     => $uri,
            'action'  => $action
        ];
    }

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

    /**
     * 执行控制器的方法
     *
     * @param string $action 控制器和方法,例如 'HomeController@index'
     */
    private function executeAction($action)
    {
        list($controllerName, $method) = explode('@', $action);
        $fullControllerName = "MyFramework\\Controllers\\$controllerName";
        if (class_exists($fullControllerName)) {
            $controller = new $fullControllerName();
            if (method_exists($controller, $method)) {
                call_user_func([$controller, $method]);
                return;
            }
        }
        // 如果控制器或方法不存在,返回 404
        $this->sendNotFound();
    }

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

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. 创建日志目录

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

mkdir logs
chmod 755 logs

6. 添加新路由和控制器

假设添加一个新的路由 /users,并调用一个新的控制器方法 UsersController@list,同时使用 Composer 安装的另一个包,例如 Guzzle HTTP 进行 HTTP 请求。

步骤:

  1. 安装 Guzzle

    composer require guzzlehttp/guzzle
    
  2. 创建新的控制器

    创建 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 获取用户数据
                $response = $this->client->request('GET', 'https://jsonplaceholder.typicode.com/users');
                $users = json_decode($response->getBody(), true);
    
                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>";
            }
        }
    }
    ?>
    
  3. 添加新路由

    打开 index.php 并添加新路由:

    // index.php 中的路由定义部分
    $router->add('GET', '/users', 'UsersController@list');
    
  4. 测试新路由

    重新启动 PHP 内置服务器(如果已经在运行,无需重新启动),然后访问 http://localhost:8000/users。您应该会看到一个用户列表,数据来自外部 API,并且操作被记录在 logs/app.log 文件中。


7. 完整代码示例

以下是更新后的完整框架代码,包括 Composer 集成和在控制器中使用 Composer 包的示例。

目录结构

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

{
    "name": "yourname/my_framework",
    "description": "A simple PHP framework example with Composer integration",
    "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->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
     * @param string $action 控制器和方法,例如 'HomeController@index'
     */
    public function add($method, $uri, $action)
    {
        $this->routes[] = [
            'method'  => strtoupper($method),
            'uri'     => $uri,
            'action'  => $action
        ];
    }

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

    /**
     * 执行控制器的方法
     *
     * @param string $action 控制器和方法,例如 'HomeController@index'
     */
    private function executeAction($action)
    {
        list($controllerName, $method) = explode('@', $action);
        $fullControllerName = "MyFramework\\Controllers\\$controllerName";
        if (class_exists($fullControllerName)) {
            $controller = new $fullControllerName();
            if (method_exists($controller, $method)) {
                call_user_func([$controller, $method]);
                return;
            }
        }
        // 如果控制器或方法不存在,返回 404
        $this->sendNotFound();
    }

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

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 获取用户数据
            $response = $this->client->request('GET', 'https://jsonplaceholder.typicode.com/users');
            $users = json_decode($response->getBody(), true);

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

7. 创建日志目录

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

mkdir logs
chmod 755 logs

8. 测试框架

  1. 启动本地服务器

    使用 PHP 内置服务器进行测试。在项目根目录下运行以下命令:

    php -S localhost:8000
    
  2. 访问不同的路由

  3. 测试表单提交

    创建一个简单的 HTML 表单,提交到 /submit

    <!-- save as form.html in project root -->
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>表单提交</title>
    </head>
    <body>
        <h1>提交表单</h1>
        <form action="/submit" method="POST">
            <label for="name">姓名:</label>
            <input type="text" id="name" name="name" required><br><br>
            <label for="email">邮箱:</label>
            <input type="email" id="email" name="email" required><br><br>
            <button type="submit">提交</button>
        </form>
    </body>
    </html>
    

    然后访问 http://localhost:8000/form.html,填写表单并提交,将看到提交的数据被输出,并且日志记录在 logs/app.log 文件中。


9. 总结

通过以上步骤,已经成功地将 Composer 集成到自定义 PHP 框架中,并在控制器类中使用了 Composer 安装的包(如 Monolog 和 Guzzle)。以下是关键点的回顾:

  1. Composer 初始化:使用 composer init 初始化项目,并通过 composer require 安装所需的包。

  2. 自动加载配置:在 composer.json 中配置 autoload,并使用 PSR-4 标准组织代码。

  3. 命名空间使用:通过定义命名空间,使类的组织更为规范,并利用 Composer 的自动加载功能简化类的引入。

  4. 控制器集成 Composer 包:在控制器中引入和使用 Composer 安装的包,扩展框架的功能。

  5. 日志记录与外部 API:示例展示了如何使用 Monolog 记录日志,以及如何使用 Guzzle 进行外部 HTTP 请求。

进一步扩展

为了使框架更加完善,可以考虑以下扩展:

  • 动态路由支持:支持带参数的动态路由,如 /user/{id}

  • 中间件机制:添加中间件以处理认证、授权、日志记录等功能。

  • 视图模板集成:集成模板引擎(如 Twig 或 Blade)以分离视图和逻辑。

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

  • 错误和异常处理:实现统一的错误和异常处理机制,提供友好的错误页面。