通过 PHP 原生代码实现 HTTP 控制器
引言
上篇教程学院君给大家演示了如何基于 PHP 原生代码实现简单的 HTTP 路由器,并且留了个引子:在我们注册路由时,除了通过匿名函数作为处理器之外,还可以通过控制器方法。
说到控制器,不得不提 MVC 设计模式,目前主流的 Web 开发框架都是基于 MVC 模式的,在 MVC 模式中,M 代表模型(Model),V 代表视图(View),C 代表控制器(Controller),控制器负责对请求进行处理并返回响应,模型类负责底层数据存取与处理,而视图层负责数据渲染与页面交互。对于一些 CRUD 操作(数据库增删改查操作)来说,常见的业务逻辑也就是从模型类获取数据并将其渲染到视图页面,或者从视图页面获取用户提交数据并将其存储到模型类,控制器则负责局中调度:
编写控制器基类
在面向对象编程中,我们可以编写控制器类来表示控制器,然后通过控制器方法作为具体的请求处理器,以博客应用为例,在 blog/app/http
目录下新建 controller
子目录来存放所有控制器,在编写具体的业务逻辑控制器之前,先新建一个 Controller.php
脚本来编写控制器基类:
<?php
namespace AppHttpController;
use AppCoreContainer;
use AppHttpRequest;
use AppStoreStoreContract;
class Controller
{
/**
* @var StoreContract
*/
protected $connection;
/**
* @var Container
*/
protected $container;
/**
* @var Request
*/
protected $request;
public function __construct()
{
$this->container = Container::getInstance();
$store = $this->container->resolve(StoreContract::class);
$this->connection = $store->newConnection();
$this->request = $this->container->resolve('request');
}
}
在这个控制器基类中,我们定义了会被所有控制器共用的 connection、container 和
编写业务控制器类
接下来,我们要做的就是将 app/routes/web.php
中之前通过匿名函数注册的请求处理器代码重构到对应的控制器方法中。
在 app/http/controller
目录下创建对应的控制器文件:
然后编写对应的控制器类代码,首先是处理首页请求的 HomeController
:
<?php
namespace AppHttpController;
class HomeController extends Controller
{
public function index()
{
$albums = $this->connection->table('albums')->selectAll();
$pageTitle = $siteName = $this->container->resolve('app.name');
$siteUrl = $this->container->resolve('app.url');
$siteDesc = $this->container->resolve('app.desc');
include __DIR__ . "/../../../views/home.php";
}
}
然后是处理专辑页请求的 AlbumController
:
<?php
namespace AppHttpController;
class AlbumController extends Controller
{
public function list()
{
$id = intval($this->request->get('id'));
if (empty($id)) {
echo '请指定要访问的专辑 ID';
exit();
}
$album = $this->connection->table('albums')->select($id);
$posts = $this->connection->table('posts')->selectByWhere(['album_id' => $id]);
$pageTitle = $siteName = $this->container->resolve('app.name');
$siteUrl = $this->container->resolve('app.url');
include __DIR__ . '/../../../views/album.php';
}
}
最后是处理文章页请求的 PostController
:
<?php
namespace AppHttpController;
class PostController extends Controller
{
public function show()
{
$id = intval($this->request->get('id'));
if (empty($id)) {
echo '请指定要访问的文章 ID';
exit();
}
$post = $this->connection->table('posts')->select($id);
$printer = $this->container->resolve(AppPrinterPrinterContract::class);
if ($this->container->resolve('app.editor') == 'markdown') {
$post['content'] = $printer->driver('markdown')->render($post['text']);
} else {
$post['content'] = $printer->render($post['html']);
}
$album = $this->connection->table('albums')->select($post['album_id']);
$pageTitle = $post['title'] . ' - ' . $this->container->resolve('app.name');
$siteUrl = $this->container->resolve('app.url');
include __DIR__ . '/../../../views/post.php';
}
}
重构路由注册和分发代码
这样一来,我们就将 app/routes/web.php
中之前以匿名函数形式注册的路由处理器代码都搬到控制器中了,因此,可以移除对应的代码,并将路由的处理器属性调整为对应的控制器方法:
<?php
$router = new AppHttpRouter();
$router->register('get', '/', 'HomeController@index');
$router->register('get', 'album', 'AlbumController@list');
$router->register('get', 'post', 'PostController@show');
这样一来,路由注册代码就精简了很多,结构也更加清晰,为了能够正常执行形如 HomeController@index
的路由处理器,需要在 Router
类的 dispatch
方法中对其进行解析和处理:
public function dispatch(Request $request)
{
...
if (is_callable($callback)) {
// 通过匿名函数注册的路由回调
call_user_func($callback, $request);
} elseif (is_string($callback) && strpos($callback, '@') !== FALSE) {
// 通过控制器方法注册的路由回调
list($controller, $method) = explode('@', $callback);
$controller = 'App\Http\Controller\' . $controller;
$instance = new $controller;
call_user_func([$instance, $method]);
} else {
throw new Exception('无效的路由回调');
}
}
重点关注通过控制器方法注册路由回调这段代码,首先通过 explode
函数解析出控制器名称和方法,然后加上默认命名空间前缀 AppHttpController
以便可以加载到具体的控制器类,最后,通过 call_user_func
函数执行控制器对象实例上的对应方法返回响应给客户端。
其他收尾工作
除此之外,由于我们调整了视图引入逻辑和位置,所以之前传入的 $container
变量不再可用,因此,需要将引用该变量的代码片段进行调整,具体可以参考 Github 上的项目源码。
运行 composer dump-auto
让代码修改产生的命名空间与目录路径映射调整生效,访问 http://localhost:9000
访问博客首页,可以正常访问则表示代码重构成功:
到目前为止,我们已经在项目中引入了路由器和控制器,接下来,学院君会引入模板引擎机制优化视图模板的引入和变量传递,因为目前通过简单的 include
语句这种方式维护起来很不方便,实现也不够优雅。
全文完)
- linux:nohup 不生成 nohup.out的方法
- 让VIM支持Python2 by update-alternatives
- Angular中ngCookies模块介绍
- 如何让jboss eap 6.2+ 的多个war应用共享 jar 包?
- scala 学习笔记(07) 一等公民的函数
- 让BASH,VIM美美的Powerline
- 极品三数666.cn易主,价格让人大惊!
- ui-router中使用ocLazyLoad和resolve
- rpc框架之 avro 学习 2 - 高效的序列化
- rpc框架之HA/负载均衡构架设计
- 使用Docker-Docker for Web Developers(2)
- 打造高效前端工作环境-tmuxinator
- 在Linux Mint上安装node.js和npm
- JS魔法堂:再识Number type
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- PAT (Basic Level) Practice (中文)1019 数字黑洞 (20 分)
- 从 12.9K 开源项目学到的新东西
- PAT (Basic Level) Practice (中文)1048 数字加密 (20 分)
- 一、类加载的双亲委托机制详解
- PAT (Basic Level) Practice (中文)1021 个位数统计 (15 分)
- PAT (Basic Level) Practice (中文)1049 数列的片段和 (20 分)
- PAT (Basic Level) Practice (中文)1022 D进制的A+B (20 分)
- [900]mysql字符串数字互转
- 技术分享 | Semi-join Materialization 子查询优化策略
- 前端性能和错误监控
- Network在单细胞转录组数据分析中的应用
- PAT (Basic Level) Practice (中文)1024 科学计数法 (20 分)
- PAT (Basic Level) Practice (中文)1053 住房空置率 (20 分)
- 解决vue+axios请求报错POST http: net::ERR_CONNECTION_REFUSED,在封装的请求中统一处理请求异常的问题
- PAT (Basic Level) Practice (中文)1025 反转链表 (25 分)