├── phpstan.tests.neon ├── phpstan.neon ├── .editorconfig ├── psalm.xml ├── src └── Dflydev │ └── FigCookies │ ├── StringUtil.php │ ├── FigRequestCookies.php │ ├── Modifier │ └── SameSite.php │ ├── Cookie.php │ ├── FigResponseCookies.php │ ├── Cookies.php │ ├── SetCookies.php │ └── SetCookie.php ├── LICENSE ├── phpcs.xml.dist ├── composer.json ├── .github └── workflows │ └── tests.yml ├── README.md └── CHANGELOG.md /phpstan.tests.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 4 3 | paths: 4 | - tests 5 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 8 3 | paths: 4 | - src 5 | ignoreErrors: 6 | - '#Unsafe usage of new static\(\)\.#' 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; top-most EditorConfig file 2 | root = true 3 | 4 | # All files. 5 | [*] 6 | end_of_line = LF 7 | indent_style = space 8 | indent_size = 4 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Dflydev/FigCookies/StringUtil.php: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ./src 16 | ./tests 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dflydev/fig-cookies", 3 | "description": "Cookies for PSR-7 HTTP Message Interface.", 4 | "license": "MIT", 5 | "keywords": ["psr7", "psr-7", "cookies"], 6 | "authors": [ 7 | { 8 | "name": "Beau Simensen", 9 | "email": "beau@dflydev.com" 10 | } 11 | ], 12 | "config": { 13 | "allow-plugins": { 14 | "dealerdirect/phpcodesniffer-composer-installer": true, 15 | "phpstan/extension-installer": true 16 | } 17 | }, 18 | "require": { 19 | "php": "^7.2 || ^8.0", 20 | "ext-pcre": "*", 21 | "psr/http-message": "^1.0.1 || ^2" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Dflydev\\FigCookies\\": "src/Dflydev/FigCookies" 26 | } 27 | }, 28 | "require-dev": { 29 | "phpunit/phpunit": "^7.2.6 || ^9", 30 | "squizlabs/php_codesniffer": "^3.3", 31 | "doctrine/coding-standard": "^8", 32 | "phpstan/phpstan": "^0.12", 33 | "phpstan/phpstan-phpunit": "^0.12.16", 34 | "phpstan/extension-installer": "^1.0", 35 | "scrutinizer/ocular": "^1.8", 36 | "vimeo/psalm": "^4.4" 37 | }, 38 | "autoload-dev": { 39 | "psr-4": { 40 | "Dflydev\\FigCookies\\": "tests/Dflydev/FigCookies" 41 | } 42 | }, 43 | "extra": { 44 | "branch-alias": { 45 | "dev-main": "3.0.x-dev" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Dflydev/FigCookies/FigRequestCookies.php: -------------------------------------------------------------------------------- 1 | get($name); 18 | 19 | if ($cookie) { 20 | return $cookie; 21 | } 22 | 23 | return Cookie::create($name, $value); 24 | } 25 | 26 | public static function set(RequestInterface $request, Cookie $cookie): RequestInterface 27 | { 28 | return Cookies::fromRequest($request) 29 | ->with($cookie) 30 | ->renderIntoCookieHeader($request); 31 | } 32 | 33 | public static function modify(RequestInterface $request, string $name, callable $modify): RequestInterface 34 | { 35 | if (! is_callable($modify)) { 36 | throw new InvalidArgumentException('$modify must be callable.'); 37 | } 38 | 39 | $cookies = Cookies::fromRequest($request); 40 | $cookie = $modify($cookies->has($name) 41 | ? $cookies->get($name) 42 | : Cookie::create($name)); 43 | 44 | return $cookies 45 | ->with($cookie) 46 | ->renderIntoCookieHeader($request); 47 | } 48 | 49 | public static function remove(RequestInterface $request, string $name): RequestInterface 50 | { 51 | return Cookies::fromRequest($request) 52 | ->without($name) 53 | ->renderIntoCookieHeader($request); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Dflydev/FigCookies/Modifier/SameSite.php: -------------------------------------------------------------------------------- 1 | value = $value; 27 | } 28 | 29 | public static function strict(): self 30 | { 31 | return new self(self::STRICT); 32 | } 33 | 34 | public static function lax(): self 35 | { 36 | return new self(self::LAX); 37 | } 38 | 39 | public static function none(): self 40 | { 41 | return new self(self::NONE); 42 | } 43 | 44 | /** 45 | * @throws InvalidArgumentException If the given SameSite string is neither strict nor lax. 46 | */ 47 | public static function fromString(string $sameSite): self 48 | { 49 | $lowerCaseSite = strtolower($sameSite); 50 | 51 | if ($lowerCaseSite === 'strict') { 52 | return self::strict(); 53 | } 54 | 55 | if ($lowerCaseSite === 'lax') { 56 | return self::lax(); 57 | } 58 | 59 | if ($lowerCaseSite === 'none') { 60 | return self::none(); 61 | } 62 | 63 | throw new InvalidArgumentException(sprintf( 64 | 'Expected modifier value to be either "strict", "lax", or "none", "%s" given', 65 | $sameSite 66 | )); 67 | } 68 | 69 | public function asString(): string 70 | { 71 | return 'SameSite=' . $this->value; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Dflydev/FigCookies/Cookie.php: -------------------------------------------------------------------------------- 1 | name = $name; 21 | $this->value = $value; 22 | } 23 | 24 | public function getName(): string 25 | { 26 | return $this->name; 27 | } 28 | 29 | public function getValue(): ?string 30 | { 31 | return $this->value; 32 | } 33 | 34 | public function withValue(?string $value = null): Cookie 35 | { 36 | $clone = clone $this; 37 | 38 | $clone->value = $value; 39 | 40 | return $clone; 41 | } 42 | 43 | /** 44 | * Render Cookie as a string. 45 | */ 46 | public function __toString(): string 47 | { 48 | return urlencode($this->name) . '=' . urlencode((string) $this->value); 49 | } 50 | 51 | /** 52 | * Create a Cookie. 53 | */ 54 | public static function create(string $name, ?string $value = null): Cookie 55 | { 56 | return new static($name, $value); 57 | } 58 | 59 | /** 60 | * Create a list of Cookies from a Cookie header value string. 61 | * 62 | * @return Cookie[] 63 | */ 64 | public static function listFromCookieString(string $string): array 65 | { 66 | $cookies = StringUtil::splitOnAttributeDelimiter($string); 67 | 68 | return array_map(static function ($cookiePair) { 69 | return static::oneFromCookiePair($cookiePair); 70 | }, $cookies); 71 | } 72 | 73 | /** 74 | * Create one Cookie from a cookie key/value header value string. 75 | */ 76 | public static function oneFromCookiePair(string $string): Cookie 77 | { 78 | [$cookieName, $cookieValue] = StringUtil::splitCookiePair($string); 79 | 80 | $cookie = new static($cookieName); 81 | 82 | if ($cookieValue !== null) { 83 | $cookie = $cookie->withValue($cookieValue); 84 | } 85 | 86 | return $cookie; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Dflydev/FigCookies/FigResponseCookies.php: -------------------------------------------------------------------------------- 1 | get($name); 18 | 19 | if ($cookie) { 20 | return $cookie; 21 | } 22 | 23 | return SetCookie::create($name, $value); 24 | } 25 | 26 | public static function set(ResponseInterface $response, SetCookie $setCookie): ResponseInterface 27 | { 28 | return SetCookies::fromResponse($response) 29 | ->with($setCookie) 30 | ->renderIntoSetCookieHeader($response); 31 | } 32 | 33 | /** 34 | * @deprecated Do not use this method. Will be removed in v4.0. 35 | * 36 | * If you want to remove a cookie, create it normally and call ->expire() 37 | * on the SetCookie object. 38 | */ 39 | public static function expire(ResponseInterface $response, string $cookieName): ResponseInterface 40 | { 41 | return static::set($response, SetCookie::createExpired($cookieName)); 42 | } 43 | 44 | public static function modify(ResponseInterface $response, string $name, callable $modify): ResponseInterface 45 | { 46 | if (! is_callable($modify)) { 47 | throw new InvalidArgumentException('$modify must be callable.'); 48 | } 49 | 50 | $setCookies = SetCookies::fromResponse($response); 51 | $setCookie = $modify($setCookies->has($name) 52 | ? $setCookies->get($name) 53 | : SetCookie::create($name)); 54 | 55 | return $setCookies 56 | ->with($setCookie) 57 | ->renderIntoSetCookieHeader($response); 58 | } 59 | 60 | public static function remove(ResponseInterface $response, string $name): ResponseInterface 61 | { 62 | return SetCookies::fromResponse($response) 63 | ->without($name) 64 | ->renderIntoSetCookieHeader($response); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Dflydev/FigCookies/Cookies.php: -------------------------------------------------------------------------------- 1 | cookies[$cookie->getName()] = $cookie; 27 | } 28 | } 29 | 30 | public function has(string $name): bool 31 | { 32 | return isset($this->cookies[$name]); 33 | } 34 | 35 | public function get(string $name): ?Cookie 36 | { 37 | if (! $this->has($name)) { 38 | return null; 39 | } 40 | 41 | return $this->cookies[$name]; 42 | } 43 | 44 | /** @return Cookie[] */ 45 | public function getAll(): array 46 | { 47 | return array_values($this->cookies); 48 | } 49 | 50 | public function with(Cookie $cookie): Cookies 51 | { 52 | $clone = clone $this; 53 | 54 | $clone->cookies[$cookie->getName()] = $cookie; 55 | 56 | return $clone; 57 | } 58 | 59 | public function without(string $name): Cookies 60 | { 61 | $clone = clone $this; 62 | 63 | if (! $clone->has($name)) { 64 | return $clone; 65 | } 66 | 67 | unset($clone->cookies[$name]); 68 | 69 | return $clone; 70 | } 71 | 72 | /** 73 | * Render Cookies into a Request. 74 | */ 75 | public function renderIntoCookieHeader(RequestInterface $request): RequestInterface 76 | { 77 | $cookieString = implode('; ', $this->cookies); 78 | 79 | $request = $request->withHeader(static::COOKIE_HEADER, $cookieString); 80 | 81 | return $request; 82 | } 83 | 84 | /** 85 | * Create Cookies from a Cookie header value string. 86 | */ 87 | public static function fromCookieString(string $string): self 88 | { 89 | return new static(Cookie::listFromCookieString($string)); 90 | } 91 | 92 | public static function fromRequest(RequestInterface $request): Cookies 93 | { 94 | $cookieString = $request->getHeaderLine(static::COOKIE_HEADER); 95 | 96 | return static::fromCookieString($cookieString); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Dflydev/FigCookies/SetCookies.php: -------------------------------------------------------------------------------- 1 | setCookies[$setCookie->getName()] = $setCookie; 27 | } 28 | } 29 | 30 | public function has(string $name): bool 31 | { 32 | return isset($this->setCookies[$name]); 33 | } 34 | 35 | public function get(string $name): ?SetCookie 36 | { 37 | if (! $this->has($name)) { 38 | return null; 39 | } 40 | 41 | return $this->setCookies[$name]; 42 | } 43 | 44 | /** @return SetCookie[] */ 45 | public function getAll(): array 46 | { 47 | return array_values($this->setCookies); 48 | } 49 | 50 | public function with(SetCookie $setCookie): SetCookies 51 | { 52 | $clone = clone $this; 53 | 54 | $clone->setCookies[$setCookie->getName()] = $setCookie; 55 | 56 | return $clone; 57 | } 58 | 59 | public function without(string $name): SetCookies 60 | { 61 | $clone = clone $this; 62 | 63 | if (! $clone->has($name)) { 64 | return $clone; 65 | } 66 | 67 | unset($clone->setCookies[$name]); 68 | 69 | return $clone; 70 | } 71 | 72 | /** 73 | * Render SetCookies into a Response. 74 | */ 75 | public function renderIntoSetCookieHeader(ResponseInterface $response): ResponseInterface 76 | { 77 | $response = $response->withoutHeader(static::SET_COOKIE_HEADER); 78 | foreach ($this->setCookies as $setCookie) { 79 | $response = $response->withAddedHeader(static::SET_COOKIE_HEADER, (string) $setCookie); 80 | } 81 | 82 | return $response; 83 | } 84 | 85 | /** 86 | * Create SetCookies from a collection of SetCookie header value strings. 87 | * 88 | * @param string[] $setCookieStrings 89 | */ 90 | public static function fromSetCookieStrings(array $setCookieStrings): self 91 | { 92 | return new static(array_map(static function (string $setCookieString): SetCookie { 93 | return SetCookie::fromSetCookieString($setCookieString); 94 | }, $setCookieStrings)); 95 | } 96 | 97 | /** 98 | * Create SetCookies from a Response. 99 | */ 100 | public static function fromResponse(ResponseInterface $response): SetCookies 101 | { 102 | return new static(array_map(static function (string $setCookieString): SetCookie { 103 | return SetCookie::fromSetCookieString($setCookieString); 104 | }, $response->getHeader(static::SET_COOKIE_HEADER))); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: ~ 5 | pull_request: ~ 6 | 7 | jobs: 8 | phpcs: 9 | name: PHPCS 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - uses: shivammathur/setup-php@v2 16 | with: 17 | php-version: 8.0 18 | extensions: curl, mbstring 19 | coverage: none 20 | tools: composer:v2, cs2pr 21 | 22 | - run: composer update --no-progress 23 | 24 | - run: vendor/bin/phpcs -q --report=checkstyle | cs2pr 25 | 26 | phpunit: 27 | name: PHPUnit on ${{ matrix.php }} ${{ matrix.composer-flags }} 28 | runs-on: ubuntu-latest 29 | strategy: 30 | matrix: 31 | php: ['7.3', '7.4'] 32 | coverage: [pcov] 33 | composer-flags: [''] 34 | include: 35 | - php: '7.2' 36 | coverage: 'none' 37 | - php: '8.0' 38 | coverage: 'none' 39 | - php: '8.1' 40 | coverage: 'none' 41 | - php: '8.2' 42 | coverage: 'none' 43 | - php: '8.3' 44 | coverage: 'none' 45 | - php: '8.4' 46 | coverage: 'none' 47 | - php: '8.5' 48 | coverage: 'none' 49 | 50 | steps: 51 | - uses: actions/checkout@v2 52 | with: 53 | fetch-depth: 0 54 | 55 | - uses: shivammathur/setup-php@v2 56 | with: 57 | php-version: ${{ matrix.php }} 58 | extensions: curl 59 | coverage: ${{ matrix.coverage }} 60 | tools: composer:v2 61 | 62 | - run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 63 | 64 | - name: "Use PHPUnit 9.3+ on PHP 8+" 65 | run: composer require --no-update --dev phpunit/phpunit:^9.3 66 | if: ${{ matrix.php == '8.0' || matrix.php == '8.1' || matrix.php == '8.2' }} 67 | 68 | - run: composer update --no-progress ${{ matrix.composer-flags }} 69 | 70 | - run: vendor/bin/phpunit --no-coverage 71 | if: ${{ matrix.coverage == 'none' }} 72 | 73 | - run: vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover 74 | if: ${{ matrix.coverage != 'none' }} 75 | 76 | - run: php vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover 77 | if: ${{ matrix.coverage != 'none' && github.repository == 'dflydev/dflydev-fig-cookies' }} 78 | 79 | phpstan: 80 | name: PHPStan 81 | runs-on: ubuntu-latest 82 | 83 | steps: 84 | - uses: actions/checkout@v2 85 | 86 | - uses: shivammathur/setup-php@v2 87 | with: 88 | php-version: 8.0 89 | extensions: curl 90 | coverage: none 91 | tools: composer:v2 92 | 93 | - run: composer update --no-progress 94 | 95 | - run: vendor/bin/phpstan analyse --no-progress 96 | 97 | - run: vendor/bin/phpstan analyse --no-progress -c phpstan.tests.neon 98 | 99 | psalm: 100 | name: Psalm 101 | runs-on: ubuntu-latest 102 | 103 | steps: 104 | - uses: actions/checkout@v2 105 | 106 | - uses: shivammathur/setup-php@v2 107 | with: 108 | php-version: 8.0 109 | extensions: curl 110 | coverage: none 111 | tools: composer:v2 112 | 113 | - run: composer update --no-progress 114 | 115 | - run: vendor/bin/psalm --no-progress --output-format=github 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FIG Cookies 2 | 3 | Managing Cookies for PSR-7 Requests and Responses. 4 | 5 | [![Latest Stable Version](https://poser.pugx.org/dflydev/fig-cookies/v/stable)](https://packagist.org/packages/dflydev/fig-cookies) 6 | [![Total Downloads](https://poser.pugx.org/dflydev/fig-cookies/downloads)](https://packagist.org/packages/dflydev/fig-cookies) 7 | [![Latest Unstable Version](https://poser.pugx.org/dflydev/fig-cookies/v/unstable)](https://packagist.org/packages/dflydev/fig-cookies) 8 | [![License](https://poser.pugx.org/dflydev/fig-cookies/license)](https://packagist.org/packages/dflydev/fig-cookies) 9 |
10 | [![Build Status](https://travis-ci.org/dflydev/dflydev-fig-cookies.svg?branch=master)](https://travis-ci.org/dflydev/dflydev-fig-cookies) 11 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/dflydev/dflydev-fig-cookies/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/dflydev/dflydev-fig-cookies/?branch=master) 12 | [![Code Coverage](https://scrutinizer-ci.com/g/dflydev/dflydev-fig-cookies/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/dflydev/dflydev-fig-cookies/?branch=master) 13 | [![Code Climate](https://codeclimate.com/github/dflydev/dflydev-fig-cookies/badges/gpa.svg)](https://codeclimate.com/github/dflydev/dflydev-fig-cookies) 14 |
15 | [![Join the chat at https://gitter.im/dflydev/dflydev-fig-cookies](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dflydev/dflydev-fig-cookies?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 16 | 17 | ## Installation 18 | 19 | ```bash 20 | $> composer require dflydev/fig-cookies 21 | ``` 22 | 23 | ## Concepts 24 | 25 | FIG Cookies tackles two problems, managing **Cookie** _Request_ headers and 26 | managing **Set-Cookie** _Response_ headers. It does this by way of introducing 27 | a `Cookies` class to manage collections of `Cookie` instances and a 28 | `SetCookies` class to manage collections of `SetCookie` instances. 29 | 30 | Instantiating these collections looks like this: 31 | 32 | ```php 33 | // Get a collection representing the cookies in the Cookie headers 34 | // of a PSR-7 Request. 35 | $cookies = Dflydev\FigCookies\Cookies::fromRequest($request); 36 | 37 | // Get a collection representing the cookies in the Set-Cookie headers 38 | // of a PSR-7 Response 39 | $setCookies = Dflydev\FigCookies\SetCookies::fromResponse($response); 40 | ``` 41 | 42 | After modifying these collections in some way, they are rendered into a 43 | PSR-7 Request or PSR-7 Response like this: 44 | 45 | ```php 46 | // Render the Cookie headers and add them to the headers of a 47 | // PSR-7 Request. 48 | $request = $cookies->renderIntoCookieHeader($request); 49 | 50 | // Render the Set-Cookie headers and add them to the headers of a 51 | // PSR-7 Response. 52 | $response = $setCookies->renderIntoSetCookieHeader($response); 53 | ``` 54 | 55 | Like PSR-7 Messages, `Cookie`, `Cookies`, `SetCookie`, and `SetCookies` 56 | are all represented as immutable value objects and all mutators will 57 | return new instances of the original with the requested changes. 58 | 59 | While this style of design has many benefits it can become fairly 60 | verbose very quickly. In order to get around that, FIG Cookies provides 61 | two facades in an attempt to help simplify things and make the whole process 62 | less verbose. 63 | 64 | ## Basic Usage 65 | 66 | The easiest way to start working with FIG Cookies is by using the 67 | `FigRequestCookies` and `FigResponseCookies` classes. They are facades to the 68 | primitive FIG Cookies classes. Their jobs are to make common cookie related 69 | tasks easier and less verbose than working with the primitive classes directly. 70 | 71 | There is overhead on creating `Cookies` and `SetCookies` and rebuilding 72 | requests and responses. Each of the `FigCookies` methods will go through this 73 | process so be wary of using too many of these calls in the same section of 74 | code. In some cases it may be better to work with the primitive FIG Cookies 75 | classes directly rather than using the facades. 76 | 77 | ### Request Cookies 78 | 79 | Requests include cookie information in the **Cookie** request header. The 80 | cookies in this header are represented by the `Cookie` class. 81 | 82 | ```php 83 | use Dflydev\FigCookies\Cookie; 84 | 85 | $cookie = Cookie::create('theme', 'blue'); 86 | ``` 87 | 88 | To easily work with request cookies, use the `FigRequestCookies` facade. 89 | 90 | #### Get a Request Cookie 91 | 92 | The `get` method will return a `Cookie` instance. If no cookie by the specified 93 | name exists, the returned `Cookie` instance will have a `null` value. 94 | 95 | The optional third parameter to `get` sets the value that should be used if a 96 | cookie does not exist. 97 | 98 | ```php 99 | use Dflydev\FigCookies\FigRequestCookies; 100 | 101 | $cookie = FigRequestCookies::get($request, 'theme'); 102 | $cookie = FigRequestCookies::get($request, 'theme', 'default-theme'); 103 | ``` 104 | 105 | #### Set a Request Cookie 106 | 107 | The `set` method will either add a cookie or replace an existing cookie. 108 | 109 | The `Cookie` primitive is used as the second argument. 110 | 111 | ```php 112 | use Dflydev\FigCookies\FigRequestCookies; 113 | 114 | $request = FigRequestCookies::set($request, Cookie::create('theme', 'blue')); 115 | ``` 116 | 117 | #### Modify a Request Cookie 118 | 119 | The `modify` method allows for replacing the contents of a cookie based on the 120 | current cookie with the specified name. The third argument is a `callable` that 121 | takes a `Cookie` instance as its first argument and is expected to return a 122 | `Cookie` instance. 123 | 124 | If no cookie by the specified name exists, a new `Cookie` instance with a 125 | `null` value will be passed to the callable. 126 | 127 | ```php 128 | use Dflydev\FigCookies\FigRequestCookies; 129 | 130 | $modify = function (Cookie $cookie) { 131 | $value = $cookie->getValue(); 132 | 133 | // ... inspect current $value and determine if $value should 134 | // change or if it can stay the same. in all cases, a cookie 135 | // should be returned from this callback... 136 | 137 | return $cookie->withValue($value); 138 | } 139 | 140 | $request = FigRequestCookies::modify($request, 'theme', $modify); 141 | ``` 142 | 143 | #### Remove a Request Cookie 144 | 145 | The `remove` method removes a cookie from the request headers if it exists. 146 | 147 | ```php 148 | use Dflydev\FigCookies\FigRequestCookies; 149 | 150 | $request = FigRequestCookies::remove($request, 'theme'); 151 | ``` 152 | 153 | Note that this does not cause the client to remove the cookie. Take a look at 154 | the `SetCookie` class' `expire()` method to do that. 155 | 156 | ### Response Cookies 157 | 158 | Responses include cookie information in the **Set-Cookie** response header. The 159 | cookies in these headers are represented by the `SetCookie` class. 160 | 161 | ```php 162 | use Dflydev\FigCookies\Modifier\SameSite; 163 | use Dflydev\FigCookies\SetCookie; 164 | 165 | $setCookie = SetCookie::create('lu') 166 | ->withValue('Rg3vHJZnehYLjVg7qi3bZjzg') 167 | ->withExpires('Tue, 15-Jan-2013 21:47:38 GMT') 168 | ->withMaxAge(500) 169 | ->rememberForever() 170 | ->withPath('/') 171 | ->withDomain('.example.com') 172 | ->withSecure(true) 173 | ->withHttpOnly(true) 174 | ->withSameSite(SameSite::lax()) 175 | ->withPartitioned() 176 | ; 177 | ``` 178 | 179 | To easily work with response cookies, use the `FigResponseCookies` facade. 180 | 181 | #### Get a Response Cookie 182 | 183 | The `get` method will return a `SetCookie` instance. If no cookie by the 184 | specified name exists, the returned `SetCookie` instance will have a `null` 185 | value. 186 | 187 | The optional third parameter to `get` sets the value that should be used if a 188 | cookie does not exist. 189 | 190 | ```php 191 | use Dflydev\FigCookies\FigResponseCookies; 192 | 193 | $setCookie = FigResponseCookies::get($response, 'theme'); 194 | $setCookie = FigResponseCookies::get($response, 'theme', 'simple'); 195 | ``` 196 | 197 | #### Set a Response Cookie 198 | 199 | The `set` method will either add a cookie or replace an existing cookie. 200 | 201 | The `SetCookie` primitive is used as the second argument. 202 | 203 | ```php 204 | use Dflydev\FigCookies\FigResponseCookies; 205 | 206 | $response = FigResponseCookies::set($response, SetCookie::create('token') 207 | ->withValue('a9s87dfz978a9') 208 | ->withDomain('example.com') 209 | ->withPath('/firewall') 210 | ); 211 | ``` 212 | 213 | #### Modify a Response Cookie 214 | 215 | The `modify` method allows for replacing the contents of a cookie based on the 216 | current cookie with the specified name. The third argument is a `callable` that 217 | takes a `SetCookie` instance as its first argument and is expected to return a 218 | `SetCookie` instance. 219 | 220 | If no cookie by the specified name exists, a new `SetCookie` instance with a 221 | `null` value will be passed to the callable. 222 | 223 | ```php 224 | use Dflydev\FigCookies\FigResponseCookies; 225 | 226 | $modify = function (SetCookie $setCookie) { 227 | $value = $setCookie->getValue(); 228 | 229 | // ... inspect current $value and determine if $value should 230 | // change or if it can stay the same. in all cases, a cookie 231 | // should be returned from this callback... 232 | 233 | return $setCookie 234 | ->withValue($newValue) 235 | ->withExpires($newExpires) 236 | ; 237 | } 238 | 239 | $response = FigResponseCookies::modify($response, 'theme', $modify); 240 | ``` 241 | 242 | #### Remove a Response Cookie 243 | 244 | The `remove` method removes a cookie from the response if it exists. 245 | 246 | ```php 247 | use Dflydev\FigCookies\FigResponseCookies; 248 | 249 | $response = FigResponseCookies::remove($response, 'theme'); 250 | ``` 251 | 252 | #### Expire a Response Cookie 253 | 254 | The `expire` method sets a cookie with an expiry date in the far past. This 255 | causes the client to remove the cookie. 256 | 257 | Note that in order to expire a cookie, you need to configure its `Set-Cookie` 258 | header just like when you initially wrote the cookie (i.e. same domain/path). 259 | The easiest way to do this is to re-use the same code for configuring the 260 | header when setting as well as expiring the cookie: 261 | 262 | ```php 263 | use Dflydev\FigCookies\FigResponseCookies; 264 | use Dflydev\FigCookies\SetCookie; 265 | 266 | $setCookie = SetCookie::create('ba') 267 | ->withValue('UQdfdafpJJ23k111m') 268 | ->withPath('/') 269 | ->withDomain('.example.com') 270 | ; 271 | 272 | FigResponseCookies::set($response, $setCookie->expire()); 273 | ``` 274 | 275 | ## FAQ 276 | 277 | ### Do you call `setcookies`? 278 | 279 | No. 280 | 281 | Delivery of the rendered `SetCookie` instances is the responsibility of the 282 | PSR-7 client implementation. 283 | 284 | ### Do you do anything with sessions? 285 | 286 | No. 287 | 288 | It would be possible to build session handling using cookies on top of FIG 289 | Cookies but it is out of scope for this package. 290 | 291 | ### Do you read from `$_COOKIES`? 292 | 293 | No. 294 | 295 | FIG Cookies only pays attention to the `Cookie` headers on 296 | [PSR-7](https://packagist.org/packages/psr/http-message) Request 297 | instances. In the case of `ServerRequestInterface` instances, PSR-7 298 | implementations should be including `$_COOKIES` values in the headers 299 | so in that case FIG Cookies may be interacting with `$_COOKIES` 300 | indirectly. 301 | 302 | ## License 303 | 304 | MIT, see LICENSE. 305 | 306 | ## Community 307 | 308 | Want to get involved? Here are a few ways: 309 | 310 | - Find us in the #dflydev IRC channel on irc.freenode.org. 311 | - Mention [@dflydev](https://twitter.com/dflydev) on Twitter. 312 | - [![Join the chat at https://gitter.im/dflydev/dflydev-fig-cookies](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dflydev/dflydev-fig-cookies?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 313 | -------------------------------------------------------------------------------- /src/Dflydev/FigCookies/SetCookie.php: -------------------------------------------------------------------------------- 1 | name = $name; 51 | $this->value = $value; 52 | } 53 | 54 | public function getName(): string 55 | { 56 | return $this->name; 57 | } 58 | 59 | public function getValue(): ?string 60 | { 61 | return $this->value; 62 | } 63 | 64 | public function getExpires(): int 65 | { 66 | return $this->expires; 67 | } 68 | 69 | public function getMaxAge(): int 70 | { 71 | return $this->maxAge; 72 | } 73 | 74 | public function getPath(): ?string 75 | { 76 | return $this->path; 77 | } 78 | 79 | public function getDomain(): ?string 80 | { 81 | return $this->domain; 82 | } 83 | 84 | public function getSecure(): bool 85 | { 86 | return $this->secure; 87 | } 88 | 89 | public function getHttpOnly(): bool 90 | { 91 | return $this->httpOnly; 92 | } 93 | 94 | public function getSameSite(): ?SameSite 95 | { 96 | return $this->sameSite; 97 | } 98 | 99 | public function getPartitioned(): bool 100 | { 101 | return $this->partitioned; 102 | } 103 | 104 | public function withValue(?string $value = null): self 105 | { 106 | $clone = clone $this; 107 | 108 | $clone->value = $value; 109 | 110 | return $clone; 111 | } 112 | 113 | /** @param int|DateTimeInterface|string|null $expires */ 114 | private function resolveExpires($expires = null): int 115 | { 116 | if ($expires === null) { 117 | return 0; 118 | } 119 | 120 | if ($expires instanceof DateTimeInterface) { 121 | return (int) $expires->getTimestamp(); 122 | } 123 | 124 | if (is_numeric($expires)) { 125 | return (int) $expires; 126 | } 127 | 128 | $time = strtotime($expires); 129 | 130 | if (! is_int($time)) { 131 | throw new InvalidArgumentException(sprintf('Invalid expires "%s" provided', $expires)); 132 | } 133 | 134 | return $time; 135 | } 136 | 137 | /** @param int|string|DateTimeInterface|null $expires */ 138 | public function withExpires($expires = null): self 139 | { 140 | $expires = $this->resolveExpires($expires); 141 | 142 | $clone = clone $this; 143 | 144 | $clone->expires = $expires; 145 | 146 | return $clone; 147 | } 148 | 149 | public function rememberForever(): self 150 | { 151 | return $this->withExpires(new DateTime('+5 years')); 152 | } 153 | 154 | public function expire(): self 155 | { 156 | return $this->withExpires(new DateTime('-5 years')); 157 | } 158 | 159 | public function withMaxAge(?int $maxAge = null): self 160 | { 161 | $clone = clone $this; 162 | 163 | $clone->maxAge = (int) $maxAge; 164 | 165 | return $clone; 166 | } 167 | 168 | public function withPath(?string $path = null): self 169 | { 170 | $clone = clone $this; 171 | 172 | $clone->path = $path; 173 | 174 | return $clone; 175 | } 176 | 177 | public function withDomain(?string $domain = null): self 178 | { 179 | $clone = clone $this; 180 | 181 | $clone->domain = $domain; 182 | 183 | return $clone; 184 | } 185 | 186 | public function withSecure(bool $secure = true): self 187 | { 188 | $clone = clone $this; 189 | 190 | $clone->secure = $secure; 191 | 192 | return $clone; 193 | } 194 | 195 | public function withHttpOnly(bool $httpOnly = true): self 196 | { 197 | $clone = clone $this; 198 | 199 | $clone->httpOnly = $httpOnly; 200 | 201 | return $clone; 202 | } 203 | 204 | public function withSameSite(SameSite $sameSite): self 205 | { 206 | $clone = clone $this; 207 | 208 | $clone->sameSite = $sameSite; 209 | 210 | return $clone; 211 | } 212 | 213 | public function withoutSameSite(): self 214 | { 215 | $clone = clone $this; 216 | 217 | $clone->sameSite = null; 218 | 219 | return $clone; 220 | } 221 | 222 | public function withPartitioned(bool $partitioned = true): self 223 | { 224 | $clone = clone $this; 225 | 226 | $clone->partitioned = $partitioned; 227 | if ($partitioned) { 228 | $clone->secure = true; 229 | } 230 | 231 | return $clone; 232 | } 233 | 234 | public function __toString(): string 235 | { 236 | $cookieStringParts = [ 237 | urlencode($this->name) . '=' . urlencode((string) $this->value), 238 | ]; 239 | 240 | $cookieStringParts = $this->appendFormattedDomainPartIfSet($cookieStringParts); 241 | $cookieStringParts = $this->appendFormattedPathPartIfSet($cookieStringParts); 242 | $cookieStringParts = $this->appendFormattedExpiresPartIfSet($cookieStringParts); 243 | $cookieStringParts = $this->appendFormattedMaxAgePartIfSet($cookieStringParts); 244 | $cookieStringParts = $this->appendFormattedSecurePartIfSet($cookieStringParts); 245 | $cookieStringParts = $this->appendFormattedHttpOnlyPartIfSet($cookieStringParts); 246 | $cookieStringParts = $this->appendFormattedSameSitePartIfSet($cookieStringParts); 247 | $cookieStringParts = $this->appendFormattedPartitionedPartIfSet($cookieStringParts); 248 | 249 | return implode('; ', $cookieStringParts); 250 | } 251 | 252 | public static function create(string $name, ?string $value = null): self 253 | { 254 | return new static($name, $value); 255 | } 256 | 257 | public static function createRememberedForever(string $name, ?string $value = null): self 258 | { 259 | return static::create($name, $value)->rememberForever(); 260 | } 261 | 262 | /** 263 | * @deprecated Do not use this method. Will be removed in v4.0. 264 | * 265 | * If you want to remove a cookie, create it normally and call ->expire() 266 | * on the SetCookie object. 267 | */ 268 | public static function createExpired(string $name): self 269 | { 270 | return static::create($name)->expire(); 271 | } 272 | 273 | public static function fromSetCookieString(string $string): self 274 | { 275 | $rawAttributes = StringUtil::splitOnAttributeDelimiter($string); 276 | 277 | $rawAttribute = array_shift($rawAttributes); 278 | 279 | if (! is_string($rawAttribute)) { 280 | throw new InvalidArgumentException(sprintf( 281 | 'The provided cookie string "%s" must have at least one attribute', 282 | $string 283 | )); 284 | } 285 | 286 | [$cookieName, $cookieValue] = StringUtil::splitCookiePair($rawAttribute); 287 | 288 | $setCookie = new static($cookieName); 289 | 290 | if ($cookieValue !== null) { 291 | $setCookie = $setCookie->withValue($cookieValue); 292 | } 293 | 294 | while ($rawAttribute = array_shift($rawAttributes)) { 295 | $rawAttributePair = explode('=', $rawAttribute, 2); 296 | 297 | $attributeKey = $rawAttributePair[0]; 298 | $attributeValue = count($rawAttributePair) > 1 ? $rawAttributePair[1] : null; 299 | 300 | $attributeKey = strtolower($attributeKey); 301 | 302 | switch ($attributeKey) { 303 | case 'expires': 304 | $setCookie = $setCookie->withExpires($attributeValue); 305 | break; 306 | case 'max-age': 307 | $setCookie = $setCookie->withMaxAge((int) $attributeValue); 308 | break; 309 | case 'domain': 310 | $setCookie = $setCookie->withDomain($attributeValue); 311 | break; 312 | case 'path': 313 | $setCookie = $setCookie->withPath($attributeValue); 314 | break; 315 | case 'secure': 316 | $setCookie = $setCookie->withSecure(true); 317 | break; 318 | case 'httponly': 319 | $setCookie = $setCookie->withHttpOnly(true); 320 | break; 321 | case 'samesite': 322 | $setCookie = $setCookie->withSameSite(SameSite::fromString((string) $attributeValue)); 323 | break; 324 | case 'partitioned': 325 | $setCookie = $setCookie->withPartitioned(); 326 | break; 327 | } 328 | } 329 | 330 | return $setCookie; 331 | } 332 | 333 | /** 334 | * @param string[] $cookieStringParts 335 | * 336 | * @return string[] 337 | */ 338 | private function appendFormattedDomainPartIfSet(array $cookieStringParts): array 339 | { 340 | if ($this->domain) { 341 | $cookieStringParts[] = sprintf('Domain=%s', $this->domain); 342 | } 343 | 344 | return $cookieStringParts; 345 | } 346 | 347 | /** 348 | * @param string[] $cookieStringParts 349 | * 350 | * @return string[] 351 | */ 352 | private function appendFormattedPathPartIfSet(array $cookieStringParts): array 353 | { 354 | if ($this->path) { 355 | $cookieStringParts[] = sprintf('Path=%s', $this->path); 356 | } 357 | 358 | return $cookieStringParts; 359 | } 360 | 361 | /** 362 | * @param string[] $cookieStringParts 363 | * 364 | * @return string[] 365 | */ 366 | private function appendFormattedExpiresPartIfSet(array $cookieStringParts): array 367 | { 368 | if ($this->expires) { 369 | $cookieStringParts[] = sprintf('Expires=%s', gmdate('D, d M Y H:i:s T', $this->expires)); 370 | } 371 | 372 | return $cookieStringParts; 373 | } 374 | 375 | /** 376 | * @param string[] $cookieStringParts 377 | * 378 | * @return string[] 379 | */ 380 | private function appendFormattedMaxAgePartIfSet(array $cookieStringParts): array 381 | { 382 | if ($this->maxAge) { 383 | $cookieStringParts[] = sprintf('Max-Age=%s', $this->maxAge); 384 | } 385 | 386 | return $cookieStringParts; 387 | } 388 | 389 | /** 390 | * @param string[] $cookieStringParts 391 | * 392 | * @return string[] 393 | */ 394 | private function appendFormattedSecurePartIfSet(array $cookieStringParts): array 395 | { 396 | if ($this->secure) { 397 | $cookieStringParts[] = 'Secure'; 398 | } 399 | 400 | return $cookieStringParts; 401 | } 402 | 403 | /** 404 | * @param string[] $cookieStringParts 405 | * 406 | * @return string[] 407 | */ 408 | private function appendFormattedHttpOnlyPartIfSet(array $cookieStringParts): array 409 | { 410 | if ($this->httpOnly) { 411 | $cookieStringParts[] = 'HttpOnly'; 412 | } 413 | 414 | return $cookieStringParts; 415 | } 416 | 417 | /** 418 | * @param string[] $cookieStringParts 419 | * 420 | * @return string[] 421 | */ 422 | private function appendFormattedSameSitePartIfSet(array $cookieStringParts): array 423 | { 424 | if ($this->sameSite === null) { 425 | return $cookieStringParts; 426 | } 427 | 428 | $cookieStringParts[] = $this->sameSite->asString(); 429 | 430 | return $cookieStringParts; 431 | } 432 | 433 | /** 434 | * @param string[] $cookieStringParts 435 | * 436 | * @return string[] 437 | */ 438 | private function appendFormattedPartitionedPartIfSet(array $cookieStringParts): array 439 | { 440 | if ($this->partitioned) { 441 | $cookieStringParts[] = 'Partitioned'; 442 | } 443 | 444 | return $cookieStringParts; 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file, in reverse chronological order by release. 4 | 5 | ## 2.0.0 - 2018-07-11 6 | 7 | ### Added 8 | 9 | - [#32](https://github.com/dflydev/dflydev-fig-cookies/pull/32) all public API of the 10 | project now has extensive parameter and return type declarations. 11 | If you are using the library with `declare(strict_types=1);` in your codebase, you 12 | will need to run static analysis against your code to find possible type incompatibilities 13 | in method calls. 14 | If you are inheriting any of your code from this library, you will need to check 15 | that your type signatures respect variance and covariance with the symbols you are 16 | inheriting from. Here is a full list of the changes: 17 | ``` 18 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\Cookie#__construct() changed from no type to a non-contravariant string 19 | [BC] CHANGED: The parameter $value of Dflydev\FigCookies\Cookie#__construct() changed from no type to a non-contravariant ?string 20 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\Cookie#__construct() changed from no type to string 21 | [BC] CHANGED: The parameter $value of Dflydev\FigCookies\Cookie#__construct() changed from no type to ?string 22 | [BC] CHANGED: The return type of Dflydev\FigCookies\Cookie#getName() changed from no type to string 23 | [BC] CHANGED: The return type of Dflydev\FigCookies\Cookie#getValue() changed from no type to ?string 24 | [BC] CHANGED: The return type of Dflydev\FigCookies\Cookie#withValue() changed from no type to Dflydev\FigCookies\Cookie 25 | [BC] CHANGED: The parameter $value of Dflydev\FigCookies\Cookie#withValue() changed from no type to a non-contravariant ?string 26 | [BC] CHANGED: The parameter $value of Dflydev\FigCookies\Cookie#withValue() changed from no type to ?string 27 | [BC] CHANGED: The return type of Dflydev\FigCookies\Cookie#__toString() changed from no type to string 28 | [BC] CHANGED: The return type of Dflydev\FigCookies\Cookie::create() changed from no type to Dflydev\FigCookies\Cookie 29 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\Cookie::create() changed from no type to a non-contravariant string 30 | [BC] CHANGED: The parameter $value of Dflydev\FigCookies\Cookie::create() changed from no type to a non-contravariant ?string 31 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\Cookie::create() changed from no type to string 32 | [BC] CHANGED: The parameter $value of Dflydev\FigCookies\Cookie::create() changed from no type to ?string 33 | [BC] CHANGED: The return type of Dflydev\FigCookies\Cookie::listFromCookieString() changed from no type to array 34 | [BC] CHANGED: The parameter $string of Dflydev\FigCookies\Cookie::listFromCookieString() changed from no type to a non-contravariant string 35 | [BC] CHANGED: The parameter $string of Dflydev\FigCookies\Cookie::listFromCookieString() changed from no type to string 36 | [BC] CHANGED: The return type of Dflydev\FigCookies\Cookie::oneFromCookiePair() changed from no type to Dflydev\FigCookies\Cookie 37 | [BC] CHANGED: The parameter $string of Dflydev\FigCookies\Cookie::oneFromCookiePair() changed from no type to a non-contravariant string 38 | [BC] CHANGED: The parameter $string of Dflydev\FigCookies\Cookie::oneFromCookiePair() changed from no type to string 39 | [BC] CHANGED: The return type of Dflydev\FigCookies\FigRequestCookies::get() changed from no type to Dflydev\FigCookies\Cookie 40 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\FigRequestCookies::get() changed from no type to a non-contravariant string 41 | [BC] CHANGED: The parameter $value of Dflydev\FigCookies\FigRequestCookies::get() changed from no type to a non-contravariant ?string 42 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\FigRequestCookies::get() changed from no type to string 43 | [BC] CHANGED: The parameter $value of Dflydev\FigCookies\FigRequestCookies::get() changed from no type to ?string 44 | [BC] CHANGED: The return type of Dflydev\FigCookies\FigRequestCookies::set() changed from no type to Psr\Http\Message\RequestInterface 45 | [BC] CHANGED: The return type of Dflydev\FigCookies\FigRequestCookies::modify() changed from no type to Psr\Http\Message\RequestInterface 46 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\FigRequestCookies::modify() changed from no type to a non-contravariant string 47 | [BC] CHANGED: The parameter $modify of Dflydev\FigCookies\FigRequestCookies::modify() changed from no type to a non-contravariant callable 48 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\FigRequestCookies::modify() changed from no type to string 49 | [BC] CHANGED: The parameter $modify of Dflydev\FigCookies\FigRequestCookies::modify() changed from no type to callable 50 | [BC] CHANGED: The return type of Dflydev\FigCookies\FigRequestCookies::remove() changed from no type to Psr\Http\Message\RequestInterface 51 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\FigRequestCookies::remove() changed from no type to a non-contravariant string 52 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\FigRequestCookies::remove() changed from no type to string 53 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookies#has() changed from no type to bool 54 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\SetCookies#has() changed from no type to a non-contravariant string 55 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\SetCookies#has() changed from no type to string 56 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookies#get() changed from no type to ?Dflydev\FigCookies\SetCookie 57 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\SetCookies#get() changed from no type to a non-contravariant string 58 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\SetCookies#get() changed from no type to string 59 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookies#getAll() changed from no type to array 60 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookies#with() changed from no type to Dflydev\FigCookies\SetCookies 61 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookies#without() changed from no type to Dflydev\FigCookies\SetCookies 62 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\SetCookies#without() changed from no type to a non-contravariant string 63 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\SetCookies#without() changed from no type to string 64 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookies#renderIntoSetCookieHeader() changed from no type to Psr\Http\Message\ResponseInterface 65 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookies::fromSetCookieStrings() changed from no type to self 66 | [BC] CHANGED: The parameter $setCookieStrings of Dflydev\FigCookies\SetCookies::fromSetCookieStrings() changed from no type to a non-contravariant array 67 | [BC] CHANGED: The parameter $setCookieStrings of Dflydev\FigCookies\SetCookies::fromSetCookieStrings() changed from no type to array 68 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookies::fromResponse() changed from no type to Dflydev\FigCookies\SetCookies 69 | [BC] CHANGED: The return type of Dflydev\FigCookies\StringUtil::splitOnAttributeDelimiter() changed from no type to array 70 | [BC] CHANGED: The parameter $string of Dflydev\FigCookies\StringUtil::splitOnAttributeDelimiter() changed from no type to a non-contravariant string 71 | [BC] CHANGED: The parameter $string of Dflydev\FigCookies\StringUtil::splitOnAttributeDelimiter() changed from no type to string 72 | [BC] CHANGED: The return type of Dflydev\FigCookies\StringUtil::splitCookiePair() changed from no type to array 73 | [BC] CHANGED: The parameter $string of Dflydev\FigCookies\StringUtil::splitCookiePair() changed from no type to a non-contravariant string 74 | [BC] CHANGED: The parameter $string of Dflydev\FigCookies\StringUtil::splitCookiePair() changed from no type to string 75 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookie#getName() changed from no type to string 76 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookie#getValue() changed from no type to ?string 77 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookie#getExpires() changed from no type to int 78 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookie#getMaxAge() changed from no type to int 79 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookie#getPath() changed from no type to ?string 80 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookie#getDomain() changed from no type to ?string 81 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookie#getSecure() changed from no type to bool 82 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookie#getHttpOnly() changed from no type to bool 83 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookie#withValue() changed from no type to self 84 | [BC] CHANGED: The parameter $value of Dflydev\FigCookies\SetCookie#withValue() changed from no type to a non-contravariant ?string 85 | [BC] CHANGED: The parameter $value of Dflydev\FigCookies\SetCookie#withValue() changed from no type to ?string 86 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookie#withExpires() changed from no type to self 87 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookie#rememberForever() changed from no type to self 88 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookie#expire() changed from no type to self 89 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookie#withMaxAge() changed from no type to self 90 | [BC] CHANGED: The parameter $maxAge of Dflydev\FigCookies\SetCookie#withMaxAge() changed from no type to a non-contravariant ?int 91 | [BC] CHANGED: The parameter $maxAge of Dflydev\FigCookies\SetCookie#withMaxAge() changed from no type to ?int 92 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookie#withPath() changed from no type to self 93 | [BC] CHANGED: The parameter $path of Dflydev\FigCookies\SetCookie#withPath() changed from no type to a non-contravariant ?string 94 | [BC] CHANGED: The parameter $path of Dflydev\FigCookies\SetCookie#withPath() changed from no type to ?string 95 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookie#withDomain() changed from no type to self 96 | [BC] CHANGED: The parameter $domain of Dflydev\FigCookies\SetCookie#withDomain() changed from no type to a non-contravariant ?string 97 | [BC] CHANGED: The parameter $domain of Dflydev\FigCookies\SetCookie#withDomain() changed from no type to ?string 98 | [BC] CHANGED: Default parameter value for for parameter $secure of Dflydev\FigCookies\SetCookie#withSecure() changed from NULL to true 99 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookie#withSecure() changed from no type to self 100 | [BC] CHANGED: The parameter $secure of Dflydev\FigCookies\SetCookie#withSecure() changed from no type to a non-contravariant bool 101 | [BC] CHANGED: The parameter $secure of Dflydev\FigCookies\SetCookie#withSecure() changed from no type to bool 102 | [BC] CHANGED: Default parameter value for for parameter $httpOnly of Dflydev\FigCookies\SetCookie#withHttpOnly() changed from NULL to true 103 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookie#withHttpOnly() changed from no type to self 104 | [BC] CHANGED: The parameter $httpOnly of Dflydev\FigCookies\SetCookie#withHttpOnly() changed from no type to a non-contravariant bool 105 | [BC] CHANGED: The parameter $httpOnly of Dflydev\FigCookies\SetCookie#withHttpOnly() changed from no type to bool 106 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookie#__toString() changed from no type to string 107 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookie::create() changed from no type to self 108 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\SetCookie::create() changed from no type to a non-contravariant string 109 | [BC] CHANGED: The parameter $value of Dflydev\FigCookies\SetCookie::create() changed from no type to a non-contravariant ?string 110 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\SetCookie::create() changed from no type to string 111 | [BC] CHANGED: The parameter $value of Dflydev\FigCookies\SetCookie::create() changed from no type to ?string 112 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookie::createRememberedForever() changed from no type to self 113 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\SetCookie::createRememberedForever() changed from no type to a non-contravariant string 114 | [BC] CHANGED: The parameter $value of Dflydev\FigCookies\SetCookie::createRememberedForever() changed from no type to a non-contravariant ?string 115 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\SetCookie::createRememberedForever() changed from no type to string 116 | [BC] CHANGED: The parameter $value of Dflydev\FigCookies\SetCookie::createRememberedForever() changed from no type to ?string 117 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookie::createExpired() changed from no type to self 118 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\SetCookie::createExpired() changed from no type to a non-contravariant string 119 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\SetCookie::createExpired() changed from no type to string 120 | [BC] CHANGED: The return type of Dflydev\FigCookies\SetCookie::fromSetCookieString() changed from no type to self 121 | [BC] CHANGED: The parameter $string of Dflydev\FigCookies\SetCookie::fromSetCookieString() changed from no type to a non-contravariant string 122 | [BC] CHANGED: The parameter $string of Dflydev\FigCookies\SetCookie::fromSetCookieString() changed from no type to string 123 | [BC] CHANGED: The return type of Dflydev\FigCookies\FigResponseCookies::get() changed from no type to Dflydev\FigCookies\SetCookie 124 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\FigResponseCookies::get() changed from no type to a non-contravariant string 125 | [BC] CHANGED: The parameter $value of Dflydev\FigCookies\FigResponseCookies::get() changed from no type to a non-contravariant ?string 126 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\FigResponseCookies::get() changed from no type to string 127 | [BC] CHANGED: The parameter $value of Dflydev\FigCookies\FigResponseCookies::get() changed from no type to ?string 128 | [BC] CHANGED: The return type of Dflydev\FigCookies\FigResponseCookies::set() changed from no type to Psr\Http\Message\ResponseInterface 129 | [BC] CHANGED: The return type of Dflydev\FigCookies\FigResponseCookies::expire() changed from no type to Psr\Http\Message\ResponseInterface 130 | [BC] CHANGED: The parameter $cookieName of Dflydev\FigCookies\FigResponseCookies::expire() changed from no type to a non-contravariant string 131 | [BC] CHANGED: The parameter $cookieName of Dflydev\FigCookies\FigResponseCookies::expire() changed from no type to string 132 | [BC] CHANGED: The return type of Dflydev\FigCookies\FigResponseCookies::modify() changed from no type to Psr\Http\Message\ResponseInterface 133 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\FigResponseCookies::modify() changed from no type to a non-contravariant string 134 | [BC] CHANGED: The parameter $modify of Dflydev\FigCookies\FigResponseCookies::modify() changed from no type to a non-contravariant callable 135 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\FigResponseCookies::modify() changed from no type to string 136 | [BC] CHANGED: The parameter $modify of Dflydev\FigCookies\FigResponseCookies::modify() changed from no type to callable 137 | [BC] CHANGED: The return type of Dflydev\FigCookies\FigResponseCookies::remove() changed from no type to Psr\Http\Message\ResponseInterface 138 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\FigResponseCookies::remove() changed from no type to a non-contravariant string 139 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\FigResponseCookies::remove() changed from no type to string 140 | [BC] CHANGED: The return type of Dflydev\FigCookies\Cookies#has() changed from no type to bool 141 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\Cookies#has() changed from no type to a non-contravariant string 142 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\Cookies#has() changed from no type to string 143 | [BC] CHANGED: The return type of Dflydev\FigCookies\Cookies#get() changed from no type to ?Dflydev\FigCookies\Cookie 144 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\Cookies#get() changed from no type to a non-contravariant string 145 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\Cookies#get() changed from no type to string 146 | [BC] CHANGED: The return type of Dflydev\FigCookies\Cookies#getAll() changed from no type to array 147 | [BC] CHANGED: The return type of Dflydev\FigCookies\Cookies#with() changed from no type to Dflydev\FigCookies\Cookies 148 | [BC] CHANGED: The return type of Dflydev\FigCookies\Cookies#without() changed from no type to Dflydev\FigCookies\Cookies 149 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\Cookies#without() changed from no type to a non-contravariant string 150 | [BC] CHANGED: The parameter $name of Dflydev\FigCookies\Cookies#without() changed from no type to string 151 | [BC] CHANGED: The return type of Dflydev\FigCookies\Cookies#renderIntoCookieHeader() changed from no type to Psr\Http\Message\RequestInterface 152 | [BC] CHANGED: The return type of Dflydev\FigCookies\Cookies::fromCookieString() changed from no type to self 153 | [BC] CHANGED: The parameter $string of Dflydev\FigCookies\Cookies::fromCookieString() changed from no type to a non-contravariant string 154 | [BC] CHANGED: The parameter $string of Dflydev\FigCookies\Cookies::fromCookieString() changed from no type to string 155 | [BC] CHANGED: The return type of Dflydev\FigCookies\Cookies::fromRequest() changed from no type to Dflydev\FigCookies\Cookies 156 | ``` 157 | 158 | - [#31](https://github.com/dflydev/dflydev-fig-cookies/pull/31) A new abstraction was 159 | introduced to support `SameSite=Lax` and `SameSite=Strict` `Set-Cookie` header modifiers. 160 | 161 | ### Deprecated 162 | 163 | - Nothing. 164 | 165 | ### Removed 166 | 167 | - Nothing. 168 | 169 | ### Fixed 170 | 171 | - [#32](https://github.com/dflydev/dflydev-fig-cookies/pull/32) `SetCookie#withExpires()` 172 | will now reject any expiry time that cannot be parsed into a timestamp. 173 | - [#32](https://github.com/dflydev/dflydev-fig-cookies/pull/32) A `SetCookie` can no longer 174 | be constructed via `Dflydev\FigCookies\SetCookie::fromSetCookieString('')`: an empty string 175 | will now be rejected. 176 | --------------------------------------------------------------------------------