论坛首页 编程语言技术论坛

简易MVC组件[改自Qee v3 beta] -- 从5.3到5.2的向下兼容

浏览 2565 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2011-10-12  
PHP

前两天得到1份 QEEPHP v3的测试版本... 新功能确实很好,而且变小了很多,虽然离正式版的出现还有些距离,但是已经能看出

很多的改变,比如 事件机制,实体操作层的改变,MVC的处理改变 等等....

 

此处 因为之前的 CoreApp-mini 已经写了很多,其里面并没有对模块实现进行限制,可以使用多种方式来实现...

 

之前 缺少MVC的完整实现,此处正好把QEE 的MVC模式引过来....

 

因为 v3里面C处 没有 模块/命名空间/控制器/动作 这么一说 只有 action 但是 action 是可以存在 以 . 分隔的 多级

所以 现有的qee2.1的 url 路由 可以 修剪下 就能 放到 qee v3版本里面的MVC 中,
将 UDI 转成 .分隔的actionName 应该很好做到
同理 actionName 到UDI应该也好实现
主要就是在 这2个的转换上面 

因为 CoreApp-mini 里面已经有了个正向的url路由,所以也就没有引入qee的路由...

CoreApp-mini 的定位就是 简单,够用, 小系统的专用框架,大的复杂的项目可以使用 QEEPHP来实现

先上图:


再上代码:


<?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);
    }
}
 
  • 大小: 72 KB
  • 大小: 36.7 KB
  • 大小: 10.9 KB
  • 大小: 21.1 KB
  • 大小: 31.3 KB
  • 大小: 8.7 KB
   发表时间:2011-10-12  
有时候我真觉得PHP需要搞个框架吗?
0 请登录后投票
   发表时间:2011-11-11  
osacar 写道
有时候我真觉得PHP需要搞个框架吗?


+1
0 请登录后投票
   发表时间:2011-11-21  
osacar 写道
有时候我真觉得PHP需要搞个框架吗?

我也有同感,尤其是看到很多成功的PHP程序,如wordpress等甚至连MVC结构都没有用。
0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics