前端控制器已经能很好地在一个地方集中处理请求并选择适当的Command了,但是Command子类对象自己处理了视图的分配工作。要是能够使用一个类(根据Command处理后返回的状态值)来决定视图并返回到前端控制器,再由前端控制器来调用视图显示,这样的话前端控制器就处于视图层和业务层的中间了,而且也很好地把Command和视图分开了。应用控制器是个好的解决方案。
应用控制器负责映射请求到命令,并映射命令到视图。它允许改变应用程序的流程而不需要修改核心代码。它能把Command类解放出来,让Command类集中精力完成自己的工作,包括处理输入、调用应用程序逻辑和处理结果等。
先来看看顺序图吧。
应用控制器是一个类(或者一组类),它帮助前端控制接管处理请求的任务而且又把适当的视图返回给前端控制器调用。那么应用控制是什么方式运行的呢?它是通过一个xml配置文件来决定Command和视图工作的方式。比如下面这个xml文件(有点像struts的方式):
<?xml version="1.0" encoding="UTF-8"?>
<options>
<dsn>sqlite://data/demo.db</dsn>
<username>root</username>
<password>root</password>
<controller>
<view>default</view>
<view status="CMD_OK">success</view>
<view status="CMD_ERROR">error</view>
<command name="ListStudents">
<view>list_students</view>
</command>
<command name="AddStudent">
<view>add_student</view>
<status value="CMD_OK">
<forward>ListStudents</forward>
</status>
</command>
<command name="SimpleAddStudent">
<classalias name="AddStudent"/>
<view>simple_add_student</view>
</command>
</controller>
</options>
可以看到xml中<command>中可以包含<view>、<status>、<forward>三类子元素,分别表示的是Command对应的视图、Command处理业务后的状态值、Command处理后的跳转(这里跳转到另一个Command)。
从xml的结构就能了解到Command类需要一些新的属性status了。
namespace demo\command;
/**
* 抽象父类
*/
abstract class Command {
// 状态值映射
private static $STATUS_STRINGS = array(
'CMD_DEFAULT' => 0,
'CMD_OK' => 1,
'CMD_ERROR' => 2,
'CMD_INSUFFICIENT_DATA' => 3
);
// 当前状态值
private $status = 0;
public final function __construct() {
// 子类不能重写构造函数
}
/**
* 按状态字符串返回状态值
* @param unknown_type $staStr
*/
public static function status($stauStr = 'CMD_DEFAULT') {
if (empty($stauStr)) {
$stauStr = 'CMD_DEFAULT';
}
return self::$STATUS_STRINGS[$stauStr];
}
/**
* 调用子类实现的doExecute
* @param \demo\controller\Request $request
*/
public function execute(\demo\controller\Request $request) {
$this->doExecute($request);
}
protected abstract function doExecute(\demo\controller\Request $request);
}
系统中有个专门获取配置的助手类ApplicationHelper可以实现对xml配置的读取。由于xml中的元素结构相对灵活一些,那么就需要一个ControllerMap来管理各元素中的值和Command、视图的一一映射关系。
namespace demo\controller;
class ControllerMap {
private $classrootMap = array();
private $forwardMap = array();
private $viewMap = array();
public function addClassroot($cmd, $classroot) {
$this->classrootMap[$cmd] = $classroot;
}
public function getClassroot($cmd) {
if (isset($this->classrootMap[$cmd])) {
return $this->classrootMap[$cmd];
}
return $cmd;
}
public function addForward($cmd = 'default', $status = 0, $newCmd) {
$this->forwardMap[$cmd][$status] = $newCmd;
}
public function getForward($cmd, $status) {
if (isset($this->forwardMap[$cmd][$status])) {
return $this->forwardMap[$cmd][$status];
}
return null;
}
public function addView($cmd = 'default', $status = 0, $view) {
$this->viewMap[$cmd][$status] = $view;
}
public function getView($cmd, $status) {
if (isset($this->viewMap[$cmd][$status])) {
return $this->viewMap[$cmd][$status];
}
return null;
}
}
首先需要获取xml中的配置ApplicationHelper类的getOptions。
namespace demo\controller;
/**
* 助手类:获取xml配置
* 单例模式
*
*/
class ApplicationHelper {
private static $instance;
private $config = 'data/config.xml';
private function __construct() {
}
public static function getInstance() {
if (isset(self::$instance) == false) {
self::$instance = new self();
}
return self::$instance;
}
public function init() {
// 初始化配置从序列化文件中获取
$dsn = \demo\base\ApplicationRegistry::getInstance()->getDSN();
$camp = \demo\base\ApplicationRegistry::getInstance()->getControllerMap();
if (is_null($dsn) || is_null($camp)) {
$this->getOptions();
}
}
/**
* 获取xml配置
*/
public function getOptions() {
// xml
$this->ensure(file_exists($this->config), "Could not find options file!");
$options = @simplexml_load_file($this->config);
var_dump($options);
$this->ensure($options instanceof \SimpleXMLElement, 'Could not resolve options file!');
// <dsn>
$dsn = (string)$options->dsn;
$this->ensure($dsn, 'No dsn found!');
\demo\base\ApplicationRegistry::getInstance()->setDSN($dsn);
// <controller>
$map = new ControllerMap();
// <view>
foreach ($options->controller->view as $default_view) {
$stauStr = trim((string)$default_view['status']);
$status = \demo\command\Command::status($stauStr);
$map->addView('default', $status, (string)$default_view);
}
// <command>
foreach ($options->controller->command as $cvf) {
$cmd = trim((string)$cvf['name']);
// <classalias>
if($cvf->classalias) {
$classroot = (string)$cvf->classalias['name'];
$map->addClassroot($cmd, $classroot);
}
// <view>、<forward>
if ($cvf->view) {
$view = trim((string)$cvf->view);
$forward = trim((string)$cvf->forward);
$map->addView($cmd, 0, $view);
if ($forward) {
$map->addForward($cmd, 0, $forward);
}
}
// <status>
foreach ($cvf->status as $status) {
$stauStr = trim((string)$status['value']);
$view = trim((string)$status->view);
$forward = trim((string)$status->forward);
$stau = \demo\command\Command::status($stauStr);
if ($view) {
$map->addView($cmd, $stau, $view);
}
if ($forward) {
$map->addForward($cmd, $stau, $forward);
}
}
}
// var_dump($map);
\demo\base\ApplicationRegistry::getInstance()->setControllerMap($map);
}
private function ensure($expr, $msg) {
if (!$expr) {
throw new \demo\base\AppException($msg);
}
}
}
获取xml配置的过程是一个比较费时的操作,可以先把ControllerMap对象序列化到文件中去,之后可以通过ApplicationRegistry获取并把它当做全局数据缓存起来。
/**
* Application作用域
*/
class ApplicationRegistry extends Registry {
private static $instance;
private $freezedir = "./data"; // 此处硬编码,具体根据实际情况配置
private $values = array();
private $mtimes = array();
private function __construct() {
}
public static function getInstance() {
if (isset(self::$instance) == false) {
self::$instance = new self();
}
return self::$instance;
}
/**
* 从序列化文件中获取$key数据
*/
protected function get($key) {
$path = $this->freezedir . DIRECTORY_SEPARATOR . $key;
if (file_exists($path)) {
// 清楚文件缓存
clearstatcache();
$mtime = filemtime($path);
if (isset($this->mtimes[$key]) == false) {
$this->mtimes[$key]=0;
}
// 文件最近被修改过,重新反序列化新的数据
if ($mtime > $this->mtimes[$key] ) {
$data = file_get_contents($path);
$this->mtimes[$key] = $mtime;
return ($this->values[$key] = unserialize($data));
}
}
if (isset( $this->values[$key]) == true) {
return $this->values[$key];
}
return null;
}
protected function set($key, $val) {
$this->values[$key] = $val;
$path = $this->freezedir . DIRECTORY_SEPARATOR . $key;
if (file_exists($path)) {
touch($path);
}
file_put_contents($path, serialize($val));
$this->mtimes[$key]=time();
}
public function getDSN() {
if (isset($this->values['dsn'])) {
return $this->values['dsn'];
}
return self::getInstance()->get('dsn');
}
public function setDSN($dsn) {
return self::getInstance()->set('dsn', $dsn);
}
/**
*
* @param \demo\controller\ControllerMap $map
*/
public function setControllerMap(\demo\controller\ControllerMap $map) {
self::getInstance()->set('cmap', $map);
}
public function getControllerMap() {
if (isset($this->values['cmap'])) {
return $this->values['cmap'];
}
return self::getInstance()->get('cmap');
}
/**
* 获取AppController
*/
public function getAppController() {
$obj = self::getInstance();
if (!isset($obj->appController)) {
$cmap = $obj->getControllerMap();
$obj->appController = new \demo\controller\AppController($cmap);
}
return $obj->appController;
}
// 其它一些列getter和setter
// ......
}
这次需要实现更加复杂的调用,比如forward,那么就需要简单地修改Request类的代码了,使它能够符合调用逻辑的需要。
namespace demo\controller;
/**
* 封装用户请求
* Request
*/
class Request {
private $properties;
private $feedback = array();
// 保存业务对象,可以供给视图使用
private $objects = array();
// 保存上一个已执行的Command对象
private $lastCommand;
public function __construct() {
$this->init();
$this->filterProperties();
\demo\base\RequestRegistry::getInstance()->setRequest($this);
}
public function __clone() {
$this->properties = array();
}
public function init() {
if (isset($_SERVER['REQUEST_METHOD'])) {
if ($_SERVER['REQUEST_METHOD']) {
$this->properties = $_REQUEST;
return ;
}
}
// 命令行下的方式
foreach ($_SERVER['argv'] as $arg) {
if (strpos($arg, '=')) {
list($key, $val) = explode('=', $arg);
$this->setProperties($key, $val);
}
}
}
public function filterProperties() {
// 过滤用户请求...
}
public function getProperty($key) {
return $this->properties[$key];
}
public function setProperties($key, $val) {
$this->properties[$key] = $val;
}
public function getFeedback() {
return $feedback;
}
public function addFeedback($msg) {
array_push($this->feedback, $msg);
}
public function getFeedbackString($separator = '\n') {
return implode('\n', $this->feedback);
}
/**
*
* @param \demo\command\Command $cmd
*/
public function setLastCommand(\demo\command\Command $cmd) {
$this->lastCommand = $cmd;
}
public function getLastCommand() {
return $this->lastCommand;
}
/**
*
* @param string $name
* @param Domain $object
*/
public function setObject($name, $object) {
$this->objects[$name] = $object;
}
public function getObject($name) {
if (isset($this->objects[$name])) {
return $this->objects[$name];
}
return null;
}
}
现在已经添加了能够保存映射关系的类ControllerMap,修改了Request、Command、ApplicationHelper、ApplicationRegistry,主要是添加,少量修改之前的代码。
上面这些类已经能够完成系统的初始化了,包括读取xml配置(ApplicationHelper)、全局变量访问(Registry);
之后就是核心部分了:Controller和AppController两个类了。
分享到:
相关推荐
ThinkPHP 的应用开发非常关键,本文以控制器的用法为主线,通过十讲的内容全面剖析了 ThinkPHP5.0 生命周期中的控制器角色是如何进行获取请求、数据验证、业务处理、异常处理、模板渲染,以及如何进行响应输出和行为...
本文实例讲述了TP(thinkPHP)框架多层控制器和多级控制器的使用。...给App应用添加多层控制器,不需添加任何参数,只需按照如上的方式直接建文件即可。 多层控制器实例化: /* * 多层控制器实例化 即和访问控制器Co
PHP100视频教程36:PHP中正则表达式学习及应用(一) PHP100视频教程37:PHP中正则表达式学习及应用(二) PHP100视频教程38:PHP中正则表达式学习及应用(三) PHP100视频教程39:PHP中正则表达式学习及应用(四...
前端控制器 PHP 示例 - 音乐播放器 演示项目,展示了实现前端控制器模式和为 API 创建自己的微框架是多么容易。 当前项目模仿音乐播放器和 Spotify API 提供的音乐搜索。技术决策我决定实现前端控制器模式来封装典型...
一个PHP框架,用于使用MVC(模型-视图-控制器)设计模式构建Web应用程序。 执照 该项目为开源项目,可根据。 作者 约翰·罗宾逊( Robin 要求 PHP> = 7.2.0 JSON PHP扩展 PDO PHP扩展(可选,取决于使用的功能) ...
在这本书中,你可以学习如何使用 PHP 编程语言结合 MVC 设计模式来构建现代化的 Web 应用程序。...控制器和业务逻辑: 介绍控制器层的作用和功能,以及如何在控制器中实现业务逻辑、处理用户输入等。
基于空气监测, 开发基于STM32微控制器设计一个室内空气质量监测报警终端,实现室内二氧化碳、光照度、温湿度、粉尘浓度的参数监测及超标报警。 单片机设计,工具源码,适合毕业设计、课程设计作业,所有源码均经过...
具体来说,控制器从应用主体接管控制后会分析请求数据并传送到模型, 传送模型结果到视图,最后生成输出响应信息。 操作 控制器由 操作 组成,它是执行终端用户请求的最基础的单元,一个控制器可有一个或多个操作。...
LightPHP目录: apps-应用层 -Action-控制器 -Model-模型 core-框架核心 -config.php -配置文件 -action.php -路由文件 -common.php -公共方法 cache-缓存目录,框架暂时支持file缓存 index.php-单入口
36:PHP中正则表达式学习及应用(一) 37:PHP中正则表达式学习及应用(二) 38:PHP中正则表达式学习及应用(三) 39:PHP中正则表达式学习及应用(四) 40:PHP中开发自己的-UBB代码 41:PHP站内搜索、多关键字、...
控制器中包含了你应用程序需要创建响应的抽象逻辑。 接收请求,返回响应的基本生命周期 1、每个请求都被单个前端控制器(如app.php或index.php)文件处理,前端控制器负责引导框架; 2、路由查看并匹配请求信息,并...
比如:AdminBase 为应用后台的公用的控制器,在每一个应用后台控制器里面都来继承公共的AdminBase ,但是同时要确保AdminBase 也是继承CI_Controller的。 前台HomeBase也是同样的道理。 具体实现很简单,只要在...
PHP-MVC骨架 ...这是一个普通的MVC应用程序,因此包含模型/视图/控制器 首先,它运行启动脚本 启动脚本加载主脚本和配置文件。 然后创建一个新的应用程序实例,并在类中解析该URL。 在类中,设置
这是一个简单的 MVC 框架,用于在 PHP 中构建 Web 应用程序。 它是免费和。 它是为“课程而创建的。 本课程解释了如何将框架组合在一起,从头开始逐步构建它。 如果您参加过该课程,那么您将已经知道如何使用它。 ...
基于ruff物联网操作系统、微软语音控制、青云云平台开发出一款致力于智能家居控制的手机应用,User只需要通过一部简单的手机应用、一句简单的语音指令,就可以远程控制家庭的电器运行状态;在实现中通过模拟展示出了...
使用MVC应用程序被分成三个核心部件:模型、视图、控制器。它们各自处理自己的任务。2、为什么要使用 MVC首先,最重要的一点是多个视图能共享一个模型,现在需要用越来越多的方式来访问你的应用程序。由于模型返回的...
YY框架的控制器和其他所有框架的控制器均不相同,其他框架的一个控制器就是一个类文件,而歪歪框架的控制器是实际存在的一个php过程脚本。 之所以没有沿用其他框架采用类文件的形式,是因为通常的一个URL请求用到的...
尊重\休息 用于RESTful应用程序和API的瘦控制器。 非常轻薄。 不要尝试更改PHP,这是一个很小的学习过程。 完全RESTful,这是构建应用程序的正确方法。 目录
这是一个样板化的Silex应用程序,它设置了使用控制器,服务,路由,模型和正确配置的合理方法-您希望它在Symfony之类的框架中工作的方式。 使用方法: 安装apache,php,composer 克隆存储库 设置从存储库到公共...