您的位置:首页 > 编程语言 > PHP开发

【深入PHP 面向对象】读书笔记(十九) - 企业模式(四) - 应用控制器

2017-11-23 01:13 891 查看

12.3.2 应用控制器

应用控制器负责映射请求到命令,并映射命令到视图;使得 Command 类能够集中精力完成包括处理输入、调用应用程序逻辑和处理结果等,而不需要对视图进行调用处理。



我们围绕这张图,来看模式中的参与者(应用控制器、命令和视图)之间的通信过程。

首先是前端控制器 FrontController 使用 AppController 应用控制器接口:

//前端控制器
function handleRequest() {
$request = new Request();
$app_c = ApplicationRegistry::appController();

while ($cmd = $app_c->getCommand($request)) {
$cmd->execute();
}
$this->invokeView($request);
}

function invokeView($target) {
include '$target.php';
exit;
}


handleRequest() 方法使用 ApplicationRegistry 注册表类的 appController() 静态方法来获取一个 AppController 应用控制器对象,并通过 AppController 应用控制器对象的 getCommand() 方法来获取命令,并通过 Command 对象的 execute() 方法来执行命令。

实现概述

针对不同的情况,我们需要加载不同的页面,比如对于一个数据输入表单:如果用户添加错误类型的数据,页面可能会重新显示表单或者显示错误页;如果用户数据填写正确,页面就需要跳转到其他页面。

因此,我们需要在 Command 命令对象中定义一个状态标识来告诉系统当前状态,比如是指示用户填写数据错误,还是用户填写数据合法。这个状态标识定义成一个数组,存储在 Command 超类中:

private static $STATUS_STRINGS = array(
'CMD_DEFAULT' => 0,
'CMD_OK' => 1,
'CMD_ERROR' => 2,
'CMD_INSUF
4000
FICIENT_DATA' =>3
);


配置文件

使用 XML 格式的配置文件:

<control>
<view>main</view>
<view status="CMD_OK">main</view>
<view status="CMD_ERROR">Error</view>

<command name="ListVenues">
<view>listvenues</view>
</command>

<command name="QuickAddVenue">
<classroot name="AddVenue"/>
<view>quickadd</view>
</command>

<command name="AddVenue">
<view>addvenue</view>
<status value="CMD_OK">
<forward>AddSpace</forward>
</status>
</command>

<command name="AddSpace">
<view>addspace</view>
<status value="CMD_OK">
<forward>ListVenues</forward>
</status>
</command>
</control>


<view>main</view>
<view status="CMD_OK">main</view>
<view status="CMD_ERROR">Error</view>


第一个 view 元素是所有命令的默认视图,如果没有指定调用特定的视图,则此处定义的默认视图会被调用。接下来两个 view 元素制定了更为具体的内容,根据命令的状态来调用相应的视图。

<command name="ListVenues">
<view>listvenues</view>
</command>


command 元素用于根据指定的命令来显示视图。

<command name="QuickAddVenue">
<classroot name="AddVenue"/>
<view>quickadd</view>
</command>


同时配置文件还支持别名,通过别名来指定加载其他命令,来实现加载视图。

<command name="AddVenue">
<view>addvenue</view>
<status value="CMD_OK">
<forward>AddSpace</forward>
</status>
</command>


此外,命令可以加载多个视图,并且支持通过命令的状态来确定是否加载指定视图。

解析配置文件

定义一个 ApplicationHelper 来实现解析 XML 配置文件,XML 的解析是一个烦琐的工作,具体的解析这里不详细说,只给出大致的解析过程,通过 SimpleXML 解析:

private function getOptions() {
$this->ensure(file_exists($this->config),'could not found options file');
$options = @simplexml_load_file($this->config);

$map = new ControllerMap();

foreach ($options->control->view as $default_view) {
$stat_str = trim($default_view['status']);
$status = Command::statuses($stat_str);
$map->addView('default',$status,$default_view);
}

ApplicationRegistry::setControllerMap($map);
}


存储配置数据

ControllerMap 内封装了3个数组,用于缓存从XML中解析出的数据:

class ControllerMap {
private $viewMap = array();
private $forwardMap = array();
private $classrootMap = array();

function addClassroot($command, $classroot) {
$this->classrootMap[$command] = $classroot;
}

function getClassroot($command) {
if (isset($this->classrootMap[$command])) {
return $this->classrootMap[$command];
}
return $command;
}

function addView($command='default', $status=0, $view) {
$this->viewMap[$command][$status] = $view;
}

function getView($command, $status) {
if (isset($this->viewMap[$command][$status])) {
return $this->viewMap[$command][$status];
}
return null;
}

function addForward($command='default', $status=0, $newCommand) {
$this->forwardMap[$command][$status] = $newCommand;
}

function getForward($command, $status) {
if (isset($this->forwardMap[$command][$status])) {
return $this->forwardMap[$command][$status];
}
return null;
}
}


例如这样一段 XML 配置文件:

<command name="AddVenue">
<view>addvenue</view>
<status value="CMD_OK">
<forward>AddSpace</forward>
</status>
</command>


在调用进行缓存时,会执行下面的命令:

