Laravel Cookie源码分析

使用Cookie的方法

为了安全起见,Laravel 框架创建的所有 Cookie 都经过加密并使用一个认证码进行签名,这意味着如果客户端修改了它们则需要对其进行有效性验证。我们使用 Illuminate\Http\Request 实例的 cookie 方法从请求中获取 Cookie 的值:

$value = $request->cookie('name');

也可以使用Facade Cookie来读取Cookie的值:

Cookie::get('name', '');//第二个参数的意思是读取不到name的cookie值的话,返回空字符串

添加Cookie到响应

可以使用 响应对象的cookie 方法将一个 Cookie 添加到返回的 Illuminate\Http\Response 实例中,你需要传递 Cookie 的名称、值、以及有效期(分钟)到这个方法:

return response('Learn Laravel Kernel')->cookie('cookie-name', 'cookie-value', $minutes
);

响应对象的cookie 方法接收的参数和 PHP 原生函数 setcookie 的参数一致:

return response('Learn Laravel Kernel')->cookie('cookie-name', 'cookie-value', $minutes, $path, $domain, $secure, $httpOnly
);

还可使用Facade Cookiequeue方法以队列的形式将Cookie添加到响应:

Cookie::queue('cookie-name', 'cookie-value');

queue 方法接收 Cookie 实例或创建 Cookie 所必要的参数作为参数,这些 Cookie 会在响应被发送到浏览器之前添加到响应中。

接下来我们来分析一下Laravel中Cookie服务的实现原理。

Cookie服务注册

之前在讲服务提供器的文章里我们提到过,Laravel在BootStrap阶段会通过服务提供器将框架中涉及到的所有服务注册到服务容器里,这样在用到具体某个服务时才能从服务容器中解析出服务来,所以Cookie服务的注册也不例外,在config/app.php中我们能找到Cookie对应的服务提供器和门面。

'providers' => [/** Laravel Framework Service Providers...*/......Illuminate\Cookie\CookieServiceProvider::class,......
]    'aliases' => [......'Cookie' => Illuminate\Support\Facades\Cookie::class,......
]

Cookie服务的服务提供器是 Illuminate\Cookie\CookieServiceProvider ,其源码如下:

<?phpnamespace 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配置中读取出pathdomainsecure这些参数来设置Cookie服务用的默认路径和域名等参数,我们来看一下CookieJarsetDefaultPathAndDomain的实现:

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

上面说了生成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参数时调用了CookieJarmake方法返回一个\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返回响应时发生的,在Laravelpublic/index.php里:

$response->send();

Laravel的Response继承自Symfony的Responsesend方法定义在SymfonyResponse

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 developerif (headers_sent()) {return $this;}// headersforeach ($this->headers->allPreserveCase() as $name => $values) {foreach ($values as $value) {header($name.': '.$value, false, $this->statusCode);}}// statusheader(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;}
}    

Responsesend方法里发送响应头时将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响应首部中去了。

读取Cookie

Laravel读取请求中的Cookie值$value = $request->cookie('name'); 其实是Laravel的Request对象直接去读取Symfony请求对象的cookies来实现的, 我们在写Laravel Request对象的文章里有提到它依赖于SymfonyRequestSymfonyRequest在实例化时会把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服务外自己也定义了两个方法:

<?phpnamespace 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加密

关于对Cookie的加密可以看一下Illuminate\Cookie\Middleware\EncryptCookies中间件的源码,它的子类App\Http\Middleware\EncryptCookies是Laravelweb中间件组里的一个中间件,如果想让客户端的Javascript程序能够读Laravel设置的Cookie则需要在App\Http\Middleware\EncryptCookies$exception里对Cookie名称进行声明。

Laravel中Cookie模块大致的实现原理就梳理完了,希望大家看了我的源码分析后能够清楚Laravel Cookie实现的基本流程这样在遇到困惑或者无法通过文档找到解决方案时可以通过阅读源码看看它的实现机制再相应的设计解决方案。

本文已经收录在系列文章Laravel源码学习里,欢迎访问阅读。

