一个 PHP MVC 的实现

一切见诸代码, 注释应该是无声的.....

<?php
/**
 * PtAction base class file
 *
 * PHP Version PHP 5
 *
 * LICENSE: The version 3.0 of PHP License.
 *
 * @author Changsheng Jiang <pointer@tom.com>
 */
 
define('PT_BASE_ROOT', dirname(__FILE__).DIRECTORY_SEPARATOR);

 
require_once 'func.lib.php';
 
/**
 * PtConfig
 *
 * The base configuration class with some static function for convenient.
 */
class PtConfig {
 
    /**
     * Get the static properties, this e.g can be served as a class
     * configuration.
     * 
     * @param string $name component name, usually a class name
     * @param string $var  the variable name
     * @return mixed the property
     */

    public static function &getStaticProperty($name, $var) {
        static $properties;
        return $properties[$name][$var];
    }

 
    public static function setStaticProperty($configs) {
        foreach ($configs as $class=>$config) {

            foreach ($config as $k=>$v) {
                $config = & self::getStaticProperty($class, $k);
                $config = $v;
            }

        }
    }
}
 
/**
 * PtAppConfiguration
 *
 * A module/application configuration class
 */
class PtAppConfiguration extends PtConfig {

 
}
 
/**
 * PtUser
 *
 */
class PtUser extends PtComponent {
    /**
     * @var  array $userData
     * @access protected
     */

    protected  $userData;
 
    var $id;
    var $username = '';
    var $password = '';
    var $privilege = -1;

 
    /**
     * set user data form array, the array must has the key
     * 'username', 'password', 'privilege' seted.
     *
     * @param array $ua
     * @access public
     */
    public function fromArray($ua) {
        foreach ($ua as $key=>$value) {

            $this->userData[$key]=$value;
        }
        $this->username = getValue($this->userData, 'username', '');
        $this->password = getValue($this->userData, 'password', '');
        $this->privilege = getValue($this->userData, 'privilege', 0);
        $this->id = getValue($this->userData, 'id', 0);

 
    }
 
    /**
     * Load user data from session
     *
     * @access public
     * @return bool true if session has user data.
     */
    public function getSession() {

        if(isset($_SESSION['user'])) {
            $this->fromArray($_SESSION['user']);
            return true;
        }

        return false;
    }
 
    /**
     * Return user data as a array
     */
    public function toArray() {

        return $this->userData;
    }
 
    /**
     * set the session
     */
    public function setSession() {

        foreach($this->userData as $key=>$value) {
            $_SESSION['user'][$key]=$value;
        }

    }
 
    /**
     * check is login, get session auto.
     */
    public function isLogin() {

        if ($this->privilege < 0) {
            $this->getSession();
        }

        return $this->privilege > 0;
    }
 
    /**
     * login, abstract function ...
     */
    public function login() {

 
    }
 
    /**
     * logout, reset the session
     */
    public function logout() {

        unset($_SESSION['user']);
        $this->privilege=0;    
    }

 
    /**
     * check user has the privilege do admin
     */
    public function isCanAdmin() {
        $level = PtConfig::getStaticProperty(get_class($this), 'admin_privilege');
        if (!$level) $level = 10000;
        return $this->privilege >= $level;
    }

 
    /**
     * check if user is is root.
     */
    public function isRoot() {
        $level = PtConfig::getStaticProperty(get_class($this), 'root_privilege');
        if (!$level) $level = 30000;
        return $this->privilege >= $level;
    }

}
 
/**
 * PtComponent
 *
 * The PtComponent class is the base class of all classes which has
 * prefix Pt.
 *
 * All common feature of MVC should be place here.
 * 
 */
class PtComponent
{
 
    /**
     * toggle debug message out or not.
     *
     * @access public
     * @var    integer
     */
    public $debugging = 0;

 
    /**
     * The number for generating a unique id
     *
     * @access private
     * @var    integer
     * @see    getUniqueId
     */
    private static $_uniqueid =0;
 
