自己动手写PHP框架(二)
作者:Terry Gao
上一篇提到了类的自动加载和Session,今天就来逐一说说。
1. 类的自动加载
在使用PHP的OO模式开发系统时,通常大家习惯将每个类的实现都存放在一个单独的文件里,这样会很容易实现对类进行复用,同时将来维护时也很便利,这也是OO设计的基本思想之一。如果需要使用一个类,只需要直接使用include/require将其包含进来即可。但随着项目规模的不断扩大,使用这种方式会带来一些隐含的问题:如果一个PHP文件需要使用很多其它类,那么就需要很多的require/include语句,这样有可能会造成遗漏或者包含进不必要的类文件。如果大量的文件都需要使用其它的类,那么要保证每个文件都包含正确的类文件肯定是一个噩梦。
PHP5为这个问题提供了一个解决方案,这就是类的自动装载(autoload)机制。
/* Nova\Framework\Autoloader.php */ <?php namespace Nova\Framework; class Autoloader { public static $loader; /** * Autoloader 构造函数 */ private function __construct() { //将$this->import()注册到sql_autoload,作为本项目中类的自动加载方法 spl_autoload_register(array( $this, 'import' )); } /** * Autoloader的入口函数 * 用于创建Autoloader的唯一实例化对象 * * @return Autoloader */ public static function init() { if (self::$loader == NULL) self::$loader = new self(); return self::$loader; } /** * 类的自动加载方法 * 根据传入参数$className,自动引入相应类的源文件 * * @param string $className */ public function import($className) { $path = explode('\\', substr($className, strlen('Nova'))); $filePath = ROOT_DIR . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $path) . '.php'; if (is_file($filePath)) { require $filePath; } } }
这个自动加载类比较简单,初始化后,只有一个主要的方法import(),它通过解析传入进来的类名(由于我们使用了命名空间,所以类名基本上都是“NovaFrameworkAutoloader这样的形式”),从项目根目录开始,按照类名本身指定的路径来定位相应类的源码文件,如果存在该文件,则将其引入。
更多关于自动加载类的机制和原理,可以参考PHP autoload原理
2. Session
默认情况下,PHP是将session以文件的形式存在服务器上,具体可以在php.ini中配置。但是实际生产环境中,稍大些的站点都不会采用这种形式,一般都会借助sql数据库、或者nosql类型的如memcached、redis等缓存服务器来存储session,这样做可以有效缓解PHP服务器的压力和处理速度,提高并发能力。
在Nova中我们使用redis服务器来存取Session。
/* Nova\Framework\Session.php <?php namespace Nova\Framework; class Session { private static $sessionId, $redisCache, $userIp; public function __construct() { } public static function start() { //注册Session的各种处理函数 session_set_save_handler( array(__CLASS__, "open"), array(__CLASS__, "close"), array(__CLASS__, "read"), array(__CLASS__, "write"), array(__CLASS__, "destory"), array(__CLASS__, "gc") ); session_start(); } /** * session_start时会调用该函数 * * @return bool */ public static function open() { //生成或获取一个session id self::get_sid(); //获取用于存储Session的Redis对象实例 self::$redisCache = Redis::get_instance(); return true; } /** * 使用SessionId作为key,从Redis中读取相应数据,并将数据写入Session变量 * * @return bool */ public static function read() { $sessionValue = self::$redisCache->get(self::$sessionId, SESSION_TABLE_NAME); if ($sessionValue) { $_SESSION = $sessionValue; } return true; } /** * 将Session变量的内容写入Redis中 * * @return bool */ public static function write() { if (!empty($_SESSION)) { self::$redisCache->set(self::$sessionId, $_SESSION, SESSION_TABLE_NAME, SESSION_TIMEOUT); } return true; } /** * 通过删除Redis中SessionId对应的数据来注销Session * session_destory()是自动调用 * * @return bool */ public static function destory() { if (self::$redisCache->exists(self::$sessionId, SESSION_TABLE_NAME)) { self::$redisCache->delete(self::$sessionId, SESSION_TABLE_NAME); } setcookie(SESSION_NAME, self::$sessionId, 1, COOKIE_PATH, COOKIE_DOMAIN, FALSE); return true; } public static function close() { return true; } public static function gc() { return true; } /** * 返回一个SessionId * 若Cookie中已存在SessionId,则直接返回该SessionId * 若不存在,则按照规则新生成一个SessionId * * @return string Session Id */ public static function get_sid() { self::$userIp = Tools::real_ip(); $arr = $_COOKIE; //判断Cookie中是否已经存在SessionId if (is_null(self::$sessionId) && empty($arr[SESSION_NAME])) { //使用MD5对用户IP+随机字符串加密后作为新的SessionId self::$sessionId = function_exists('com_create_guid') ? md5(self::$userIp . com_create_guid()) : md5(self::$userIp . uniqid(mt_rand(), true)); //对新的SessionId再做一次crc32运算,作为最终的SessionId self::$sessionId .= sprintf('%08x', crc32(self::$sessionId)); //将SessionId写入Cookie中 setcookie(SESSION_NAME, self::$sessionId, time() + SESSION_TIMEOUT, COOKIE_PATH, COOKIE_DOMAIN, FALSE); $_COOKIE[SESSION_NAME] = self::$sessionId; } else { self::$sessionId = $arr[SESSION_NAME]; } //返回SessionId return self::$sessionId; } }
Nova基本上重写了Session的一些核心处理函数。为了方便使用自定义的全局Redis Rootkey,Nova把Redis方法也重写了。
<?php namespace Nova\Framework; class Redis extends \Redis { private static $_instanceObj; public $groupName = REDIS_ROOT; private $tempName = "temp:"; private $_redis; private $groupPath = REDIS_ROOT; public function __construct() { $this->_redis = new \Redis(); $this->_redis->connect(REDIS_HOST, REDIS_PORT); } public static function get_instance($redisKey = REDIS_ROOT) { if (!(self::$_instanceObj[$redisKey] instanceof self)) { self::$_instanceObj[$redisKey] = new self; } self::$_instanceObj[$redisKey]->redisKey = $redisKey; return self::$_instanceObj[$redisKey]; } public function set_group($groupName = "") { if (empty($groupName)) { return FLASE; } $this->groupName = $groupName; $this->groupPath = implode(":", explode("/", $groupName)) . ":"; return TRUE; } public function set($key, $data, $groupName = "", $timeout = SESSION_TIMEOUT) { if (empty($groupName)) { $groupName = $this->groupName . $this->tempName; } else { $groupName = $this->groupName . $groupName; } if (is_array($data)) { $data = json_encode($data); } $redisKey = $groupName . $key; return $this->_redis->setex($redisKey, $timeout, $data); } public function get($key, $groupName = "") { if (empty($groupName)) { $groupName = $this->groupName . $this->tempName; } else { $groupName = $this->groupName . $groupName; } $redisKey = $groupName . $key; $return = ""; $temp = $this->_redis->get($redisKey); $return = json_decode($temp, 1); return empty($return) ? $temp : $return; } public function delete($key, $groupName = "") { if (empty($groupName)) { $groupName = $this->groupName . $this->tempName; } else { $groupName = $this->groupName . $groupName; } $redisKey = $groupName . $key; return $this->_redis->delete($redisKey); } public function exists($key, $groupName = "") { if (empty($groupName)) { $groupName = $this->groupName . $this->tempName; } else { $groupName = $this->groupName . $groupName; } $redisKey = $groupName . $key; return $this->_redis->exists($redisKey); } }
你可以在Github上查看Nova项目的源代码。
如果你有任何问题或建议,可以扫描下方二维码或者微信搜索[phpjiagoushier],关注我的微信公众号[PHP架构],与我交流互动。