$map->addView('AddVenue', 0, 'addvenue');
$map->addView('AddVenue', 1, 'AddSpace');


在 $viewMap 中存储:

$viewMap['AddVenue'][0] = 'addvenue';
$viewMap['AddVenue'][1] = 'AddSpace';


接下来时应用控制器类,实现根据相应的命令来调用相应的视图:

class AppController {

private static $base_cmd;
private static $default_cmd;
private $controllerMap;
private $invoked = array();

function __contruct(ControllerMap $map) {
$this->controllerMap = $map;
if (!self::$base_cmd) {
self::$base_cmd = new ReflectionClass("Command");
self::$default_cmd = new DefaultCommand();
}
}

function getView(Request $req) {
$view = $this->getResource($req, 'View');
return $view;
}

function getForward(Request $req)  {
$forward = $this->getResource($req, 'Forward');
if ($forward) {
$req->setProperty()
}
return $forward;
}

/* getResource() 方法执行查找工作,用于转向(getForward())或选择视图(getView())*/
private function getResource(Request $req, $res) {
// 得到前一个命令及其状态
$cmd_str = $req->getProperty('cmd');
$previous = $req->getLastCommand();
$status = $previous->getStatus();
if (!$status) {
$status = 0;
}
$acquire = "get$res";
// 得到前一个命令的资源及其状态

$resource = $this->controllerMap->$acquire($cmd_str, $status);
// 如果没有查找到指定命令的资源 则查找状态为0的资源
if(!$resource) {
$resource = $this->controllerMap->$acquire($cmd_str, 0);
}

// 如果状态为0的资源也找不到的话,查找默认状态资源
if (!$resource) {
$resource = $this->controllerMap->$acquire('default', $status);
}

// 其他情况获取'default'失败,状态为0
if (!$resource) {
$resource = $this->controllerMap->$acquire('default', 0);
}

return $resource;
}

/* getCommand() 方法负责返回转向中需要使用的所有命令。*/
function getCommand(Request $req) {
$previous = $req->getLastCommand();

if (!$previous) {
// 这是本次请求调用的第一个命令
$cmd = $req->getProperty('cmd');
if (!$cmd) {
// 如果无法得到命令 则使用默认命令
$req->setProperty('cmd', 'default');
return self::$default_cmd;
}
} else {
// 之前已经执行过一个命令
$cmd = $this->getForward($req);
if (!$cmd) {
return null;
}
}

// 在$cmd变量中保存着命令名称,并将其解析为Command对象
$cmd_obj = $this->resolveCommand($cmd);
if (!$cmd_obj) {
throw new AppException("could not resolve $cmd");
}

$cmd_class = get_class($cmd_obj);
if (isset($this->invoked[$cmd_class])) {
throw new AppException("circular forwarding");
}

$this->invoked[$cmd_class] = 1;
return $cmd_obj;
}

function resolveCommand($cmd) {
$classname = $this->controllerMap->getClassroot($cmd);
$filepath = 'woo/command/$classroot.php';
if (file_exists($filepath)) {
require_once($filepath);
if (class_exists($classname)) {
$cmd_class = new ReflectionClass($classname);
if ($cmd_class->isSubClassOf(self::$base_cmd)) {
return $cmd_class->newInstance();
}
}
}

return null;
}

}


getResource() 方法执行查找工作,用于转向(getForward())或选择视图(getView()),它会优先查找最具体的字符串和状态标识的组合,然后才搜索通用的组合。

getCommand() 方法负责返回转向中需要使用的所有命令。

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;

final function __contruct(){}

/*execute() 方法使用抽象方法 doExecute() 返回的值来设置状态标识,并将它保存在 Request 对象中。*/
function execute(Request $req) {
$this->status = $this->doExecute($req);
$req->setCommand($this);
}

/*getStatus() 用于当前的状态标识*/
function getStatus() {
return $this->status;
}

/*statuses() 方法用于将字符串状态转换成相应的数字*/
static function statuses($str='CMD_DEFAULT'){
return self::$STATUS_STRINGS[$str];
}

abstract function doExecute(Request $req);
}


Command 类定义了一个状态字符串数组 $STATUS_STRINGS。statuses() 方法用于将字符串状态转换成相应的数字,getStatus() 用于当前的状态标识,execute() 方法使用抽象方法 doExecute() 返回的值来设置状态标识,并将它保存在 Request 对象中。

一个具体的 Command 类:

class AddVenue extends Command {
function doExecute(Request $req) {
$name = $req->getProperty('venue_name');
if (!$name) {
$request->addFeedback('no name provided');
return self::statuses('CMD_INSUFFICIENT_DATA');
} else {
$venue_obj = new Venue(null, $name);
$request->setObject('venue', $venue_obj);
$request->addFeedback('$name added({$venue_obj->getId()})');
return self::statuses('CMD_OK');
}
}
}


具体的 venue 类:

class Venue {
private $id;
private $name;

function __construct($id, $name) {
$this->id = $id;
$this->name = $name;
}

function getName() {
return $this->name;
}
function getId() {
return $this->id;
}
}


这样一个基本的应用控制器就建立起来了,系统的响应由配置文件决定。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