前两天得到1份 QEEPHP v3的测试版本... 新功能确实很好,而且变小了很多,虽然离正式版的出现还有些距离,但是已经能看出
很多的改变,比如 事件机制,实体操作层的改变,MVC的处理改变 等等....
此处 因为之前的 CoreApp-mini 已经写了很多,其里面并没有对模块实现进行限制,可以使用多种方式来实现...
之前 缺少MVC的完整实现,此处正好把QEE 的MVC模式引过来....
<?php /* * 简易MVC组件[改自Qee v3 beta] * -- 带特定的目录结构 * -- 不再有控制器这个物件 * -- by sese coreapp-mini */ /** * 视图对象 */ class MvcView { /** * 视图文件所在目录 * * @var string */ public $view_dir; /** * 视图默认使用的布局 * * @var string */ public $view_layout; /** * 默认使用的视图 * * @var string */ public $viewname; /** * 视图变量 * * @var array */ public $vars; /** * 构造函数 * * @param string $view_dir * @param string $viewname * @param array $vars */ function __construct($view_dir, $viewname, array $vars) { $this->view_dir = $view_dir; $this->vars = $vars; $this->vars['_BASE_DIR'] = get_request_dir(); $this->viewname = $viewname; } /** * 渲染一个视图文件,返回结果 * * @return string */ function execute() { $viewname = $this->viewname; $child = new MvcViewLayer($this, $viewname); $error_reporting = ini_get('error_reporting'); error_reporting($error_reporting & ~E_NOTICE); $child->parse(); $layer = $child; while (($parent = $layer->parent) != null) { $parent->parse($layer->blocks); $layer = $parent; } error_reporting($error_reporting); return $child->root()->contents; } /** * 查找指定视图文件 * * @param string $viewname * * @return string */ function view_filename($viewname) { $filename = str_replace('.', DIRECTORY_SEPARATOR, $viewname) . '.php'; return $this->view_dir . DIRECTORY_SEPARATOR . $filename; } } /** * 视图层 */ class MvcViewLayer { /** * 该层所属的视图对象 * * @var MvcView */ public $view; /** * 父层对象 * * @var MvcViewLayer */ public $parent; /** * 视图名称 * * @var string */ public $viewname; /** * 该层的内容 * * @var string */ public $contents; /** * 该层区块的内容 * * @var array */ public $blocks = array(); /** * 该层的区块 * * @var array */ private $_block_stack = array(); /** * 预定义的区块 * * @var array */ private $_predefined_blocks = array(); /** * 构造函数 * * @param MvcView $view * @param string $viewname */ function __construct(MvcView $view, $viewname) { $this->view = $view; $this->viewname = $viewname; } /** * 返回该层的顶级层(最底层的视图) * * @return MvcViewLayer */ function root() { return ($this->parent) ? $this->parent->root() : $this; } /** * 分析视图,并返回结果 * * @param array $predefined_blocks */ function parse(array $predefined_blocks = array()) { $this->_predefined_blocks = $predefined_blocks; ob_start(); extract($this->view->vars); include $this->view->view_filename($this->viewname); $this->contents = ob_get_clean(); $this->_predefined_blocks = null; foreach ($this->blocks as $block_name => $contents) { $search = "%_view_block.{$block_name}_%"; if (strpos($this->contents, $search) !== false) { $this->contents = str_replace($search, $contents, $this->contents); } } } /** * 从指定层继承 * * @param string $viewname */ function extend($viewname) { $this->parent = new MvcViewLayer($this->view, $viewname); } /** * 定义一个区块 * * @param string $block_name * @param boolean $append */ function block($block_name, $append = false) { array_push($this->_block_stack, array($block_name, $append)); ob_start(); } /** * 结束最后定义的一个区块 */ function endblock() { list($block_name, $append) = array_pop($this->_block_stack); $contents = ob_get_clean(); $this->_create_block($contents, $block_name, $append); } /** * 定义一个空区块 * * @param string $block_name * @param boolean $append */ function empty_block($block_name, $append = false) { $this->_create_block('', $block_name, $append); } /** * 载入一个视图片段 * * @param string $viewname 视图片段名 */ function element($viewname) { $__filename = $this->view->view_filename("_elements/{$viewname}"); extract($this->view->vars); include $__filename; } /** * 完成一个区块 * * @param string $contents * @param string $block_name * @param boolean $append */ private function _create_block($contents, $block_name, $append) { if (isset($this->_predefined_blocks[$block_name])) { if ($append) { $contents .= $this->_predefined_blocks[$block_name]; } else { $contents = $this->_predefined_blocks[$block_name]; } } $this->blocks[$block_name] = $contents; echo "%_view_block.{$block_name}_%"; } } /** * MvcAction没有找到 * */ class MvcActionMissingException extends Exception { /** * 动作名 * * @var string */ public $actionName; /** * 构造函数 * * @param string $actionName */ function __construct($actionName){ $this->actionName = $actionName; parent::__construct(sprintf('缺少Action "%s".',$actionName)); } } /** * 动作对象基础类 */ abstract class MvcBaseAction { /** * 应用程序对象 * * @var MvcAppEntry */ public $app; /** * 动作名称 * * @var string */ public $name; /** * 当前请求 * * @var MvcRequest */ public $request; /** * 执行结果 * * @var mixed */ public $result; /** * 构造函数 * * @param MvcAppEntry $app * @param string $name */ function __construct($app, $name) { $this->app = $app; $this->name = $name; $this->request = $app->request; } /** * UDI 转 MVC Action 标识 */ static function formatUDIAction(array $udi){ return sprintf('%s.%s.%s',$udi[Router::module],$udi[Router::operate],$udi[Router::action]); } /** * 执行动作 */ function __execute() { if (!$this->__before_execute()) return; if ($this->validate_input()) { $result = $this->execute(); if (!is_null($result)) $this->result = $result; if (!$this->validate_output()) { $this->on_validate_output_failed(); } } else { $this->on_validate_input_failed(); } $this->__after_execute(); } /** * 执行指定的视图对象 * * @param array $vars */ function view(array $vars = null) { if (!is_array($vars)) $vars = array(); $this->result = $this->app->view($this->name, $vars); } /** * 应用程序执行的动作内容,在继承的动作对象中必须实现此方法 * * 返回值会被保存到动作对象的 $result 属性中。 * * @return mixed */ abstract function execute(); /** * 继承类覆盖此方法,用于在执行请求前过滤并验证输入数据 * * 如果返回 false 则阻止调用 execute() 方法,并调用 validate_input_failed() 方法。 * * @return bool */ function validate_input() { return true; } /** * 继承类覆盖此方法,用于在执行请求后过滤并验证输出数据 * * 如果返回 false 则调用 validate_output_failed() 方法。 * * @return bool */ function validate_output() { return true; } /** * 请求前对数据进行验证失败时调用此方法 */ function on_validate_input_failed() { } /** * 请求执行后对数据进行验证失败时调用此方法 */ function on_validate_output_failed() { } /** * 执行动作之前调用,如果返回 false 则阻止动作的执行 * * @return bool */ protected function __before_execute() { return true; } /** * 执行动作之后调用 */ protected function __after_execute() { } } /** * 封装一个请求 */ class MvcRequest { /** * GET 数据 * * @var array */ public $get; /** * POST 数据 * * @var array */ public $post; /** * COOKIE 数据 * * @var array */ public $cookie; /** * SESSION 数据 * * @var array */ public $session; function __construct($get, $post, $cookie, $session) { $this->get = $get; $this->post = $post; $this->cookie = $cookie; $this->session = $session; } /** * 从 GET 取得数据,如果指定数据不存在则返回 $default 指定的默认值 * * @param string $name * @param mixed $default * * @return mixed */ function get($name, $default = null) { return isset($this->get[$name]) ? $this->get[$name] : $default; } /** * 从 POST 取得数据,如果指定数据不存在则返回 $default 指定的默认值 * * @param string $name * @param mixed $default * * @return mixed */ function post($name, $default = null) { return isset($this->post[$name]) ? $this->post[$name] : $default; } /** * 从 COOKIE 取得数据,如果指定数据不存在则返回 $default 指定的默认值 * * @param string $name * @param mixed $default * * @return mixed */ function cookie($name, $default = null) { return isset($this->cookie[$name]) ? $this->cookie[$name] : $default; } /** * 从 SESSION 取得数据,如果指定数据不存在则返回 $default 指定的默认值 * * @param string $name * @param mixed $default * * @return mixed */ function session($name, $default = null) { return isset($this->session[$name]) ? $this->session[$name] : $default; } } /** * 用与保存和读取应用程序设置的工具类 */ class MvcConfig { /** * 应用程序设置 * * @var array */ protected $_config = array(); /** * 导入设置 * * @param array $config */ function import(array $config) { $this->_config = array_merge($this->_config, $config); } /** * 读取指定的设置,如果不存在则返回$default参数指定的默认值 * * @param string $item * @param mixed $default * @param bool $found * * @return mixed */ function get($item, $default = null, & $found = false) { if (is_array($item)) { $found = false; foreach ($item as $key) { $return = $this->get($key, $default, $found); if ($found) return $return; } return $default; } if (strpos($item, '/') === false) { $found = array_key_exists($item, $this->_config); return $found ? $this->_config[$item] : $default; } list($keys, $last) = self::_get_nested_keys($item); $config =& $this->_config; foreach ($keys as $key) { if (array_key_exists($key, $config)) { $config =& $config[$key]; } else { return $default; } } $found = array_key_exists($last, $config); return $found ? $config[$last] : $default; } /** * 修改指定的设置 * * @param string $item * @param mixed $value */ function set($item, $value) { if (strpos($item, '/') === false) { $this->_config[$item] = $value; } list($keys, $last) = self::_get_nested_keys($item); $config =& $this->_config; foreach ($keys as $key) { if (!array_key_exists($key, $config)) { $config[$key] = array(); } $config =& $config[$key]; } $config[$last] = $value; } static private function _get_nested_keys($key) { $keys = normalize($key, '/'); $last = array_pop($keys); return array($keys, $last); } } /** * MvcAppEntry 类封装了一个基本的应用程序对象 * * 如果需要定制应用程序对象,开发者可以从 MvcAppEntry 派生自己的继承类。 * 在coreapp-mini 中 每个 MvcAppEntry 对应一个内属的模块 */ class MvcAppEntry { /** * 当前请求 * * @var MvcRequest */ public $request; /** * 当前MvcAppEntry 对应的设置对象 * * @var MvcConfig */ public $config; /** * 应用程序基本路径 * * @var string */ private $_base_path; /** * 应用模块的标识 * * @var string */ private $_app_id; /** * 工具对象集合 * * @var array */ private $_tools_instance = array(); /** * 应用程序实例对象集合 * * @var array */ private static $_instances = array(); /** * 构造函数 * * 参数 $config 包含应用模块的设置,必须包含如下4个键 * * app.id: 应用模块的标识,如 pwadmin * app.base_path: 应用模块相对于根目录的基准路径,如 /modules/pwadmin * app.defentry: 应用模块的初始执行入口 * app.creator: 应用模块的创建者,如 色色 * * @param array $config * @param bool $set_instance 是否增加到实例对象列表,是则可以通过 instance(app_id)返回MvcAppEntry对象 */ function __construct(array $config, $set_instance = true) { $must_opt = array( 'app.id', 'app.base_path', 'app.defentry', 'app.creator', ); foreach ($must_opt as $opt){ if (isset($config[$opt]) && !empty($config[$opt])) continue; throw new Exception(sprintf('需要的键 "%s" 没有找到.', $opt)); } $this->config = new MvcConfig(); $this->config->import($config); $this->_app_id = $this->config->get('app.id'); $this->_base_path = rtrim($this->config->get('app.base_path'), '/\\'); $autoload_tools = $this->config->get('app.autoload_tools',null); if ($autoload_tools){ $autoload_tools = normalize($autoload_tools); foreach ($autoload_tools as $name) { $tool = $this->tool($name); if (method_exists($tool, 'autorun')) $tool->autorun(); } } $this->request = new MvcRequest($_GET, $_POST, isset($_COOKIE) ? $_COOKIE : array(), isset($_SESSION) ? $_SESSION : array()); if ($set_instance) { self::$_instances[strtolower(trim($this->_app_id))] = $this; } } /** * 取得应用程序实例 * * @param string $app_id * * @return MvcAppEntry */ static function instance($app_id) { if (!empty($app_id)){ $app_id = strtolower(trim($this->_app_id)); if ( isset(self::$_instances[$app_id]) && (self::$_instances[$app_id] instanceof MvcAppEntry) ) return self::$_instances[$app_id]; } return null; } /** * 返回应用程序根目录 * * @return string */ function base_path() { return $this->_base_ath; } /** * 返回应用模块的标识 * * @return string */ function app_id() { return $this->_app_id; } /** * 执行应用程序 * * @param string $action_name 这个应该是 将 udi 转换之后的名称 * * @return mixed */ function run($action_name = null) { // 解析请求 URL 中的动作名称 if (is_null($action_name)) { $action_name = $this->config->get('app.defentry'); } $action_name = self::_format_action_name($action_name); // 动作对象 $action_class_name = str_replace('.', '_', $action_name) . 'Action'; $action_class_file = $this->_base_path . '/actions/' . str_replace('.', '/', $action_name) . '.php'; CoreApp::import($action_class_file,true); if (!class_exists($action_class_name,false)) { return $this->_process_result($this->_on_action_not_found($action_class_name)); } // 执行动作 $action = new $action_class_name($this, $action_name); /* @var $action BaseAction */ $action->__execute(); return $this->_process_result($action->result); } /** * 生成 URL * * @param string $url * @param array $params * * @return string */ function url($url, array $params = null) { return url($url,$params); } /** * 取得指定的视图对象 * * @param string $viewname * @param array $vars * * @return View */ function view($viewname, array $vars) { return new View($this->_base_ath . '/views', $viewname, $vars); } /** * 根据 tools 设定创建并返回指定的工具对象 * * @param string $toolname * * @return object */ function tool($toolname) { if (!isset($this->_tools_instance[$toolname])) { $tool_config = $this->config->get("app.tools/{$toolname}"); if (is_array($tool_config)) { $class = $tool_config['class']; $class_file = $tool_config['file']; } else { if (is_string($tool_config) && !empty($tool_config)) { $class = $tool_config; } else { $class = ucfirst($toolname) . 'Tool'; } $file = $this->_base_path . '/tools/' . $class . '.php'; if (!is_array($tool_config)) $tool_config = array(); } CoreApp::import($file,true); $this->_tools_instance[$toolname] = new $class($this, $tool_config); } return $this->_tools_instance[$toolname]; } /** * 确定指定的工具对象是否存在 * * @param string $toolName * * @return bool */ function has_tool($toolName) { return isset($this->_tools[$toolName]); } /** * 处理动作对象的执行结果 * * @param mixed $result */ protected function _process_result($result) { $charset = $this->config->get('app.output_charset', 'utf-8'); if (is_object($result) && method_exists($result, 'execute')) { if (!headers_sent()) { header('X-Powered-By-CoreAppMini: ' . CoreApp::VER); header("Content-Type: text/html; charset={$charset}"); } return $result->execute(); } elseif (is_string($result)) { if (!headers_sent()) { header('X-Powered-By-CoreAppMini: ' . CoreApp::VER); header("Content-Type: text/html; charset={$charset}"); } return $result; } else { return $result; } } /** * 指定的控制器或动作没有找到 * * @param string $action_name */ protected function _on_action_not_found($action_name) { throw new MvcActionMissingException($action_name); } /** * 格式化动作名称 * * @param string $action_name * * @return string */ protected static function _format_action_name($action_name) { $action_name = strtolower($action_name); if (strpos($action_name, '.') !== false) { $action_name = preg_replace('/\.+/', '.', $action_name); } $action_name = trim($action_name, ". \t\r\n\0\x0B"); return preg_replace('/[^a-z\.]/', '', $action_name); } }