Laravel核心解读--Cookie源码分析相关推荐

  1. Laravel核心解读--Session源码解析

    Session 模块源码解析 由于HTTP最初是一个匿名.无状态的请求/响应协议,服务器处理来自客户端的请求然后向客户端回送一条响应.现代Web应用程序为了给用户提供个性化的服务往往需要在请求中识别出 ...

  2. 《深入理解Spark:核心思想与源码分析》——1.2节Spark初体验

    本节书摘来自华章社区<深入理解Spark:核心思想与源码分析>一书中的第1章,第1.2节Spark初体验,作者耿嘉安,更多章节内容可以访问云栖社区"华章社区"公众号查看 ...

  3. 《深入理解Spark:核心思想与源码分析》——第1章环境准备

    本节书摘来自华章社区<深入理解Spark:核心思想与源码分析>一书中的第1章环境准备,作者耿嘉安,更多章节内容可以访问云栖社区"华章社区"公众号查看 第1章 环 境 准 ...

  4. 《深入理解Spark:核心思想与源码分析》——3.10节创建和启动ExecutorAllocationManager...

    本节书摘来自华章社区<深入理解Spark:核心思想与源码分析>一书中的第3章,第3.10节创建和启动ExecutorAllocationManager,作者耿嘉安,更多章节内容可以访问云栖 ...

  5. spring源码分析第五天------springAOP核心原理及源码分析

    spring源码分析第五天------springAOP核心原理及源码分析 1. 面向切面编程.可以通过预 编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术 切面(A ...

  6. spring源码分析第四天------springmvc核心原理及源码分析

    spring源码分析第四天------springmvc核心原理及源码分析 1.基础知识普及 2. SpringMVC请求流程 3.SpringMVC代码流程 4.springMVC源码分析 4.1 ...

  7. 《深入理解Spark:核心思想与源码分析》——SparkContext的初始化(叔篇)——TaskScheduler的启动...

    <深入理解Spark:核心思想与源码分析>一书前言的内容请看链接<深入理解SPARK:核心思想与源码分析>一书正式出版上市 <深入理解Spark:核心思想与源码分析> ...

  8. 《深入理解Spark:核心思想与源码分析》——1.3节阅读环境准备

    本节书摘来自华章社区<深入理解Spark:核心思想与源码分析>一书中的第1章,第1.3节阅读环境准备,作者耿嘉安,更多章节内容可以访问云栖社区"华章社区"公众号查看 1 ...

  9. 《深入理解SPARK:核心思想与源码分析》(第1章)

    自己牺牲了7个月的周末和下班空闲时间,通过研究Spark源码和原理,总结整理的<深入理解Spark:核心思想与源码分析>一书现在已经正式出版上市,目前亚马逊.京东.当当.天猫等网站均有销售 ...

最新文章

  1. 人工智能及其应用(第5版).蔡自兴-2章课后习题。【参考答案】
  2. 成功解决lib\subprocess.py, line 997, in _execute_child startupinfo) FileNotFoundError: [WinError 2]
  3. DL之ResNet:ResNet算法的简介(论文介绍)、架构详解、案例应用等配图集合之详细攻略
  4. python3 的 str bytes 区别
  5. 关于@Autowired后Spring无法注入的问题
  6. 理解 RIPv1使用广播更新路由与RIPv2使用组播更新路由的区别
  7. unity3d 预制体
  8. html - meta name=viewport content=XX/ 标签常见属性及说明
  9. ftp改为sftp_浅谈 FTP、FTPS 与 SFTP
  10. 深入理解设计模式(22):享元模式
  11. Vue2.0以后,有哪些变化
  12. Algorithms - Counter计数器 的 详解 与 代码
  13. Windows Phone 7 使用Perst数据库的Demo——流水账
  14. C标准 C90~C18 官网PDF下载
  15. 计算机智能化弱电设备发展趋势,中国弱电智能化发展趋势
  16. c语言编程绘制空间螺旋线,阿基米德螺旋线
  17. Android使用Bugly实现静默安装/自动安装app
  18. 《论工业社会及其未来》—泰德.卡辛斯基
  19. 火狐浏览器点击下载按钮没反应
  20. 三年级优秀书籍推荐_三年级推荐书单

热门文章

  1. Handler的源码分析
  2. consul代理---健康检测
  3. 为linux服务器安装rkhunter工具
  4. leetcode 【 Sort List 】 python 实现
  5. 国内域名商.wang总量统计TOP10:新网居亚 地位不稳
  6. struts1起服务报错
  7. C#.NET通用权限管理在DB2数据库上运行的脚本参考 - 通过程序将数据导入到目标数据库中...
  8. net.sf.ezmorph.bean.MorphDynaBean cannot be cast to com.console.demo.web.model.XXX
  9. 在linux中添加字体
  10. Scala中的match(模式匹配)