1.2 MVC框架内核
上一篇简单介绍了一下 MVC架构模式,网上有很多资料可以检索到。
理解了架构模式,我们如何使用php开发出符合架构模式的程序呢?
1.分层 我们通过 文件夹来实现
(1) 简单项目,可如下组织文件:
app
|-Http #HTTP MVC应用程序逻辑
| |-Controllers #控制程序逻辑
| |-Models #模型业务逻辑
resource
|-views #视图模板文件
| |- theme1 #视觉主题1
| | |- user #会员相关视图
| | | |- login.html.php #会员登录视图
| | | |- register.html.php #会员注册视图
......
如上所示,app文件夹中 我们存放 MVC应用业务源文件,有Controller和Model,但是却没有放View。
主要因为view作为前端展示,所以放到了resource/views文件夹,把前端展示和后端数据做了分离。
(2) 复杂项目,涉及多个模块,可如下组织文件:
app
|-Http #HTTP应用程序
| |-Platforms #平台模块
| | |-Controllers #控制程序逻辑
| | |-Models #模型业务逻辑
| |-Users #会员模块
| | |-Controllers #会员控制程序逻辑
| | |-Models #会员模型业务逻辑
resource
|-views #视图模板文件
| |- theme1 #视觉主题1
| | |- platform #平台模块相关视图
| | | |- login.html.php #平台登录视图
| | |- user #会员模块相关视图
| | | |- login.html.php #会员登录视图
| | | |- register.html.php #会员注册视图
......
2.定义MVC相关接口
(1) Controller层接口
<?php
namespace Lubed\MVCKernel;
interface Controller
{
//控制初始化
public function init();
//设置视图
public function setView($view);
}
(2) Model层接口
<?php
namespace Lubed\Data;
interface Model
{
}
(3) View层接口
<?php
namespace Lubed\MVCKernel;
interface View {
//载入视图模板文件
public function load(string $name);
//渲染视图
public function render();
//设置视图模板数据
public function setData(array $data);
}
3.MVC抽象
MVC相关接口实现了,我们通过抽象类来实现一些基础逻辑。下面主要介绍MVC控制层的抽象类实现。
(1) AbstractController.php
<?php
namespace Lubed\MVCKernel;
abstract class AbstractController implements Controller {
protected $request;
protected $view;
private function __clone() { }
//构造函数 接收 Lubed/Http/Request 客户端请求对象
public function __construct($request) {
$this->request=$request;
//调用抽象方法,执行控制 初始化逻辑
$this->init();
}
//抽象方法,初始化逻辑在具体应用时再实现
abstract function init();
//设置视图对象,让控制器可以通过它选择需要加载的视图模板
public function setView($view) {
$this->view=$view;
}
}
通过前面的MVC架构介绍,控制层主要是 调用Model 获取数据,给到View去展示。
因为 Model主要是 业务逻辑,业务往往都很复杂,框架和业务无关,所以我们控制器不做封装处理,主要 封装了request和view。
AbstractController会被所有的Controller继承,所以我们在实现的时候,要保持简洁。
我们实现一个 HelloController,提供一个hello方法,输出 “Hello World!”
HelloController.php
<?php
namespace App\Http\Controllers;
use Lubed\MVCKernel\AbstractController;
class HelloController extends AbstractController
{
public function hello()
{
return "\nHello World!\n";
}
}
(2) HtmlView.php
<?php
namespace Lubed\MVCKernel\Views;
use Lubed\MVCKernel\View;
class HtmlView implements View {
protected $data;
protected $suffix;
protected $layout;
/*
* $path:模板原文件及缓存的目录配置
*/
public function __construct($path,array $data=[],string $suffix='.html') {
$this->tpl=new Tpl($path,$suffix);
$this->setData( $data);
$this->suffix=$suffix;
}
public function load(string $name){
$this->layout=new Layout($this->tpl,$name);
return $this;
}
public function display(string $name, array $data=[]) {
$view=new self($name, $data,$this->suffix);
return trim($view->render(), " \r\n");
}
public function render() {
$this->tpl->setData($this->getData());
return $this->layout->render();
}
public function setData(array $data){
$this->data = $data;
return $this;
}
private function getData() {
$loaders=[
'tpl'=>$this->tpl,
'view'=>$this->layout,
];
return array_merge($loaders, $this->data);
}
}
如上 我们通过 视图(View)、布局(Layout)和Tpl(模板),实现MVC的视图层。
(3) AbstractModel.php
<?php
namespace Lubed\Data;
abstract class AbstractModel implements Model {
/**
* @var \Lubed\Data\Connection $conn
*/
private $conn;
/**
* @param $conn
*/
public function __construct() {
$registry = Registry::getInstance();
$this->conn=$registry->get('conn');
}
protected function execute(string $sql,array $params=[]):ResultSet
{
return $this->conn->execute([$sql,$params]);
}
}
如,我们新建一个 UserModel
namespace App\Http\Models;
use Lubed\Data\AbstractModel;
class UserModel extends AbstractModel {
public function deleteBy(array $where) {
// TODO: Implement deleteBy() method.
}
public function findAll() {
// TODO: Implement findAll() method.
}
public function findBy(array $where, string $order_by='', int $page=1, int $page_size=20) {
// TODO: Implement findBy() method.
}
public function findOne() {
// TODO: Implement findOne() method.
}
public function save(array $data) {
// TODO: Implement save() method.
}
}
MVC架构模式的内核程序,主要目的是 将HTTP请求分发给控制器,给控制器设置视图,并根据请求调用并执行控制器的方法,返回结果。
实现样例如下:
<?php
namespace Lubed\MVCKernel;
use Lubed\Supports\Kernel;
use Lubed\Reflections\{ReflectionFactory, ReflectionFactoryAware, DefaultReflectionFactory};
class DefaultKernel implements Kernel, ReflectionFactoryAware {
protected $is_init;
protected $is_boot;
protected $controller;
protected $action;
protected $interceptors;
protected $reflection_factory;
protected $request;
protected $view;
public function __construct() {
//未初始化
$this->is_init=false;
//未启动
$this->is_boot=false;
//设置反射
$this->setReflectionFactory(new DefaultReflectionFactory);
}
public function getRequest() {
return $this->request;
}
public function setRequest($request) : self {
$this->request=$request;
return $this;
}
public function init($callee) {
//要被调用执行的控制类
$this->controller=$callee[0] ?? '';
//要被调用执行的方法
$this->action=$callee[1] ?? '';
//TODO:将来可考虑运行前后的中间件
$this->interceptors=[];
if (!method_exists($this->controller, $this->action)) {
MVCExceptions::invalidActionHandler(sprintf('%s:No valid action handler found:%s ', __METHOD__, $this->action));
}
//已初始化
$this->is_init=true;
}
public function boot(&$result) {
if (!$this->is_boot) {
//调用执行指定控制器的特定方法
$result=$this->invokeAction($this->controller, $this->action, []);
//已启动
$this->is_boot=true;
}
}
public function setReflectionFactory(ReflectionFactory $reflection_factory) {
$this->reflection_factory=$reflection_factory;
}
//注册视图实例
public function registerView($view) {
$this->view=$view;
}
//调用执行控制器的方法
protected function invokeAction($ctl, string $method, array $arguments) {
if (!$ctl || !$method) {
MVCExceptions::invalidActionHandler("Invalid method(%s->%s).", $ctl, $method);
}
//反射 控制器的方法
$rf_method=$this->reflection_factory->getMethod($ctl, $method);
//获取控制器方法 需要的参数
$parameters=$rf_method->getParameters();
$values=[];
$total=count($parameters);
//处理参数
for ($i=0; $i < $total; $i++) {
$parameter=array_shift($parameters);
$name=$parameter->getName();
if (isset($arguments[$name])) {
$values[]=$arguments[$name];
} else if ($parameter->isOptional()) {
$values[]=$parameter->getDefaultValue();
} else {
MVCExceptions::missRequiredArgument(sprintf("Missing required argument: %s for action %s:%s", $name, $ctl, $method), ['method'=>__METHOD__]);
}
}
//创建控制器实例
$controller=new $ctl($this->request);
//执行控制器的初始化方法
$controller->init();
//设置视图
$controller->setView($this->view);
//执行控制器的方法,并返回结果
return $rf_method->invokeArgs($controller, $values);
}
}