阅读时间:1 分钟
0 字
模板控制器
模板控制器是处理页面请求和渲染模板的核心组件。DuxLite 提供了简洁的模板渲染方法,支持数据传递、布局管理和基本的渲染优化。
基本概念
设计理念
- 模板驱动:基于 Latte 模板引擎的页面渲染
- 数据传递:控制器向模板传递数据
- 布局管理:支持布局继承和组件复用
- 错误处理:优雅的模板错误处理机制
核心方法
- sendTpl():渲染模板并返回响应
- Location 重定向:通过响应头实现重定向
- 数据获取:从请求中获取参数和数据
- 数据验证:表单数据验证和处理
基础模板渲染
简单页面控制器
php
<?php
namespace App\Web;
use Core\Route\Attribute\Route;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class HomeController
{
/**
* 首页
*/
#[Route(methods: 'GET', route: '/', name: 'home', app: 'web')]
public function index(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
return sendTpl($response, 'home/index', [
'title' => '欢迎访问',
'message' => 'Hello DuxLite!',
'current_time' => date('Y-m-d H:i:s')
]);
}
/**
* 关于页面
*/
#[Route(methods: 'GET', route: '/about', name: 'about', app: 'web')]
public function about(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
$data = [
'title' => '关于我们',
'company' => [
'name' => 'DuxLite Framework',
'founded' => '2023',
'description' => '简洁高效的PHP框架'
],
'team' => [
['name' => '张三', 'role' => '架构师'],
['name' => '李四', 'role' => '开发工程师'],
['name' => '王五', 'role' => '产品经理']
]
];
return sendTpl($response, 'home/about', $data);
}
}表单处理
表单显示和处理
php
class ContactController
{
/**
* 显示联系表单
*/
#[Route(methods: 'GET', route: '/contact', name: 'contact.form', app: 'web')]
public function form(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
return sendTpl($response, 'contact/form', [
'title' => '联系我们',
'form_action' => '/contact',
'old_input' => [], // 用于表单回填
'errors' => [] // 用于显示验证错误
]);
}
/**
* 处理表单提交
*/
#[Route(methods: 'POST', route: '/contact', name: 'contact.submit', app: 'web')]
public function submit(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
$data = \Core\Utils\RequestParam::body($request);
try {
// 数据验证
$rules = [
'name' => [
['required', '姓名不能为空'],
['lengthMin', 2, '姓名至少2个字符']
],
'email' => [
['required', '邮箱不能为空'],
['email', '邮箱格式无效']
],
'subject' => [
['required', '主题不能为空']
],
'message' => [
['required', '留言内容不能为空'],
['lengthMin', 10, '留言内容至少10个字符']
]
];
$validated = \Core\Validator\Validator::parser($data, $rules);
// 保存留言
Contact::create([
'name' => $validated->name,
'email' => $validated->email,
'subject' => $validated->subject,
'message' => $validated->message,
'ip' => $request->getServerParams()['REMOTE_ADDR'] ?? '',
'created_at' => date('Y-m-d H:i:s')
]);
// 发送邮件通知(可选)
$this->sendContactNotification($validated);
// 重定向到成功页面
return $response->withHeader('Location', '/contact/success')->withStatus(302);
} catch (\Core\Handlers\ExceptionValidator $e) {
// 验证失败,返回表单页面并显示错误
return sendTpl($response, 'contact/form', [
'title' => '联系我们',
'form_action' => '/contact',
'old_input' => $data, // 回填表单数据
'errors' => $e->getErrors() // 显示验证错误
]);
}
}
/**
* 提交成功页面
*/
#[Route(methods: 'GET', route: '/contact/success', name: 'contact.success', app: 'web')]
public function success(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
return sendTpl($response, 'contact/success', [
'title' => '提交成功',
'message' => '感谢您的留言,我们会尽快回复!',
'redirect_url' => '/',
'redirect_delay' => 3
]);
}
private function sendContactNotification($data): void
{
// 发送邮件通知逻辑
// 可以使用队列异步处理
}
}数据传递和处理
请求数据获取
php
class UserController
{
#[Route(methods: ['GET', 'POST'], route: '/profile', name: 'profile', app: 'web')]
public function profile(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
// 获取当前用户
$user = $this->getCurrentUser();
if (!$user) {
return $response->withHeader('Location', '/login?redirect=' . urlencode('/profile'))->withStatus(302);
}
if ($request->getMethod() === 'POST') {
return $this->updateProfile($request, $response, $user);
}
// 获取查询参数
$params = \Core\Utils\RequestParam::query($request);
$tab = $params['tab'] ?? 'basic';
return sendTpl($response, 'user/profile', [
'title' => '个人资料',
'user' => $user,
'active_tab' => $tab,
'tabs' => [
'basic' => '基本信息',
'security' => '安全设置',
'privacy' => '隐私设置'
]
]);
}
private function updateProfile(
ServerRequestInterface $request,
ResponseInterface $response,
$user
): ResponseInterface {
$data = \Core\Utils\RequestParam::body($request);
try {
// 根据不同的tab处理不同的数据
switch ($data['tab'] ?? 'basic') {
case 'basic':
$this->updateBasicInfo($user, $data);
break;
case 'security':
$this->updatePassword($user, $data);
break;
case 'privacy':
$this->updatePrivacySettings($user, $data);
break;
}
return $response->withHeader('Location', '/profile?tab=' . ($data['tab'] ?? 'basic'))->withStatus(302);
} catch (\Exception $e) {
return sendTpl($response, 'user/profile', [
'title' => '个人资料',
'user' => $user,
'active_tab' => $data['tab'] ?? 'basic',
'error_message' => $e->getMessage(),
'old_input' => $data
]);
}
}
private function updateBasicInfo($user, $data): void
{
$rules = [
'nickname' => [['required', '昵称不能为空']],
'email' => [['required', '邮箱不能为空'], ['email', '邮箱格式无效']]
];
$validated = \Core\Validator\Validator::parser($data, $rules);
$user->update([
'nickname' => $validated->nickname,
'email' => $validated->email,
'bio' => $data['bio'] ?? '',
'updated_at' => date('Y-m-d H:i:s')
]);
}
}文件上传处理
php
class UploadController
{
#[Route(methods: 'GET', route: '/upload', name: 'upload.form', app: 'web')]
public function form(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
return sendTpl($response, 'upload/form', [
'title' => '文件上传',
'max_size' => '2MB',
'allowed_types' => ['jpg', 'png', 'gif']
]);
}
#[Route(methods: 'POST', route: '/upload', name: 'upload.submit', app: 'web')]
public function upload(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
try {
$file = \Core\Utils\RequestParam::file($request, 'file');
if (!$file || $file->getError() !== UPLOAD_ERR_OK) {
throw new \Core\Handlers\ExceptionBusiness('文件上传失败');
}
// 验证文件类型
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (!in_array($file->getClientMediaType(), $allowedTypes)) {
throw new \Core\Handlers\ExceptionBusiness('只允许上传图片文件');
}
// 验证文件大小 (2MB)
if ($file->getSize() > 2 * 1024 * 1024) {
throw new \Core\Handlers\ExceptionBusiness('文件大小不能超过2MB');
}
// 生成文件名和路径
$extension = pathinfo($file->getClientFilename(), PATHINFO_EXTENSION);
$filename = date('Y/m/d/') . uniqid() . '.' . $extension;
$uploadPath = 'uploads/' . $filename;
$stream = $file->getStream()->detach();
\Core\App::storage()->writeStream($uploadPath, $stream);
// 保存上传记录
$upload = Upload::create([
'original_name' => $file->getClientFilename(),
'filename' => $filename,
'path' => $uploadPath,
'size' => $file->getSize(),
'type' => $file->getClientMediaType(),
'created_at' => date('Y-m-d H:i:s')
]);
return sendTpl($response, 'upload/success', [
'title' => '上传成功',
'file' => $upload,
'preview_url' => \Core\App::storage()->publicUrl($uploadPath)
]);
} catch (\Exception $e) {
return sendTpl($response, 'upload/form', [
'title' => '文件上传',
'error_message' => $e->getMessage(),
'max_size' => '2MB',
'allowed_types' => ['jpg', 'png', 'gif']
]);
}
}
}错误处理
友好的错误页面
php
class ErrorController
{
#[Route(methods: 'GET', route: '/404', name: 'error.404', app: 'web')]
public function notFound(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
return sendTpl($response, 'errors/404', [
'title' => '页面未找到',
'message' => '抱歉,您访问的页面不存在',
'home_url' => '/',
'suggestions' => [
'检查网址是否正确',
'返回首页重新浏览',
'使用搜索功能查找内容'
]
], 'web', 404);
}
#[Route(methods: 'GET', route: '/500', name: 'error.500', app: 'web')]
public function serverError(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
return sendTpl($response, 'errors/500', [
'title' => '服务器错误',
'message' => '服务器遇到了错误,请稍后再试',
'home_url' => '/',
'contact_url' => '/contact'
], 'web', 500);
}
}
// 在控制器中使用错误处理
class BaseController
{
protected function handleError(\Exception $e, ResponseInterface $response): ResponseInterface
{
// 记录错误日志
\Core\App::log()->error('Controller error', [
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
]);
// 开发环境显示详细错误
if (\Core\App::$debug) {
throw $e;
}
// 生产环境显示友好错误页面
return sendTpl($response, 'errors/500', [
'title' => '系统错误',
'message' => '系统遇到了错误,请稍后再试'
], 'web', 500);
}
}响应助手
常用响应方法
php
class ResponseHelper
{
/**
* 渲染模板
*/
protected function render(
ResponseInterface $response,
string $template,
array $data = [],
int $status = 200
): ResponseInterface {
return sendTpl($response, $template, $data, 'web', $status);
}
/**
* 重定向
*/
protected function redirect(
ResponseInterface $response,
string $url,
int $status = 302
): ResponseInterface {
return $response->withHeader('Location', $url)->withStatus($status);
}
/**
* JSON响应(用于AJAX)
*/
protected function json(
ResponseInterface $response,
array $data,
int $status = 200
): ResponseInterface {
return send($response, 'success', $data, [], $status);
}
/**
* 带消息的重定向
*/
protected function redirectWithMessage(
ResponseInterface $response,
string $url,
string $message,
string $type = 'success'
): ResponseInterface {
// 将消息存储到会话中
session_start();
$_SESSION['flash_message'] = $message;
$_SESSION['flash_type'] = $type;
return $response->withHeader('Location', $url)->withStatus(302);
}
}通过这些模板控制器的使用方法,可以构建完整的Web应用页面,支持数据渲染、表单处理、文件上传等常见功能。