[php]应用控制器(一)
应用控制器负责映射请求到命令,并映射命令到视图。它允许改变应用程序的流程而不需要修改核心代码。它能把Command类解放出来,让Command类集中精力完成自己的工作,包括处理输入、调用应用程序逻辑和处理结果等。
应用控制器是一个类(或者一组类),它帮助前端控制接管处理请求的任务而且又把适当的视图返回给前端控制器调用。那么应用控制是什么方式运行的呢?它是通过一个xml配置文件来决定Command和视图工作的方式。比如下面这个xml文件(有点像struts的方式):
[html]
<?xml version="1.0" encoding="UTF-8"?>
<options>
<dsn>sqlite://data/demo.db</dsn>
<username>root</username>
<password>root</password>
<controller>
<view>main</view>
<view status="CMD_OK">main</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了。
[php]
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、视图的一一映射关系。
[php]
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。
[php]
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获取并把它当做全局数据缓存起来。
[php]
/**
* 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::instance();
if (!isset($obj->appController)) {
$cmap = $obj->getControllerMap();
$obj->appController = new \demo\controller\AppController($cmap);
}
return $obj->appController;
}
// 其它一些列getter和setter
// ......
}
这次需要实现更加复杂的调用,比如forward,那么就需要简单地修改Request类的代码了,使它能够符合调用逻辑的需要。
[php]
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 setCommand(\demo\command\Command $cmd) {
$this->lastCommand = $cmd;
}
public function getLastCommand() {
return $this->lastCommand;
}
/**
*
* @param unknown_type $name
* @param unknown_type $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两个类了。
下一篇: PHP调用小黄鸡 api post发送
推荐阅读
-
Laravel使用消息队列需要注意的一些问题_php实例
-
一道小题引出的php数据类型转换问题
-
再整理一个php 产生不重复随机数5个方法
-
php中一个header()跳转到另外一个页面后要加exit()
-
smartlmage on是什么意思 php smarty模版引擎中的缓存应用
-
40分求一小段PHP的代码,大家请进解决办法
-
phpStorm 新建一个php文件一写上东西就有红线提示Expecting Statement怎么解?
-
Codeigniter的一些优秀特性总结_php实例
-
PHP中的session不能跨页传送?一跨页就丢失,为空的
-
php把上传的图片转发到另一台服务器,求高效解法?