原文地址:使用Composer实现自动加载类 更多内容请关注:智想天开
1. 初始化 Composer 并安装所需的依赖
首先,确保的开发环境中已安装 Composer。如果尚未安装,可以参考 Composer 官方安装指南 进行安装。
步骤:
-
初始化 Composer 项目
在的项目根目录(例如
my_framework/
)下运行以下命令以初始化 Composer 项目:cd my_framework composer init
该命令会引导您完成
composer.json
文件的创建过程。 -
安装所需的 Composer 包
假设在控制器中使用一个日志库,例如 Monolog。运行以下命令来安装 Monolog:
composer require monolog/monolog
这将下载 Monolog 并将其添加到
composer.json
的require
部分,同时生成或更新vendor/
目录和autoload.php
文件。
2. 配置自动加载(Autoloading)
Composer 提供了强大的自动加载功能,可以根据 composer.json
中的配置自动加载框架类和控制器类。通过使用命名空间(Namespace),可以更好地组织和管理代码。
步骤:
-
定义命名空间
假设框架命名空间为
MyFramework
,控制器命名空间为MyFrameworkControllers
。 -
更新
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
对应项目根目录。
-
-
生成自动加载文件
运行以下命令以更新 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/
,MyFrameworkControllers
对应src/Controllers/
。
4. 在控制器中使用 Composer 包
现在,可以在控制器中使用通过 Composer 安装的包。以使用 Monolog 作为示例。
步骤:
-
修改
index.php
以使用 Composer 自动加载<?php // index.php// 启用错误报告(开发阶段使用,生产环境请关闭) ini_set('display_errors', 1); error_reporting(E_ALL);// 自动加载 Composer 及框架类 require __DIR__ . '/vendor/autoload.php';// 使用命名空间 use MyFrameworkRouter;// 获取请求的 URI 和方法 $requestUri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); $requestMethod = $_SERVER['REQUEST_METHOD'];// 实例化路由器并添加路由 $router = new Router();// 定义路由规则 $router->add('GET', '/', 'ControllersHomeController@index'); $router->add('GET', '/about', 'ControllersHomeController@about'); $router->add('POST', '/submit', 'ControllersHomeController@submit');// 处理请求 $router->dispatch($requestMethod, $requestUri); ?>
-
更新
Router.php
以使用命名空间创建
src/Router.php
并添加命名空间:<?php // src/Router.phpnamespace 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";} } ?>
-
更新
Controller.php
以使用命名空间创建
src/Controller.php
并添加命名空间:<?php // src/Controller.phpnamespace MyFramework;class Controller {// 在这里可以添加公共的方法或属性 } ?>
-
更新控制器类以使用 Composer 包
创建
src/Controllers/HomeController.php
并使用 Monolog:<?php // src/Controllers/HomeController.phpnamespace MyFrameworkControllers;use MyFrameworkController; use MonologLogger; use MonologHandlerStreamHandler;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
文件。 -
日志记录:在每个方法中记录访问日志和表单提交数据。
-
-
创建日志目录
为了让 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 MyFrameworkRouter;// 获取请求的 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.phpnamespace 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.phpnamespace MyFramework;class Controller
{// 在这里可以添加公共的方法或属性
}
?>
5. src/Controllers/HomeController.php
<?php
// src/Controllers/HomeController.phpnamespace MyFrameworkControllers;use MyFrameworkController;
use MonologLogger;
use MonologHandlerStreamHandler;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 请求。
步骤:
-
安装 Guzzle
composer require guzzlehttp/guzzle
-
创建新的控制器
创建
src/Controllers/UsersController.php
:<?php // src/Controllers/UsersController.phpnamespace MyFrameworkControllers;use MyFrameworkController; use GuzzleHttpClient; use MonologLogger; use MonologHandlerStreamHandler;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>";}} } ?>
-
添加新路由
打开
index.php
并添加新路由:// index.php 中的路由定义部分 $router->add('GET', '/users', 'UsersController@list');
-
测试新路由
重新启动 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 MyFrameworkRouter;// 获取请求的 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.phpnamespace 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.phpnamespace MyFramework;class Controller
{// 在这里可以添加公共的方法或属性
}
?>
5. src/Controllers/HomeController.php
<?php
// src/Controllers/HomeController.phpnamespace MyFrameworkControllers;use MyFrameworkController;
use MonologLogger;
use MonologHandlerStreamHandler;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.phpnamespace MyFrameworkControllers;use MyFrameworkController;
use GuzzleHttpClient;
use MonologLogger;
use MonologHandlerStreamHandler;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. 测试框架
-
启动本地服务器
使用 PHP 内置服务器进行测试。在项目根目录下运行以下命令:
php -S localhost:8000
-
访问不同的路由
-
主页: http://localhost:8000/
-
关于页面: http://localhost:8000/about
-
联系我们页面: http://localhost:8000/contact
-
用户列表页面: http://localhost:8000/users
-
-
测试表单提交
创建一个简单的 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)。以下是关键点的回顾:
-
Composer 初始化:使用
composer init
初始化项目,并通过composer require
安装所需的包。 -
自动加载配置:在
composer.json
中配置autoload
,并使用 PSR-4 标准组织代码。 -
命名空间使用:通过定义命名空间,使类的组织更为规范,并利用 Composer 的自动加载功能简化类的引入。
-
控制器集成 Composer 包:在控制器中引入和使用 Composer 安装的包,扩展框架的功能。
-
日志记录与外部 API:示例展示了如何使用 Monolog 记录日志,以及如何使用 Guzzle 进行外部 HTTP 请求。
进一步扩展
为了使框架更加完善,可以考虑以下扩展:
-
动态路由支持:支持带参数的动态路由,如
/user/{id}
。 -
中间件机制:添加中间件以处理认证、授权、日志记录等功能。
-
视图模板集成:集成模板引擎(如 Twig 或 Blade)以分离视图和逻辑。
-
依赖注入容器:实现或集成依赖注入容器,提高代码的可测试性和可维护性。
-
错误和异常处理:实现统一的错误和异常处理机制,提供友好的错误页面。