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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值