自己动手写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架构师],与我交流互动。
phpjiagoushier

发表评论

邮箱地址不会被公开。 必填项已用*标注