    /**
     * The component unique id
     * 
     * @access public
     * @var string
     */

    public $componentId = "";
 
    /**
     * The parameters about the instance.
     *
     * @access protected
     * @var    array
     * @see    getParam, setParam
     */
    protected $params = array();

 
    /**
     * Return a unique id as component
     *
     * @access public
     * @return integer
     */
    public static function getUniqueId(){
        return self::$_uniqueid++;
    }

 
 
    /**
     * Get Parameter by name with default if the parameter is not
     * seted.
     *
     * @access public
     * @param  string $name the key of the parameter
     * @param  mixed  $default the default value if parameter is not seted.
     * @return mixed
     */
    public function getParam($name, $default = false) {

        return isset($this->params[$name])
            ? $this->params[$name] : $default;
    }

 
    /**
     * Unset the parameter
     *
     * @access public
     * @param  string $name the key of the parameter
     */
    public function unsetParam($name) {
        unset($this->params[$name]);
    }

 
    /**
     * Set a parameter
     *
     * @access public
     * @param string $name the key of the parameter
     * @param mixed  $value the value of the parameter
     */
    public function setParam($name, $value ) {

        unset($this->params[$name]);//unset first to prevent type overload
        $this->params[$name] = $value;
    }

 
    /**
     * Set parameters
     *
     * @access public
     * @param array $params the parameters be seted
     */
    public function setParams($params) {
        if (is_array($params)) {

            $this->params = array_merge($this->params, $params);
        }

        throw new Exception("inner error: invalid argument in setParams");
    }
 
    /**
     * Get the component name
     *
     * @access public
     * @return string
     */
    function getComponentName() {

        if (is_object($this->module) && !empty($this->module->name))

            return $this->module->name . ':' . get_class( $this );
        else

            return get_class( $this );
    }
 
    /**
     * Initialize
     * @access public
     */
    public function initialize() {

 
    }
 
    /**
     * dispose function as the module dispose.
     *
     * @access public
     */
    public function dispose() {

 
    }
 
    /**
     * debuge message, check debugging level
     *
     * @access public
     */
    public function  debugMsg($message, $level=0) {

        if ($this->debugging &&
            $this->debugging > $level ) {

            echo $message;
        }
    }
}
 
/**
 * PtModel
 *
 * Abstract base class of all Model, generally this will just empty,
 * dispatch the call to really DB.
 */
abstract class PtModel extends PtComponent {

}
 
/**
 * PtAction
 *
 * Abstract base class of all Command/Action.
 * A subclass must overload the execute function.
 */
abstract class PtAction extends PtComponent {
 
    /**
     * data for a view to show
     *
     * @var array
     * @access protected
     * @see    setViewData
     */

    protected $view_data;
 
    /**
     * the action module
     *
     * @var   mixed
     * @access public
     */
    var       $module;
 
    /**
     * check is the request method post
     *
     * @access public
     */

    public function isPost() {
        return( isset( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] === 'POST' );
    }

 
    /**
     * Main function, action take here.
     *
     * @access public
     */
    public function run($action) {
        $this->setViewData('curlevel', $this->module->getActionLevel($action));
        $this->setViewData('isLogin', $this->module->user->isLogin());
        $this->setViewData('isRoot', $this->module->user->isRoot());
        $this->setViewData('user', $this->module->user->toArray());
    }

 
    public function addViewData($key, $value) {
        if (is_array($value)) {

            $this->view_data[$key]=array_merge($this->view_data[$key], $value);
        } else {

            $this->view_data[$key]=$value;
        }
    }
 
    public function  setViewData($key, $value) {

        $this->view_data[$key]=$value;
    }
 
