CodeIgniter架構源碼筆記(13)——SESSION之檔案File驅動實現__架構

來源:互聯網
上載者:User

CI的檔案驅動要滿足以下三個條件:
1、驅動要實現open ,read ,write ,close ,destory ,gc六個方法。
session_start()時,調用了open(),read()方法。並有一定機率觸發gc()方法。
session_commit()或session_write_close()時,觸發write(),close()方法。
session_destory()會觸發desotry()方法。

這六個方法實現的功能如下:
open:擷取並建立檔案儲存的路徑
read: 根據session_id讀取或建立session_id對應的檔案,擷取檔案操作指標並加鎖flock
write:session內容變更時,將內容寫入session_id對應的檔案。該寫入是全量寫。session_data的內容包括新增的session索引值對以及已經存在的索引值對。
close:釋放(flock)檔案鎖,並關閉(fclose)檔案指標。
destory:刪除服務端session_id對應的檔案,並清除用戶端對應session_id的cookie資訊
gc:判斷檔案是否到期:根據檔案最後一次修改時間(filemtime())和目前時間對比判斷檔案對應的session是否到期。該方法在session_start時一定機率調用。

2、驅動要支援session_regenerate_id()。
該方法重建一個新的session_id並建立以此session_id命名的檔案。然後將原來老的session_id檔案中儲存的內容拷入新的檔案中。最後刪除老session_id所在的檔案。

3、驅動要實現session鎖:這裡採用檔案鎖方式。

實現:檔案驅動

class CI_Session_files_driver extends CI_Session_driver implements SessionHandlerInterface {    //檔案儲存路徑    protected $_save_path;    //檔案操作控制代碼    protected $_file_handle;    //檔案名稱    protected $_file_path;    //是否新檔案標識    protected $_file_new;    // ------------------------------------------------------------------------    //建構函式    public function __construct(&$params)    {        parent::__construct($params);        //初始設定檔案儲存路徑,根據設定檔設定php.ini中的session.save_path選項        if (isset($this->_config['save_path']))        {            $this->_config['save_path'] = rtrim($this->_config['save_path'], '/\\');            //根據設定檔設定php.ini中的session.save_path選項            ini_set('session.save_path', $this->_config['save_path']);        }        else        {            //如果設定檔中的$config['sess_save_path']不存在,則使用當前ini中預設的路徑            $this->_config['save_path'] = rtrim(ini_get('session.save_path'), '/\\');        }    }    // ------------------------------------------------------------------------    //open方法    //第一個參數$save_path對應的是ini_get('session.save_path')    //第二個參數$name對應的是ini_get('session.name')    public function open($save_path, $name)    {        //如果檔案中徑不存在,嘗試建立        if ( ! is_dir($save_path))        {            if ( ! mkdir($save_path, 0700, TRUE))            {                //如果無法建立目錄,                throw new Exception("Session: Configured save path '".$this->_config['save_path']."' is not a directory, doesn't exist or cannot be created.");            }        }        //如果目錄不可寫,拋出異常        elseif ( ! is_writable($save_path))        {            throw new Exception("Session: Configured save path '".$this->_config['save_path']."' is not writable by the PHP process.");        }        //組建檔案儲存的路徑,這裡給檔案儲存目錄加上一點料,避免衝突        $this->_config['save_path'] = $save_path;        $this->_file_path = $this->_config['save_path'].DIRECTORY_SEPARATOR            .$name // we'll use the session cookie name as a prefix to avoid collisions            .($this->_config['match_ip'] ? md5($_SERVER['REMOTE_ADDR']) : '');        return $this->_success;    }    // ------------------------------------------------------------------------    //read    //參數$session_id對應的是session_id()的值    public function read($session_id)    {        //如果session_id()對應的檔案操作指標為空白        if ($this->_file_handle === NULL)        {            // Just using fopen() with 'c+b' mode would be perfect, but it is only            // available since PHP 5.2.6 and we have to set permissions for new files,            // so we'd have to hack around this ...            //建議開啟檔案時使用c+模式,因為該模式當檔案存在時不會刪除檔案原有內容 (w+模式下會清空原檔案內容)            //但是該模式只有PHP 5.2.6後有效,所以我們不得不根據檔案是否存在而做不同的操作。            //檔案不存在就用'w+'模式,檔案存在就用'r+'模式            //當請求的Session檔案不存在,則採用'w+b'讀寫入模式建立檔案,擷取操作控制代碼            if (($this->_file_new = ! file_exists($this->_file_path.$session_id)) === TRUE)            {                //採用'w+b'模式開啟檔案,擷取操作控制代碼                if (($this->_file_handle = fopen($this->_file_path.$session_id, 'w+b')) === FALSE)                {                    //如果未能建立成功,則返回失敗                    log_message('error', "Session: File '".$this->_file_path.$session_id."' doesn't exist and cannot be created.");                    return $this->_failure;                }            }            //如果請求的Session檔案存在,則採用'r+b'讀寫入模式開啟檔案,擷取操作控制代碼            elseif (($this->_file_handle = fopen($this->_file_path.$session_id, 'r+b')) === FALSE)            {                //如果未能讀取成功,則返回失敗                log_message('error', "Session: Unable to open file '".$this->_file_path.$session_id."'.");                return $this->_failure;            }            //至此已成功擷取檔案指標,並賦給$this->_file_handle            //鎖定檔案指標對像$this->_file_handle(LOCK_EX是獨佔鎖定)            //注意:釋放鎖(LOCK_UN)放在了close()函數中            if (flock($this->_file_handle, LOCK_EX) === FALSE)            {                //沒鎖定成功,記錄日誌,釋放檔案指標,然後返回失敗.                log_message('error', "Session: Unable to obtain lock for file '".$this->_file_path.$session_id."'.");                fclose($this->_file_handle);                $this->_file_handle = NULL;                return $this->_failure;            }            // Needed by write() to detect session_regenerate_id() calls            //將$session_id賦給對像的屬性$this->_session_id            //在session_regenerate_id()更改sessionid後,在write方法中用得到,這裡儲存了老的sessionid            $this->_session_id = $session_id;            //如果是新產生的檔案,則設定檔案許可權600            if ($this->_file_new)            {                //只給讀寫權限,沒有執行許可權                chmod($this->_file_path.$session_id, 0600);                $this->_fingerprint = md5('');//摘要是Null 字元的md5                return '';            }        }        // We shouldn't need this, but apparently we do ...        // See https://github.com/bcit-ci/CodeIgniter/issues/4039        //如果$this->_file_handle === FALSE,則返回失敗。        //這是git上一叫aanbar的小哥發現的,然後補上了$this->_file_handle === FALSE這個判斷,        //因為fopen成功時返迴文件指標,如果開啟失敗返回 FALSE        elseif ($this->_file_handle === FALSE)        {            return $this->_failure;        }        else        {            //如果指標不為空白            //將檔案內部offset指標重新指向開頭            rewind($this->_file_handle);        }        $session_data = '';        //讀取內容        for ($read = 0, $length = filesize($this->_file_path.$session_id); $read < $length; $read += strlen($buffer))        {            if (($buffer = fread($this->_file_handle, $length - $read)) === FALSE)            {                break;            }            $session_data .= $buffer;        }        //根據內容組建檔案摘要        $this->_fingerprint = md5($session_data);        return $session_data;    }    // Write 注意:Session的寫入都是全量寫,不是增量寫    //參數$session_id對應的是session_id()的值    //參數$session_data不只是當前待寫入的資料,它包含整個SESSION已儲存的資料+當前要寫入的資料    public function write($session_id, $session_data)    {        /***************** session_regenerate_id()處理 開始 *****************/        //如果程式調用了session_regenerate_id(),就會造成函數調用之後的$session_id(參數$session_id)和函數調用之前的$session_id( $this->_session_id)不一致。        //這時我們需要關閉舊的檔案指標,開啟新的檔案擷取操作指標        //這裡的if條件陳述式其實就是個短路操作,分解開就是        /*if ($session_id !== $this->_session_id){            $close_flag=$this->close();//調用close關閉舊的指標            $read_flag=$this->read($session_id);//read函數參數為新的$session_id,從而建立新的檔案            //上述兩步中有一步出錯,則返回失敗。            //實際上 OR 也是短路操作,第一個$close_flag===$this->_failure的話,就不會再往後面執行$this->read($session_id)            //為說明思路,先忽略這點            if($close_flag=== $this->_failure OR $read_flag===$this->_failure)                return $this->_failure;        }*/        if ($session_id !== $this->_session_id && ($this->close() === $this->_failure OR $this->read($session_id) === $this->_failure))        {            return $this->_failure;        }        //如果$this->_file_handle)不是資源類型,則返回錯誤        if ( ! is_resource($this->_file_handle))        {            return $this->_failure;        }        //如果當前請求的session內容摘要和$session_data是一樣的,那麼說明產生新的sessionid檔案成功        //並用用touch函數測試一下檔案是否存在,同時檢測$this->_file_new標記(該標記在read中檔案不存在需要新建立時會被設定為true)        //如果條件都滿足,就返回成功了        elseif ($this->_fingerprint === md5($session_data))        {            return ( ! $this->_file_new && ! touch($this->_file_path.$session_id))                ? $this->_failure                : $this->_success;        }        /***************** session_regenerate_id()處理 結束 *****************/        //如果是現成的檔案,那麼先清空內容        if ( ! $this->_file_new)        {            //清空檔案內容            ftruncate($this->_file_handle, 0);            //將檔案內部offset指標重新指向開頭            rewind($this->_file_handle);        }        //Session的寫入都是全量寫,不是增量寫        //把$session_data內容寫入檔案。        if (($length = strlen($session_data)) > 0)        {            for ($written = 0; $written < $length; $written += $result)            {                if (($result = fwrite($this->_file_handle, substr($session_data, $written))) === FALSE)                {                    break;                }            }            if ( ! is_int($result))            {                $this->_fingerprint = md5(substr($session_data, 0, $written));                log_message('error', 'Session: Unable to write data.');                return $this->_failure;            }        }        //擷取SESSION內容摘要        $this->_fingerprint = md5($session_data);        return $this->_success;    }    // Close    //close()在當前請求的程式執行完畢後執行,或 在調用session_commit(),session_write_close()時執行    public function close()    {        if (is_resource($this->_file_handle))        {            //釋放檔案鎖            flock($this->_file_handle, LOCK_UN);            //釋放檔案指標            fclose($this->_file_handle);            //清空變數            $this->_file_handle = $this->_file_new = $this->_session_id = NULL;        }        return $this->_success;    }    // Destroy    public function destory($session_id)    {        //調用close()方法        if ($this->close() === $this->_success)        {            if (file_exists($this->_file_path.$session_id))            {                //刪除對應的用戶端cookie                $this->_cookie_destroy();                //刪除服務端檔案                return unlink($this->_file_path.$session_id)                    ? $this->_success                    : $this->_failure;            }            return $this->_success;        }        //調用close()方法失敗        elseif ($this->_file_path !== NULL)        {            //清除 PHP 緩衝的該檔案資訊, is_file(),is_dir(), file_exists()都有影響            clearstatcache();            //重複上面的語句,再刪除一次            if (file_exists($this->_file_path.$session_id))            {                $this->_cookie_destroy();                return unlink($this->_file_path.$session_id)                    ? $this->_success                    : $this->_failure;            }            return $this->_success;        }        return $this->_failure;    }    // ------------------------------------------------------------------------    //gc方法。當session_start()時有機率調用,刪除到期檔案    public function gc($maxlifetime)    {        if ( ! is_dir($this->_config['save_path']) OR ($directory = opendir($this->_config['save_path'])) === FALSE)        {            log_message('debug', "Session: Garbage collector couldn't list files under directory '".$this->_config['save_path']."'.");            return $this->_failure;        }        $ts = time() - $maxlifetime;        //確定session檔案名稱的正則規定,免得誤刪檔案        $pattern = sprintf('/^%s[0-9a-f]{%d}$/', preg_quote($this->_config['cookie_name'], '/'),            ($this->_config['match_ip'] === TRUE ? 72 : 40)        );        while (($file = readdir($directory)) !== FALSE)        {            // If the filename doesn't match this pattern, it's either not a session file or is not ours            //根據建立時間判斷是否到期            if ( ! preg_match($pattern, $file)                OR ! is_file($this->_config['save_path'].DIRECTORY_SEPARATOR.$file)                OR ($mtime = filemtime($this->_config['save_path'].DIRECTORY_SEPARATOR.$file)) === FALSE                OR $mtime > $ts)            {                continue;            }            unlink($this->_config['save_path'].DIRECTORY_SEPARATOR.$file);        }        closedir($directory);        return $this->_success;    }}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.