[php]前端控制器

当一个请求达到系统时,系统必须能够理解请求中的需求是什么,然后调用适当的业务逻辑进行处理,最后返回相应结果。对于简单的程序,整个过程可以放在视图中,但随着系统的增长,这种处理方式不能很好地满足请求、调用业务逻辑和显示适当视图。那么我们就需要在较大的系统中较好地管理这三者的关系,我们可以划分出视图层与命令和控制层。

视图层与命令和控制层之间的界线通常比较模糊,又是也把这两个层称为表现层。

前端控制器单一入口(流行的PHP框架大多都是单一入口),定义了一个中心入口文件,每个请求都需要从这个入口进入系统,当然也可以在这里对用户的输入请求进行过滤。前端控制器处理请求后选择适当的命令(处理业务的)。在PHP中,这种模式每次都需要初始化,所以会导致性能的下降,但好处还是显而易见的。


前端控制器处理的是请求,那么我们就需要把用户的请求进行处理,这里用类赖封装用户的请求Request。

下面是Request的代码:

namespace demo\controller;

/**
 * Request 
 * 封装用户请求
 */
class Request {
    private $properties;
	private $feedback = array();

    public function __construct() {
    	$this->init();
    	$this->filterProperties();
    	// 保存入Registry
    	\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);
    }
}
为什么Request不用使用内置的$_GET或者是$_POST等而是把封装在一起呢。这是为了能把用户的请求集中到一个地方进行统一处理,比如对请求过滤。这里还有一个好处就是可以从非HTTP请求中收集请求参数,允许应用程序在命令行或者在测试脚本中运行。


这里先来看看前端控制器的主体框架:

namespace demo\controller;

class Controller {
	private $appHelper;
	
	private function __construct() {
	
	}
	
	public static function run() {
		$instance = new self();
		// 加载配置
		$instance->init();
		// 处理请求
		$instance->handleReuqest();
	}
	
	private function init() {
		$appHelper = \demo\controller\ApplicationHelper::getInstance()->init();
	}
	
	private function handleReuqest() {
		$request = new \demo\controller\Request();
		$cmdReslover = new \demo\command\CommandReslover();
		$cmd = $cmdReslover->getCommand($request);
		$cmd->execute($request);
	}
}
只要在一个php文件中包含这个类文件,并运行就可以了。

use demo\controller\Controller;
require_once 'demo/controller/Controller.php';

Controller::run();

执行流程很简单: 1)Controller::run()中调用init(),其中的ApplicationHelper是用于读取系统配置的类,它帮助加载系统配置; 2)解析Request由CommandReslover对象的getCommand()来处理,返回一个Command子类; 3)Command子类处理业务。




下面分别是Controller中出现的类。

ApplicationHelper的代码:

namespace demo\controller;

/**
 * 助手类 获取系统配置
 * 单例
 */
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例子,注意Regisrty缓存
		$dsn = \demo\base\ApplicationRegistry::getInstance()->getDSN();
		if (is_null($dsn) == false) {
			return ;
		}
		
		$this->getOptions();
	}
	
	public function getOptions() {
                $this->ensure(file_exists($this->config), "Could not find options file!");
                $options = @simplexml_load_file($this->config);
                $this->ensure($options instanceof \SimpleXMLElement, 'Could not resolve options file!');
                $dsn = (string)$options->dsn;
                $this->ensure($dsn, 'No dsn found!');
                \demo\base\ApplicationRegistry::getInstance()->setDSN($dsn);
        
                // 其它一些获取配置的代码...
	}
	
	private function ensure($expr, $msg) {
		if (!$expr) {
			throw new \demo\base\AppException($msg);
		}
	}
}
对于PHP来说每次都需要读文件是件很费时的事情,那ApplicationHelper就需要实现缓存机制。可以通过简单地判断变量是否已经设置来决定是否需要读取文件。其实最高效的方式是直接把配置内容写到PHP文件中。


CommandSlover通过Request来决定返回哪一个Command子类(业务封装在里面)。简单的办法获取url中cmd的值来决定的。比如runner.php?cmd=AddUser,那么CommandSlover就返回AddUser。

namespace demo\command;

class CommandReslover {
	private static $baseCmd;
	private static $defaultCmd;
	
	public function __construct() {
		self::$baseCmd = new \ReflectionClass('\demo\command\Command');
		self::$defaultCmd = new DefaultCommand();
	}
	
	/**
	 * 解析请求返回命令
	 * @param \demo\controller\Request $request
	 */
	public function getCommand(\demo\controller\Request $request) {
		// cmd为url参数名称,如 runner.php?cmd=addUser
		$cmd = $request->getProperty('cmd');
		$sep = DIRECTORY_SEPARATOR;
		
		// 返回 默认的Command
		if (empty($cmd) == true) {
			return self::$defaultCmd;
		}
		
		// 
		$cmd = str_replace(array('.', $sep), '', $cmd);
		$filePath = "demo{$sep}command{$sep}{$cmd}.php";
		$className = '\demo\command\\' . $cmd;
		if (file_exists($filePath)) {
			@require_once $filePath;
			if (class_exists($className)) {
				// 判断cmd是否为Command的子类
				$cmdClass = new \ReflectionClass($className);
				if ($cmdClass->isSubclassOf(self::$baseCmd)) {
					return $cmdClass->newInstance();	
				} else {
					$request->addFeedback("Command '{$cmd}' is not a Command!");
				}
			}		
		}
		
		$request->addFeedback("Command '{$cmd}' not found!");
		return clone self::$defaultCmd;
	} 
}

Command子类主要封装业务。Command的类图:


Command抽象类代码:

namespace demo\command;

abstract class Command {
	
	// 子类不能够重写
	public final function __construct() {
	
	}
	
	public function execute(\demo\controller\Request $request) {
		$this->doExecute($request);
	}
	
	// 子类实现对应的业务
	protected abstract function doExecute(\demo\controller\Request $request);
}
DefaultCommand代码:

namespace demo\command;

class DefaultCommand extends Command {
	
	protected function doExecute(\demo\controller\Request $request) {
		$request->addFeedback('Welcome~');
		//
		include('demo/view/default.php');
		echo $request->getFeedbackString();
	}
}

现在前端控制已经能够在一个地方统一处理请求了。搭建好核心部分后就可以用最简短的代码来封装它,以后就可以重复使用了。虽然平时能够用更少的代码很简单的方法实现同样的效果,但这是为了能够加深对前端控制器的理解。

Command子类中的处理试图当时还不是最好的,能够把试图和命令分离开来效果会好一些。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值