    public function  getViewData($key, $args) {

        if(isset($this->view_data[$key])) {
            $data=$this->view_data[$key];
        } else {

            return null;
        }
        foreach ($args as $k=>$v) {

            if(is_integer($k)) {
                if (isset($data[$v])) {

                    $data = $data[$v];
                } else {
                    return null;
                }

            } else {
                throw new Exception("try to get view data with not supported parameters");
            }

        }
        return $data;
    }
 
    /**
     * The privilege level required to take action
     *
     * @access public
     */
    public function  getLevel() {

        if (isset($this->_level)) {
            return $this->_level;
        } else {

            $level = PtConfig::getStaticProperty(get_class($this),'level');
            if ($level !== null ) {

                return $level;
            }
        }
        return 0;// default level, should this need to get from PtConfig?

    }   
}
 
abstract class PtView extends PtComponent {
 
    /**
     * The action of the view for
     *
     * @access public
     */

    var $action;
    var $module;
 
    /**
     * the view id, use to cache
     */
    var $viewId;

 
    /**
     * get view data
     */
    public function getData() {
        $args = func_get_args();
        $key = array_shift($args);
        return $this->action->getViewData($key, $args);
    }

 
    /**
     * get view data with default value
     *
     */
    public function getDataDefault() {
        $args = func_get_args();
        $key = array_shift($args);
        $default = array_shift($args);
        $data=$this->action->getViewData($key, $args);
        if (isset($data)) {

            return $data;
        } else {
            return $default;
        }

    }
 
    /**
     * the main function to display the view
     */
     public function display () {

 
    }
}
 
/**
 * PtController
 *
 * base class of all controller.
 */
class PtController extends PtComponent {

 
    /**
     * The content filters.
     *
     * @access protected
     * @var    array
     */
    protected $_filters = array();
    protected $options;
 
    /**
     * register filters
     *
     * @access public
     * @param  mixed  $filter  the filter
     */

    function registerFilter(&$filter) {
        $this->_filters[] =& $filter;
    }

 
    /**
     * The main and door funciton of a controller
     * This follow to process which take module run and which then take action run
     */
    public function run() {
        foreach ($this->_filters as $filter) {

            $filter->preFilter();
        }
        try {
            $this->process();
        } catch (Exception $e) {

            echo $e->getMessage();
        }        
        foreach (array_reverse($this->_filters) as $filter) {

            $filter->postFilter();
        }
    }
 
    /**
     * The main proces function
     */
    public function process() {

        $options =& PtConfig::getStaticProperty(get_class($this), 'options');
        $do = getValue($options, 'modaction_url', 'do');
        $action = getValue($_REQUEST, $do, '');
        if (strlen($action) == 0) {

            $action = PtConfig::getStaticProperty(get_class($this), 'default_action');
        }
        if (strpos($action, ':') != false) {

            list( $moduleName, $remaction ) = explode(':', $action);
        } else {

            $moduleName=getValue($options, "default_module", "PtModule");
            $remaction=$action;
        }

        $module = &$this->loadModule($moduleName);
        $module->initialize();
        $module->run($remaction);
    }

 
    /**
     * load module by module name use config or default
     */
    protected function loadModule($moduleName) {
        $map = &PtConfig::getStaticProperty(get_class($this), 'module_map');
        $moduleName = getValue($map, $moduleName, $moduleName);
        $module =& PtModule::factory($moduleName);
        $module->supcontroller = &$this;
        return $module;
    }

 
    /**
     * abort, the more action can be done such as components dispose.
     */
    public function abort() {//do some more to abort.
        exit();
    }

 
    /**
     * redirect to other url, then abort
     */
    public function redirect( $url ) {

        header( 'Location: ' . $url );
        $this->abort();
    }

 
    /**
     * get full url by module:action
     */
    public function getUrl($modaction) {
        $options = PtConfig::getStaticProperty(get_class($this), 'options');
        $do = getValue($options, 'modaction_url', 'do');
        return  'http://'.$_SERVER['SERVER_NAME'].$_SERVER["SCRIPT_NAME"] . '?'.$do

            .'='.$modaction;
    }
 
