【PHP开发那些事儿】1.2 MVC框架内核

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);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sunway8110

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值