自己动手写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(),它通过解析传入进来的类名(由于我们使用了命名空间,所以类名基本上都是“Nova\Framework\Autoloader这样的形式”),从项目根目录开始,按照类名本身指定的路径来定位相应类的源码文件,如果存在该文件,则将其引入。
更多关于自动加载类的机制和原理,可以参考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架构师],与我交流互动。