    /**
     * redirect to do a module:action
     */
    public function transfer($modaction) {

        $options = PtConfig::getStaticProperty(get_class($this), 'options');
        $do = getValue($options, 'modaction_url', 'do');
        $this->redirect($this->getUrl($modaction));
    }

 
}
 
 
/**
 * PtModule
 *
 * Base class of all Module.
 */
class PtModule extends PtController { // yes, a module is a controller!

 
    /**
     * The controller which call this module.
     *
     * @access public
     * @var    mixed  Controller
     */
    var $supcontroller = null;
 
    /**
     * The module name
     */
    var $name;

 
    var $user;
 
    /**
     * check action's privilege and current user's privilege
     */
    public function isCanRun($action) {

        $level = $this->getActionLevel($action);
        $ulevel = $this->getCurLevel();
        if ($ulevel < 0 || $level < 0) return false;
        if ($ulevel >= $level ) {

            return true;
        } else {
            return false;
        }

    }
 
    /**
     * get action privilege level
     */
    public function getActionLevel($action) {

        $levels = PtConfig::getStaticProperty(get_class($this), 'levels');
        if ( !isset($levels) ) {

            $level = 0;
        } else {
            $level = getValue($levels, $action, 0);
        }

        return $level;
    }
 
    public function getCurLevel() {

        if (isset($_SESSION['USER'])) {
            $ulevel = getValue($_SESSION['USER'], 'level', 0);
        } else {

            $ulevel = 0;
        }
        return $ulevel;
    }
 
    public function isAuthed($action) {

        if ($this->isCanRun($action)) {
            return true;
        }

        return false;
    }
 
    /**
     * The main function to process action as module part.
     *
     * First load check auth, load action, initialize it, and take
     * action run, then from the result get the proper view by config
     * or default, display the view's content
     *
     * @param string $action
     */
    public function run($action) {

        $user = new PtUser();
        $user->privilege = 0;
        $user->getSession();
        $this->user=&$user;
        if (strlen($action) == 0) {

            $action = PtConfig::getStaticProperty(get_class($this), 'default_action');
        }
        if (strlen($action) == 0) {

            throw new Exception("nothing to do, please set 'default_action'");
        }
        if (!$this->isAuthed($action)) {

            throw new Exception('not authed, failed at moudle.');
        }
        $this->controller = &$this->supcontroller;// if not reset by command initialize

        $command = &$this->loadAction($action);
        $command->initialize();
        $result = $command->run($action);
        if (strlen($result) == 0) {

            $result = 'success';
        }
        $view = &$this->loadView($command, $action, $result);
        $view->initialize();
        $view->display();
        //var_dump($_SESSION);

    }
 
    /**
     * load action class
     */
    public function loadAction($action) {

        $map = &PtConfig::getStaticProperty(get_class($this), 'action_map');
        if(isset($map[$action])) {

            $action = getValue($map[$action], 'action', $action);
        } 
        $command =& $this->doLoadAction($action);
        $command->module = &$this;
        return $command;
    }

 
    protected function doLoadAction($action) {
        $options = &PtConfig::getStaticProperty(get_class($this), 'options');
        $actionName = getValue($options, 'action_name_prefix', 'Action_').$action;
        $actiondir = getValue($options, 'action_dir',
                              'module'.DIRECTORY_SEPARATOR.$this->name

                              .DIRECTORY_SEPARATOR.'action'.DIRECTORY_SEPARATOR);
        $actionbasedir = getValue($options, 'action_base_dir', PT_BASE_ROOT);
        if (class_exists($actionName, false) ||
            @include_once $actionbasedir.$actiondir.$actionName.".php") {

            if (class_exists($actionName, false)) {
                if(!is_subclass_of($actionName, 'PtAction')){

                    throw new Exception("'$action' not a valid action");
                }
                $command=new $actionName;
                return $command;
            } else {

                throw new Exception("action '$action's Class not found");
            }
        } else {

            throw new Exception("action '$action's file not found");
        }
    }
 
 
    /**
     * load view class
     */

