【深入PHP 面向对象】读书笔记(十九)

来源:互联网 发布:b50板球座子淘宝 编辑:程序博客网 时间:2024/06/15 16:05

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_INSUFFICIENT_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;    }}

这样一个基本的应用控制器就建立起来了,系统的响应由配置文件决定。

阅读全文
0 0
原创粉丝点击