php如何實現session,本身實現session,laravel如何實現session

一、php如何實現session

執行php腳本,session_start()會從php.ini中讀取配置項,將生成的惟一值sessionID保存文件放到配置項中的保存路徑和地點。並經過HTTP協議返回響應消息頭setCookie
Cookie名=Cookie值發送給客戶端。
客戶端接受到Set-Cookie,將cookie值寫入cookie文件
在接下來的訪問中,客戶端會攜帶cookie訪問瀏覽器,瀏覽器接受到cookie值,生成http請求頭將包含的COOKIE發送給Php,php識別cookie值,從保存路徑中找對應的文件。
找到文件以後,檢驗是否在有效期內,在有效期內就讀取文件,再也不有效期內就清空文件

二、本身實現session須要考慮的幾個問題

1)、生成惟一值的算法

2)、將session存到文件裏仍是存到redis仍是memcache,應該存什麼數據

3)、接受cookie值,從保存位置找到對應的文件或數據

4)、session垃圾回收機制,刪除session文件和數據

5)、分佈式的話,session同步問題

三、分析一下laravel如何實現session.

vendor/laravel/framework/src/Illuminate/Contracts定義都是接口類(契約)。向開發者提供了統一的接口類訪問Session數據
看一下session接口定義的是什麼?
<?php

namespace Illuminate\Contracts\Session;

interface Session
{
    public function getName();//得到session名字

    public function getId();//得到當前sessionID

    public function setId($id);//修改sessionID

    public function start();//啓動session,從配置文件中讀取數據

    public function save();//將session數據保存

    public function all();//得到全部的session

    public function exists($key);//檢查session名稱是否存在

    public function has($key);//檢查session名稱是否存在和不爲空

    public function get($key, $default = null);//經過session名稱獲取session值

    public function put($key, $value = null);//將session名稱和session值存入session文件或數據庫

    public function token(); //得到csrf token的值
 
    public function remove($key);//根據session名稱刪除session信息

    public function forget($keys);//根據session名稱刪除session信息

    public function flush();//清空全部的session內容

    public function migrate($destroy = false); //爲session會話建立一個新的session會話

    public function isStarted();//肯定會話是否啓動

    public function previousUrl();//從會話中獲取session的url

    public function setPreviousUrl($url);//設置previous的url存入session

    public function getHandler();//得到session處理實例

    public function handlerNeedsRequest();//肯定會話程序是否須要請求

    public function setRequestOnHandler($request);//在處理請求實例上設置請求
}
實現接口類的具體實現類只有一個,也被稱爲驅動器
vendor/laravel/framework/src/Illuminate/Session/Store.php
<?php

namespace Illuminate\Session;

use Closure;
use stdClass;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use SessionHandlerInterface;
use Illuminate\Contracts\Session\Session;
class Store implements Session
{
    protected $id;//session id

    protected $name;//session name

    protected $attributes = [];//session

    protected $handler;//會話處理程序實現,使用了sessionHandlerInterface接口

    protected $started = false;//會話存儲處理狀態
public function __construct($name, SessionHandlerInterface $handler, $id = null)//建立一個新的session實例
    {
        $this->setId($id);
        $this->name = $name;
        $this->handler = $handler;
       //setId
       //$this->id = $this->isValidId($id) ? $id : $this->generateSessionId();
       //一、 $this->isValidId($id)?
              //return is_string($id) && ctype_alnum($id) && strlen($id) === 40;
              //is_string($id)檢查sessionid是否是字符串,ctype_alnum檢查sessionid全部的字符全是字母和數字,是爲true,否爲false;strlen檢查字符串長度是否等於40
       //二、$this->generateSessionId();?
             //return Str::random(40); 這個使用Str門面類生成長度爲40的惟一值
       //handler使用的是php預留接口類SessionnHandlerInterface,這個接口是爲了將session存儲到數據庫中,(難怪在laravel查半天都沒查到)。該類的回調方法是在php內部調用。
     }
來源於vendorlaravelframeworksrcIlluminateSupportStr.php
laravel實現生成session惟一值算法函數,重點是使用了random_bytes函數和base64_encode函數
public static function random($length = 16)
        {
          $string = '';
    
            while (($len = strlen($string)) < $length) {//當它的長度小於$length時
                $size = $length - $len;//長度差
    
                $bytes = random_bytes($size);//根據size生成加密安全的僞隨機字節字符串
    
                $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size);//base64_encode對其進行編碼,目的時爲了使二進制數據能夠經過非純8比特的傳輸層傳輸,經過str_replace函數去掉字符串的/+=3種符號,在用substr截取$size大小的字符串。
            }
    
            return $string;
        }
來源於php手冊中的關於SessionHandlerInterface接口的定義
SessionHandlerInterface {
/* 方法 */
    abstract public close ( void ) : bool
    abstract public destroy ( string $session_id ) : bool
    abstract public gc ( int $maxlifetime ) : int
    abstract public open ( string $save_path , string $session_name ) : bool
    abstract public read ( string $session_id ) : string
    abstract public write ( string $session_id , string $session_data ) :bool
}
看看都有那些類實現了SessionHandler,在編程軟件中全局搜索implements SessionHandlerInterface

//CacheBasedSessionHandler 使用的實例化對象是
Illuminate/Contracts/Cache/Repository
'cache.store' => [IlluminateCacheRepository::class,
IlluminateContractsCacheRepository::class],php

//CookieSessionHandler
//DatabaseSessionHandler
//FileSessionHandler
//NullSessionHandlerlaravel

//根據session id讀取數據
    public function start()
    {
        $this->loadSession();
        //驗證csrf_token
        if (! $this->has('_token')) {
            $this->regenerateToken();
        }

        return $this->started = true;
    }
protected function loadSession()
    {
        //array_merge合併兩個數組
        //readFromHandler() 從驅動器中根據session id獲得session信息
        $this->attributes = array_merge($this->attributes, $this->readFromHandler());
    }
protected function readFromHandler()
    {
        if ($data = $this->handler->read($this->getId())) {
             //@阻止警告輸出
             //unserialize反序列化讀出來的session的id信息
            $data = @unserialize($this->prepareForUnserialize($data));
            //若是數組不是錯誤,不爲空,是數組就返回數據
            if ($data !== false && ! is_null($data) && is_array($data)) {
                return $data;
            }
        }

        return [];
    }
//返回數據
    protected function prepareForUnserialize($data)
    {
        return $data;
    }
public function save()
{
    $this->ageFlashData();

    $this->handler->write($this->getId(), $this->prepareForStorage(
        serialize($this->attributes)
    ));

    $this->started = false;
}
protected function prepareForStorage($data)
    {
        return $data;
    }
//使會話閃存存數據老化
    public function ageFlashData()
    {

        $this->forget($this->get('_flash.old', []));
        //其餘文件定義的forget方法
>  public static function forget(&$array, $keys)
>     {
>         $original = &$array;
> 
>         $keys = (array) $keys;
> 
>         if (count($keys) === 0) {
>             return;
>         }
> 
>         foreach ($keys as $key) {
>             // if the exact key exists in the top-level, remove it
>             if (static::exists($array, $key)) {
>                 unset($array[$key]);
> 
>                 continue;
>             }
> 
>             $parts = explode('.', $key);
> 
>             // clean up before each pass
>             $array = &$original;
> 
>             while (count($parts) > 1) {
>                 $part = array_shift($parts);
> 
>                 if (isset($array[$part]) && is_array($array[$part])) {
>                     $array = &$array[$part];
>                 } else {
>                     continue 2;
>                 }
>             }
> 
>             unset($array[array_shift($parts)]);
>         }
>     }

        $this->put('_flash.old', $this->get('_flash.new', []));

        $this->put('_flash.new', []);
    }
public function all()
    {
        return $this->attributes;
    }
public function exists($key)
    {
        $placeholder = new stdClass;

        return ! collect(is_array($key) ? $key : func_get_args())->contains(function ($key) use ($placeholder) {
            return $this->get($key, $placeholder) === $placeholder;
        });
    }
//檢查是否存在Key值
    public function has($key)
    {
        return ! collect(is_array($key) ? $key : func_get_args())->contains(function ($key) {
            return is_null($this->get($key));
        });
    }
//獲取session信息
    public function get($key, $default = null)
    {
        return Arr::get($this->attributes, $key, $default);
    }
//刪除,相似pop
    public function pull($key, $default = null)
    {
        return Arr::pull($this->attributes, $key, $default);
    }
public function hasOldInput($key = null)
    {
        $old = $this->getOldInput($key);

        return is_null($key) ? count($old) > 0 : ! is_null($old);
    }
public function getOldInput($key = null, $default = null)
    {
        return Arr::get($this->get('_old_input', []), $key, $default);
    }
public function replace(array $attributes)
    {
        $this->put($attributes);
    }