     public function loadView(&$command, $action, $result) {
        $actionmap = &PtConfig::getStaticProperty(get_class($this), 'action_map');
        $viewName = $action;
        if (isset($actionmap[$action])) {

            if (isset($actionmap[$action]['view'][$result]) ) {

                $viewName = $actionmap[$action]['view'][$result];
            } else {

                $viewName = $actionmape[$action];
            }
        }
        $viewName = getValue($options, 'view_name_prefix', 'View_').$viewName;
        $viewdir = getValue($options, 'view_dir',
                            'module'.DIRECTORY_SEPARATOR.$this->name

                            .DIRECTORY_SEPARATOR.'view'.DIRECTORY_SEPARATOR);
        $viewbasedir = getValue($options, 'view_base_dir', PT_BASE_ROOT);
        if (class_exists($viewName, false) ||
            @include_once $viewbasedir.$viewdir.$viewName.".php") {

            if (class_exists($viewName, false)) {
                if (!is_subclass_of($viewName, 'PtView')) {

                    throw new Exception("action '$action' with result '$result' has not a valid view");
                }
                $view=new $viewName;
            } else {

                throw new Exception("view '$viewName's Class not found");
            }
        } else {

            throw new Exception("view '$viewName's file not found");
        }
        $view->action=&$command;
        $view->module=&$this;
        return $view;
    }

 
    /**
     * Singleton function, get the singleton instance. Usually this
     * should be called after factory has load the class file.
     * 
     * @access public
     * @return PtModule a instance of PtModule
     */
    public static function &singleton($className, $name){

        if (!class_exists($className, false)) {
            throw new Exception("'$className' not a valid module or not found.");
        }

        if ($className!=__CLASS__ && !is_subclass_of($className, __CLASS__)){

            throw new Exception("class '$className' not a module");
        }
        if (!$name) {

            $name=$className;
        }
        if (isset($GLOBALS['_PtMoulde_SINGLETON'][$name])) {

            if ($GLOBALS['_PtMoulde_SINGLETON'][$name] instanceof $className) {

                return $GLOBALS['_PtMoulde_SINGLETON'][$name];
            } else {

                throw new Exception("the module $name is not a $className");
            }
        }
        $module=new $className;
        $GLOBALS['_PtMoulde_SINGLETON'][$name]=&$module;
        $module->name=$name;
        return $module;
    }

 
    public function __clone() {
        trigger_error('Moulde Clone is not allowed.', E_USER_ERROR);
    }

 
    /**
     * Try to load the $name module, and return a instance.  A
     * sub-module who wants to has it's own factory, should overload
     * this function, since the __CLASS__ is hardcoded.
     *
     * @access public
     * @param  string $name
     * @param  mixed  $param
     * @return PtModule a instance of PtModule if success, or throw PtInvalidModuleException
     */
    public static function factory($name) {

        if($name == __CLASS__) {
            return self::singleton($name, $name);
        }

        $options = &PtConfig::getStaticProperty(__CLASS__, "options");
        $moduledir = getValue($options, 'module_dir', 'module'.DIRECTORY_SEPARATOR);
        $modulebasedir = getValue($options, 'module_base_dir', PT_BASE_ROOT);
        $className = getValue($options, 'class_name_prefix', "Module_").$name;
        $moduledir = $modulebasedir.$moduledir;
        return self::_in_factory($name, $className, $moduledir);
    }

 
    protected static function  _in_factory($name, $className, $moduledir) {

        if (class_exists($className, false) ||
            @include_once $moduledir.$name.".php"){

            $module= &self::singleton($className, $name);
        } else {

            throw new Exception("Module '$name's file '${moduledir}${name}' not found");
        }
        return $module;
    }

}
 
?>

No comments: