目录¶
以下将详细介绍每个步骤。
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.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"; } } ?>
更新
Controller.php
以使用命名空间创建
src/Controller.php
并添加命名空间:<?php // src/Controller.php namespace MyFramework; class Controller { // 在这里可以添加公共的方法或属性 } ?>
更新控制器类以使用 Composer 包
创建
src/Controllers/HomeController.php
并使用 Monolog:<?php // src/Controllers/HomeController.php namespace 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.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 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.php namespace 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.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 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.php
namespace 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/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)以分离视图和逻辑。
依赖注入容器:实现或集成依赖注入容器,提高代码的可测试性和可维护性。
错误和异常处理:实现统一的错误和异常处理机制,提供友好的错误页面。