//session永久保存,在不過時範圍內
    public function put($key, $value = null)
    {
        if (! is_array($key)) {
            $key = [$key => $value];
        }

        foreach ($key as $arrayKey => $arrayValue) {
            Arr::set($this->attributes, $arrayKey, $arrayValue);
        }
    }
public function remember($key, Closure $callback)
    {
        if (! is_null($value = $this->get($key))) {
            return $value;
        }

        return tap($callback(), function ($value) use ($key) {
            $this->put($key, $value);
        });
    }
//相似於push
    public function push($key, $value)
    {
        $array = $this->get($key, []);

        $array[] = $value;

        $this->put($key, $array);
    }
public function increment($key, $amount = 1)
    {
        $this->put($key, $value = $this->get($key, 0) + $amount);

        return $value;
    }
public function decrement($key, $amount = 1)
    {
        return $this->increment($key, $amount * -1);
    }
//快閃保存,只保存兩次請求
    public function flash(string $key, $value = true)
    {
        $this->put($key, $value);

        $this->push('_flash.new', $key);

        $this->removeFromOldFlashData([$key]);
    }
public function now($key, $value)
    {
        $this->put($key, $value);

        $this->push('_flash.old', $key);
    }
public function reflash()
    {
        $this->mergeNewFlashes($this->get('_flash.old', []));

        $this->put('_flash.old', []);
    }
//刷新快閃數據時間,保持到下次請求
    public function keep($keys = null)
    {
        $this->mergeNewFlashes($keys = is_array($keys) ? $keys : func_get_args());

        $this->removeFromOldFlashData($keys);
    }
protected function mergeNewFlashes(array $keys)
    {
        $values = array_unique(array_merge($this->get('_flash.new', []), $keys));

        $this->put('_flash.new', $values);
    }
protected function removeFromOldFlashData(array $keys)
    {
        $this->put('_flash.old', array_diff($this->get('_flash.old', []), $keys));
    }
public function flashInput(array $value)
    {
        $this->flash('_old_input', $value);
    }
public function remove($key)
    {
        return Arr::pull($this->attributes, $key);
    }
public function forget($keys)
    {
        Arr::forget($this->attributes, $keys);
    }
//每次flush將數組置空
    public function flush()
    {
        $this->attributes = [];
    }
public function invalidate()
    {
        $this->flush();

        return $this->migrate(true);
    }
public function regenerate($destroy = false)
    {
        return tap($this->migrate($destroy), function () {
            $this->regenerateToken();
        });
    }
//給session生成一個新的sessionID
    public function migrate($destroy = false)
    {
        //若是
        if ($destroy) {
            $this->handler->destroy($this->getId());
        }

        $this->setExists(false);

        $this->setId($this->generateSessionId());

        return true;
    }
     //是否啓動
    public function isStarted()
    {
        return $this->started;
    }

    //獲取session名
    public function getName()
    {
        return $this->name;
    }

   //設置session 名
    public function setName($name)
    {
        $this->name = $name;
    }
    //獲取session id
    public function getId()
    {
        return $this->id;
    }
    //若是sessionid有效,就返回有效id,若是失效就生成session id
    public function setId($id)
    {
        $this->id = $this->isValidId($id) ? $id : $this->generateSessionId();
    }
    //檢查session id
    public function isValidId($id)
    {
        return is_string($id) && ctype_alnum($id) && strlen($id) === 40;
    }
    //獲取sessionID 惟一的40長度值
    protected function generateSessionId()
    {
        return Str::random(40);
    }
    //若是適用,在處理程序上設置會話的存在性
    public function setExists($value)
    {
        if ($this->handler instanceof ExistenceAwareInterface) {
            $this->handler->setExists($value);
        }
    }

   //獲取token值
    public function token()
    {
        return $this->get('_token');
    }

   //生成惟一值40,存入_token
    public function regenerateToken()
    {
        $this->put('_token', Str::random(40));
    }

   //獲取當前url
    public function previousUrl()
    {
        return $this->get('_previous.url');
    }

    //存儲當前url
    public function setPreviousUrl($url)
    {
        $this->put('_previous.url', $url);
    }

     //獲取當前使用的驅動器
    public function getHandler()
    {
        return $this->handler;
    }
    //返回驅動器實例是否使CokieSessionHandler
    public function handlerNeedsRequest()
    {
        return $this->handler instanceof CookieSessionHandler;
    }
    //若是驅動器是CookieSessionHandler,那麼執行setRequest方法
    public function setRequestOnHandler($request)
    {
        if ($this->handlerNeedsRequest()) {
            $this->handler->setRequest($request);
        }
    }
}
相關文章
相關標籤/搜索