爲了安全起見,Laravel 框架建立的全部 Cookie 都通過加密並使用一個認證碼進行簽名,這意味着若是客戶端修改了它們則須要對其進行有效性驗證。咱們使用 Illuminate\Http\Request
實例的 cookie
方法從請求中獲取 Cookie 的值:php
$value = $request->cookie('name');
也可使用Facade Cookie
來讀取Cookie的值:git
Cookie::get('name', '');//第二個參數的意思是讀取不到name的cookie值的話,返回空字符串
添加Cookie到響應github
可使用 響應對象的cookie
方法將一個 Cookie 添加到返回的 Illuminate\Http\Response
實例中,你須要傳遞 Cookie 的名稱、值、以及有效期(分鐘)到這個方法:web
return response('Learn Laravel Kernel')->cookie( 'cookie-name', 'cookie-value', $minutes );
響應對象的cookie
方法接收的參數和 PHP 原生函數 setcookie
的參數一致:api
return response('Learn Laravel Kernel')->cookie( 'cookie-name', 'cookie-value', $minutes, $path, $domain, $secure, $httpOnly );
還可以使用Facade Cookie
的queue
方法以隊列的形式將Cookie添加到響應:瀏覽器
Cookie::queue('cookie-name', 'cookie-value');
queue
方法接收 Cookie 實例或建立 Cookie 所必要的參數做爲參數,這些 Cookie 會在響應被髮送到瀏覽器以前添加到響應中。安全
接下來咱們來分析一下Laravel中Cookie服務的實現原理。cookie
以前在講服務提供器的文章裏咱們提到過,Laravel在BootStrap階段會經過服務提供器將框架中涉及到的全部服務註冊到服務容器裏,這樣在用到具體某個服務時才能從服務容器中解析出服務來,因此Cookie
服務的註冊也不例外,在config/app.php
中咱們能找到Cookie對應的服務提供器和門面。session
'providers' => [ /* * Laravel Framework Service Providers... */ ...... Illuminate\Cookie\CookieServiceProvider::class, ...... ] 'aliases' => [ ...... 'Cookie' => Illuminate\Support\Facades\Cookie::class, ...... ]
Cookie服務的服務提供器是 Illuminate\Cookie\CookieServiceProvider
,其源碼以下:app
<?php namespace Illuminate\Cookie; use Illuminate\Support\ServiceProvider; class CookieServiceProvider extends ServiceProvider { /** * Register the service provider. * * @return void */ public function register() { $this->app->singleton('cookie', function ($app) { $config = $app->make('config')->get('session'); return (new CookieJar)->setDefaultPathAndDomain( $config['path'], $config['domain'], $config['secure'], $config['same_site'] ?? null ); }); } }
在CookieServiceProvider
裏將\Illuminate\Cookie\CookieJar
類的對象註冊爲Cookie服務,在實例化時會從Laravel的config/session.php
配置中讀取出path
、domain
、secure
這些參數來設置Cookie服務用的默認路徑和域名等參數,咱們來看一下CookieJar
裏setDefaultPathAndDomain
的實現:
namespace Illuminate\Cookie; class CookieJar implements JarContract { /** * 設置Cookie的默認路徑和Domain * * @param string $path * @param string $domain * @param bool $secure * @param string $sameSite * @return $this */ public function setDefaultPathAndDomain($path, $domain, $secure = false, $sameSite = null) { list($this->path, $this->domain, $this->secure, $this->sameSite) = [$path, $domain, $secure, $sameSite]; return $this; } }
它只是把這些默認參數保存到CookieJar
對象的屬性中,等到make
生成\Symfony\Component\HttpFoundation\Cookie
對象時纔會使用它們。
上面說了生成Cookie用的是Response
對象的cookie
方法,Response
的是利用Laravel的全局函數cookie
來生成Cookie對象而後設置到響應頭裏的,有點亂咱們來看一下源碼
class Response extends BaseResponse { /** * Add a cookie to the response. * * @param \Symfony\Component\HttpFoundation\Cookie|mixed $cookie * @return $this */ public function cookie($cookie) { return call_user_func_array([$this, 'withCookie'], func_get_args()); } /** * Add a cookie to the response. * * @param \Symfony\Component\HttpFoundation\Cookie|mixed $cookie * @return $this */ public function withCookie($cookie) { if (is_string($cookie) && function_exists('cookie')) { $cookie = call_user_func_array('cookie', func_get_args()); } $this->headers->setCookie($cookie); return $this; } }
看一下全局函數cookie
的實現:
/** * Create a new cookie instance. * * @param string $name * @param string $value * @param int $minutes * @param string $path * @param string $domain * @param bool $secure * @param bool $httpOnly * @param bool $raw * @param string|null $sameSite * @return \Illuminate\Cookie\CookieJar|\Symfony\Component\HttpFoundation\Cookie */ function cookie($name = null, $value = null, $minutes = 0, $path = null, $domain = null, $secure = false, $httpOnly = true, $raw = false, $sameSite = null) { $cookie = app(CookieFactory::class); if (is_null($name)) { return $cookie; } return $cookie->make($name, $value, $minutes, $path, $domain, $secure, $httpOnly, $raw, $sameSite); }
經過cookie
函數的@return標註咱們能知道它返回的是一個Illuminate\Cookie\CookieJar
對象或者是\Symfony\Component\HttpFoundation\Cookie
對象。既cookie
函數在無接受參數時返回一個CookieJar
對象,在有Cookie參數時調用了CookieJar
的make
方法返回一個\Symfony\Component\HttpFoundation\Cookie
對象。
拿到Cookie
對象後程序接着流程往下走把Cookie設置到Response
對象的headers屬性
裏,`headers`屬性引用了\Symfony\Component\HttpFoundation\ResponseHeaderBag
對象
class ResponseHeaderBag extends HeaderBag { public function setCookie(Cookie $cookie) { $this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; $this->headerNames['set-cookie'] = 'Set-Cookie'; } }
咱們能夠看到這裏只是把Cookie
對象暫存到了headers
對象裏,真正把Cookie發送到瀏覽器是在Laravel
返回響應時發生的,在Laravel
的public/index.php
裏:
$response->send();
Laravel的Response
繼承自Symfony的Response
,send
方法定義在Symfony
的Response
裏
namespace Symfony\Component\HttpFoundation; class Response { /** * Sends HTTP headers and content. * * @return $this */ public function send() { $this->sendHeaders(); $this->sendContent(); if (function_exists('fastcgi_finish_request')) { fastcgi_finish_request(); } elseif (!\in_array(PHP_SAPI, array('cli', 'phpdbg'), true)) { static::closeOutputBuffers(0, true); } return $this; } public function sendHeaders() { // headers have already been sent by the developer if (headers_sent()) { return $this; } // headers foreach ($this->headers->allPreserveCase() as $name => $values) { foreach ($values as $value) { header($name.': '.$value, false, $this->statusCode); } } // status header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode); return $this; } /** * Returns the headers, with original capitalizations. * * @return array An array of headers */ public function allPreserveCase() { $headers = array(); foreach ($this->all() as $name => $value) { $headers[isset($this->headerNames[$name]) ? $this->headerNames[$name] : $name] = $value; } return $headers; } public function all() { $headers = parent::all(); foreach ($this->getCookies() as $cookie) { $headers['set-cookie'][] = (string) $cookie; } return $headers; } }
在Response
的send
方法裏發送響應頭時將Cookie數據設置到了Http響應首部的Set-Cookie
字段裏,這樣當響應發送給瀏覽器後瀏覽器就能保存這些Cookie數據了。
至於用門面Cookie::queue
以隊列的形式設置Cookie其實也是將Cookie暫存到了CookieJar
對象的queued
屬性裏
namespace Illuminate\Cookie; class CookieJar implements JarContract { public function queue(...$parameters) { if (head($parameters) instanceof Cookie) { $cookie = head($parameters); } else { $cookie = call_user_func_array([$this, 'make'], $parameters); } $this->queued[$cookie->getName()] = $cookie; } public function queued($key, $default = null) { return Arr::get($this->queued, $key, $default); } }
而後在web
中間件組裏邊有一個\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse
中間件,它在響應返回給客戶端以前將暫存在queued
屬性裏的Cookie設置到了響應的headers
對象裏:
namespace Illuminate\Cookie\Middleware; use Closure; use Illuminate\Contracts\Cookie\QueueingFactory as CookieJar; class AddQueuedCookiesToResponse { /** * The cookie jar instance. * * @var \Illuminate\Contracts\Cookie\QueueingFactory */ protected $cookies; /** * Create a new CookieQueue instance. * * @param \Illuminate\Contracts\Cookie\QueueingFactory $cookies * @return void */ public function __construct(CookieJar $cookies) { $this->cookies = $cookies; } /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { $response = $next($request); foreach ($this->cookies->getQueuedCookies() as $cookie) { $response->headers->setCookie($cookie); } return $response; }
這樣在Response
對象調用send
方法時也會把經過Cookie::queue()
設置的Cookie數據設置到Set-Cookie
響應首部中去了。
Laravel讀取請求中的Cookie值$value = $request->cookie('name');
實際上是Laravel的Request
對象直接去讀取Symfony
請求對象的cookies
來實現的, 咱們在寫Laravel Request
對象的文章裏有提到它依賴於Symfony
的Request
, Symfony
的Request
在實例化時會把PHP裏那些$_POST
、$_COOKIE
全局變量抽象成了具體對象存儲在了對應的屬性中。
namespace Illuminate\Http; class Request extends SymfonyRequest implements Arrayable, ArrayAccess { public function cookie($key = null, $default = null) { return $this->retrieveItem('cookies', $key, $default); } protected function retrieveItem($source, $key, $default) { if (is_null($key)) { return $this->$source->all(); } //從Request的cookies屬性中獲取數據 return $this->$source->get($key, $default); } }
關於經過門面Cookie::get()
讀取Cookie的實現咱們能夠看下Cookie
門面源碼的實現,經過源碼咱們知道門面Cookie
除了經過外觀模式代理Cookie
服務外本身也定義了兩個方法:
<?php namespace Illuminate\Support\Facades; /** * @see \Illuminate\Cookie\CookieJar */ class Cookie extends Facade { /** * Determine if a cookie exists on the request. * * @param string $key * @return bool */ public static function has($key) { return ! is_null(static::$app['request']->cookie($key, null)); } /** * Retrieve a cookie from the request. * * @param string $key * @param mixed $default * @return string */ public static function get($key = null, $default = null) { return static::$app['request']->cookie($key, $default); } /** * Get the registered name of the component. * * @return string */ protected static function getFacadeAccessor() { return 'cookie'; } }
Cookie::get()
和Cookie::has()
是門面直接讀取Request
對象cookies
屬性裏的Cookie數據。
關於對Cookie的加密能夠看一下Illuminate\Cookie\Middleware\EncryptCookies
中間件的源碼,它的子類App\Http\Middleware\EncryptCookies
是Laravelweb
中間件組裏的一箇中間件,若是想讓客戶端的Javascript程序可以讀Laravel設置的Cookie則須要在App\Http\Middleware\EncryptCookies
的$exception
裏對Cookie名稱進行聲明。
Laravel中Cookie模塊大體的實現原理就梳理完了,但願你們看了個人源碼分析後可以清楚Laravel Cookie實現的基本流程這樣在遇到困惑或者沒法經過文檔找到解決方案時能夠經過閱讀源碼看看它的實現機制再相應的設計解決方案。
本文已經收錄在系列文章Laravel源碼學習裏,歡迎訪問閱讀。