diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index d2df5560..f152e365 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -9,6 +9,7 @@ docs/ @JoshuaEstes
# Each component/contract needs a Team
/src/SonsOfPHP/**/Cache @JoshuaEstes
/src/SonsOfPHP/**/Clock @JoshuaEstes
+/src/SonsOfPHP/**/Cookie @JoshuaEstes
/src/SonsOfPHP/**/Common @JoshuaEstes
/src/SonsOfPHP/**/Cqrs @JoshuaEstes
/src/SonsOfPHP/**/EventDispatcher @JoshuaEstes
diff --git a/.github/labeler.yml b/.github/labeler.yml
index fb9d35e6..022aa9c3 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -10,6 +10,10 @@ Clock:
- docs/components/clock/*
- src/SonsOfPHP/**/Clock/*
+Cookie:
+ - docs/components/cookie/*
+ - src/SonsOfPHP/**/Cookie/*
+
Common:
- docs/components/common/*
- src/SonsOfPHP/**/Common/*
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f6a29d4d..35aaed74 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,7 @@ To get the diff between two versions, go to https://github.com/SonsOfPHP/sonsofp
* [PR #134](https://github.com/SonsOfPHP/sonsofphp/pull/134) [Pager] New Component
* [PR #170](https://github.com/SonsOfPHP/sonsofphp/pull/170) [Link] New Component (PSR-13)
* [PR #173](https://github.com/SonsOfPHP/sonsofphp/pull/173) [Money] Twig Bridge
+* [PR #181](https://github.com/SonsOfPHP/sonsofphp/pull/181) [Cookie] New Component and Contract
## [0.3.8]
diff --git a/Makefile b/Makefile
index 84a42c12..5cfa9d25 100644
--- a/Makefile
+++ b/Makefile
@@ -52,6 +52,9 @@ test-cache: phpunit
test-clock: PHPUNIT_TESTSUITE=clock
test-clock: phpunit
+test-cookie: PHPUNIT_TESTSUITE=cookie
+test-cookie: phpunit
+
test-cqrs: PHPUNIT_TESTSUITE=cqrs
test-cqrs: phpunit
@@ -102,6 +105,9 @@ coverage-cache: coverage
coverage-clock: PHPUNIT_TESTSUITE=clock
coverage-clock: coverage
+coverage-cookie: PHPUNIT_TESTSUITE=cookie
+coverage-cookie: coverage
+
coverage-cqrs: PHPUNIT_TESTSUITE=cqrs
coverage-cqrs: coverage
diff --git a/bard.json b/bard.json
index 8837d7fa..a3d28056 100644
--- a/bard.json
+++ b/bard.json
@@ -13,6 +13,10 @@
"path": "src/SonsOfPHP/Component/Clock",
"repository": "git@github.com:SonsOfPHP/clock.git"
},
+ {
+ "path": "src/SonsOfPHP/Component/Cookie",
+ "repository": "git@github.com:SonsOfPHP/cookie.git"
+ },
{
"path": "src/SonsOfPHP/Component/Cqrs",
"repository": "git@github.com:SonsOfPHP/cqrs.git"
@@ -101,6 +105,10 @@
"path": "src/SonsOfPHP/Contract/Common",
"repository": "git@github.com:SonsOfPHP/common-contract.git"
},
+ {
+ "path": "src/SonsOfPHP/Contract/Cookie",
+ "repository": "git@github.com:SonsOfPHP/cookie-contract.git"
+ },
{
"path": "src/SonsOfPHP/Contract/Cqrs",
"repository": "git@github.com:SonsOfPHP/cqrs-contract.git"
diff --git a/composer.json b/composer.json
index d42f8f7f..40bf6b78 100644
--- a/composer.json
+++ b/composer.json
@@ -47,7 +47,8 @@
"psr/log-implementation": "^1.0 || ^2.0 || ^3.0",
"sonsofphp/logger-implementation": "0.3.x-dev",
"sonsofphp/pager-implementation": "0.3.x-dev",
- "psr/link-implementation": "^1.0 || ^2.0"
+ "psr/link-implementation": "^1.0 || ^2.0",
+ "sonsofphp/cookie-implementation": "0.3.x-dev"
},
"require": {
"php": ">=8.1",
@@ -74,7 +75,8 @@
"twig/twig": "^3.0",
"ext-intl": "*",
"doctrine/collections": "^2",
- "doctrine/orm": "^2"
+ "doctrine/orm": "^2",
+ "sonsofphp/cookie-contract": "0.3.x-dev"
},
"replace": {
"sonsofphp/bard": "self.version",
@@ -110,13 +112,16 @@
"sonsofphp/money-twig": "self.version",
"sonsofphp/pager-doctrine-collections": "self.version",
"sonsofphp/pager-doctrine-dbal": "self.version",
- "sonsofphp/pager-doctrine-orm": "self.version"
+ "sonsofphp/pager-doctrine-orm": "self.version",
+ "sonsofphp/cookie": "self.version",
+ "sonsofphp/cookie-contract": "self.version"
},
"autoload": {
"psr-4": {
"SonsOfPHP\\Bard\\": "src/SonsOfPHP/Bard/src",
"SonsOfPHP\\Component\\Cache\\": "src/SonsOfPHP/Component/Cache",
"SonsOfPHP\\Component\\Clock\\": "src/SonsOfPHP/Component/Clock",
+ "SonsOfPHP\\Component\\Cookie\\": "src/SonsOfPHP/Component/Cookie",
"SonsOfPHP\\Component\\Cqrs\\": "src/SonsOfPHP/Component/Cqrs",
"SonsOfPHP\\Bundle\\Cqrs\\": "src/SonsOfPHP/Bundle/Cqrs",
"SonsOfPHP\\Bridge\\Symfony\\Cqrs\\": "src/SonsOfPHP/Bridge/Symfony/Cqrs",
@@ -139,6 +144,7 @@
"SonsOfPHP\\Bridge\\Doctrine\\ORM\\Pager\\": "src/SonsOfPHP/Bridge/Doctrine/ORM/Pager",
"SonsOfPHP\\Component\\Version\\": "src/SonsOfPHP/Component/Version",
"SonsOfPHP\\Contract\\Common\\": "src/SonsOfPHP/Contract/Common",
+ "SonsOfPHP\\Contract\\Cookie\\": "src/SonsOfPHP/Contract/Cookie",
"SonsOfPHP\\Contract\\Cqrs\\": "src/SonsOfPHP/Contract/Cqrs",
"SonsOfPHP\\Contract\\EventSourcing\\": "src/SonsOfPHP/Contract/EventSourcing",
"SonsOfPHP\\Contract\\FeatureToggle\\": "src/SonsOfPHP/Contract/FeatureToggle",
@@ -152,6 +158,7 @@
"src/SonsOfPHP/Bard/Tests",
"src/SonsOfPHP/Component/Cache/Tests",
"src/SonsOfPHP/Component/Clock/Tests",
+ "src/SonsOfPHP/Component/Cookie/Tests",
"src/SonsOfPHP/Component/Cqrs/Tests",
"src/SonsOfPHP/Bundle/Cqrs/Tests",
"src/SonsOfPHP/Bridge/Symfony/Cqrs/Tests",
diff --git a/docs/components/cookie/index.md b/docs/components/cookie/index.md
new file mode 100644
index 00000000..19aa6f17
--- /dev/null
+++ b/docs/components/cookie/index.md
@@ -0,0 +1,33 @@
+---
+title: Cookie
+---
+
+## Installation
+
+```shell
+composer require sonsofphp/cookie
+```
+
+## Usage
+
+A Cookie is treated as a value object. This means that if two cookie objects
+have the same name and value, they will be considered equal. They are also
+considered to be immutable.
+
+```php
+getHeaderValue());
+// OR
+// header('Set-Cookie: ' . (string) $cookie);
+
+// Set various attributes
+$cookie = $cookie
+ ->withPath('/')
+ ->withDomain('docs.sonsofphp.com')
+;
+```
diff --git a/docs/contracts/cookie/index.md b/docs/contracts/cookie/index.md
new file mode 100644
index 00000000..77157f49
--- /dev/null
+++ b/docs/contracts/cookie/index.md
@@ -0,0 +1,9 @@
+---
+title: Cookie
+---
+
+## Installation
+
+```shell
+composer require sonsofphp/cookie-contract
+```
diff --git a/mkdocs.yml b/mkdocs.yml
index b8b29ee1..9c0fc364 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -79,6 +79,7 @@ nav:
- components/index.md
- Cache: components/cache/index.md
- Clock: components/clock/index.md
+ - Cookie: components/cookie/index.md
- CQRS:
- components/cqrs/index.md
- Event Dispatcher: components/event-dispatcher/index.md
@@ -118,5 +119,6 @@ nav:
- Contracts:
- contracts/index.md
- Common: contracts/common/index.md
+ - Cookie: contracts/cookie/index.md
- Cqrs: contracts/cqrs/index.md
- Pager: contracts/pager/index.md
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 5ab54b33..db3a9659 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -30,6 +30,10 @@
src/SonsOfPHP/Component/Clock/Tests
+
+ src/SonsOfPHP/Component/Cookie/Tests
+
+
src/SonsOfPHP/Bridge/*/Cqrs/Tests
diff --git a/src/SonsOfPHP/Component/Cookie/.gitattributes b/src/SonsOfPHP/Component/Cookie/.gitattributes
new file mode 100644
index 00000000..84c7add0
--- /dev/null
+++ b/src/SonsOfPHP/Component/Cookie/.gitattributes
@@ -0,0 +1,4 @@
+/Tests export-ignore
+/phpunit.xml.dist export-ignore
+/.gitattributes export-ignore
+/.gitignore export-ignore
diff --git a/src/SonsOfPHP/Component/Cookie/.gitignore b/src/SonsOfPHP/Component/Cookie/.gitignore
new file mode 100644
index 00000000..5414c2c6
--- /dev/null
+++ b/src/SonsOfPHP/Component/Cookie/.gitignore
@@ -0,0 +1,3 @@
+composer.lock
+phpunit.xml
+vendor/
diff --git a/src/SonsOfPHP/Component/Cookie/Cookie.php b/src/SonsOfPHP/Component/Cookie/Cookie.php
new file mode 100644
index 00000000..baf08af6
--- /dev/null
+++ b/src/SonsOfPHP/Component/Cookie/Cookie.php
@@ -0,0 +1,201 @@
+
+ */
+final class Cookie implements CookieInterface
+{
+ public function __construct(
+ private string $name,
+ private string $value = '',
+ private array $attributes = [],
+ ) {}
+
+ public function __toString(): string
+ {
+ return $this->getHeaderValue();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getHeaderValue(): string
+ {
+ $cookie = $this->name . '=' . $this->value;
+
+ foreach ($this->attributes as $key => $val) {
+ if (is_bool($val) && true === $val) {
+ $cookie .= '; ' . $key;
+ }
+
+ if (!is_bool($val)) {
+ $cookie .= '; ' . $key . '=' . $val;
+ }
+ }
+
+ return $cookie;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withName(string $name): static
+ {
+ if ($name === $this->name) {
+ return $this;
+ }
+
+ $that = clone $this;
+ $that->name = $name;
+
+ return $that;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withValue(string $value): static
+ {
+ if ($value === $this->value) {
+ return $this;
+ }
+
+ $that = clone $this;
+ $that->value = $value;
+
+ return $that;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withPath(string $path): static
+ {
+ if (array_key_exists('Path', $this->attributes) && $path === $this->attributes['Path']) {
+ return $this;
+ }
+
+ $that = clone $this;
+ $that->attributes['Path'] = $path;
+
+ return $that;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withDomain(string $domain): static
+ {
+ if (array_key_exists('Domain', $this->attributes) && $domain === $this->attributes['Domain']) {
+ return $this;
+ }
+
+ $that = clone $this;
+ $that->attributes['Domain'] = $domain;
+
+ return $that;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withSecure(bool $secure): static
+ {
+ if (array_key_exists('Secure', $this->attributes) && $secure === $this->attributes['Secure']) {
+ return $this;
+ }
+
+ $that = clone $this;
+ $that->attributes['Secure'] = $secure;
+
+ return $that;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withHttpOnly(bool $httpOnly): static
+ {
+ if (array_key_exists('HttpOnly', $this->attributes) && $httpOnly === $this->attributes['HttpOnly']) {
+ return $this;
+ }
+
+ $that = clone $this;
+ $that->attributes['HttpOnly'] = $httpOnly;
+
+ return $that;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withSameSite(string $sameSite): static
+ {
+ if (array_key_exists('SameSite', $this->attributes) && $sameSite === $this->attributes['SameSite']) {
+ return $this;
+ }
+
+ if (!in_array(strtolower($sameSite), ['none', 'lax', 'strict'])) {
+ throw new CookieException('Invalid value for $sameSite');
+ }
+
+ $that = clone $this;
+ $that->attributes['SameSite'] = $sameSite;
+
+ return $that;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withPartitioned(bool $partitioned): static
+ {
+ if (array_key_exists('Partitioned', $this->attributes) && $partitioned === $this->attributes['Partitioned']) {
+ return $this;
+ }
+
+ $that = clone $this;
+ $that->attributes['Partitioned'] = $partitioned;
+
+ return $that;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withExpires(\DateTimeImmutable $expires): static
+ {
+ $expires = $expires->format('r');
+
+ if (array_key_exists('Expires', $this->attributes) && $expires === $this->attributes['Expires']) {
+ return $this;
+ }
+
+ $that = clone $this;
+ $that->attributes['Expires'] = $expires;
+
+ return $that;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withMaxAge(int $maxAge): static
+ {
+ if (array_key_exists('Max-Age', $this->attributes) && $maxAge === $this->attributes['Max-Age']) {
+ return $this;
+ }
+
+ $that = clone $this;
+ $that->attributes['Max-Age'] = $maxAge;
+
+ return $that;
+ }
+}
diff --git a/src/SonsOfPHP/Component/Cookie/CookieManager.php b/src/SonsOfPHP/Component/Cookie/CookieManager.php
new file mode 100644
index 00000000..1357bf85
--- /dev/null
+++ b/src/SonsOfPHP/Component/Cookie/CookieManager.php
@@ -0,0 +1,43 @@
+
+ */
+final class CookieManager implements CookieManagerInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $name): CookieInterface
+ {
+ $cookie = new Cookie($name);
+
+ if ($this->has($name)) {
+ $cookie = $cookie->withValue($_COOKIE[$name]);
+ }
+
+ return $cookie;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has(string $name): CookieInterface
+ {
+ return array_key_exists($name, $_COOKIE);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ //public function remove(string $name): CookieInterface
+ //{
+ //}
+}
diff --git a/src/SonsOfPHP/Component/Cookie/Exception/CookieException.php b/src/SonsOfPHP/Component/Cookie/Exception/CookieException.php
new file mode 100644
index 00000000..9c454982
--- /dev/null
+++ b/src/SonsOfPHP/Component/Cookie/Exception/CookieException.php
@@ -0,0 +1,12 @@
+
+ */
+class CookieException extends \Exception implements CookieExceptionInterface {}
diff --git a/src/SonsOfPHP/Component/Cookie/LICENSE b/src/SonsOfPHP/Component/Cookie/LICENSE
new file mode 100644
index 00000000..39238382
--- /dev/null
+++ b/src/SonsOfPHP/Component/Cookie/LICENSE
@@ -0,0 +1,19 @@
+Copyright 2022 to Present Joshua Estes
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/src/SonsOfPHP/Component/Cookie/README.md b/src/SonsOfPHP/Component/Cookie/README.md
new file mode 100644
index 00000000..2245d139
--- /dev/null
+++ b/src/SonsOfPHP/Component/Cookie/README.md
@@ -0,0 +1,16 @@
+Sons of PHP - Cookie
+====================
+
+## Learn More
+
+* [Documentation][docs]
+* [Contributing][contributing]
+* [Report Issues][issues] and [Submit Pull Requests][pull-requests] in the [Mother Repository][mother-repo]
+* Get Help & Support using [Discussions][discussions]
+
+[discussions]: https://github.com/orgs/SonsOfPHP/discussions
+[mother-repo]: https://github.com/SonsOfPHP/sonsofphp
+[contributing]: https://docs.sonsofphp.com/contributing/
+[docs]: https://docs.sonsofphp.com/components/cookie/
+[issues]: https://github.com/SonsOfPHP/sonsofphp/issues?q=is%3Aopen+is%3Aissue+label%3ACookie
+[pull-requests]: https://github.com/SonsOfPHP/sonsofphp/pulls?q=is%3Aopen+is%3Apr+label%3ACookie
diff --git a/src/SonsOfPHP/Component/Cookie/Tests/CookieTest.php b/src/SonsOfPHP/Component/Cookie/Tests/CookieTest.php
new file mode 100644
index 00000000..6bde7a6a
--- /dev/null
+++ b/src/SonsOfPHP/Component/Cookie/Tests/CookieTest.php
@@ -0,0 +1,174 @@
+assertInstanceOf(CookieInterface::class, $cookie);
+ }
+
+ /**
+ * @covers ::withName
+ */
+ public function testWithName(): void
+ {
+ $cookie = new Cookie('test');
+
+ $this->assertSame($cookie, $cookie->withName('test'));
+ $this->assertNotSame($cookie, $cookie->withName('test2'));
+ }
+
+ /**
+ * @covers ::withValue
+ */
+ public function testWithValue(): void
+ {
+ $cookie = new Cookie('test', 'value');
+
+ $this->assertSame($cookie, $cookie->withValue('value'));
+ $this->assertNotSame($cookie, $cookie->withValue('value2'));
+ }
+
+ /**
+ * @covers ::withPath
+ */
+ public function testWithPath(): void
+ {
+ $cookie = (new Cookie('test'))->withPath('/');
+
+ $this->assertSame($cookie, $cookie->withPath('/'));
+ $this->assertNotSame($cookie, $cookie->withPath('/testing'));
+ }
+
+ /**
+ * @covers ::withDomain
+ */
+ public function testWithDomain(): void
+ {
+ $cookie = (new Cookie('test'))->withDomain('sonsofphp.com');
+
+ $this->assertSame($cookie, $cookie->withDomain('sonsofphp.com'));
+ $this->assertNotSame($cookie, $cookie->withDomain('docs.sonsofphp.com'));
+ }
+
+ /**
+ * @covers ::withSecure
+ */
+ public function testWithSecure(): void
+ {
+ $cookie = (new Cookie('test'))->withSecure(false);
+
+ $this->assertSame($cookie, $cookie->withSecure(false));
+ $this->assertNotSame($cookie, $cookie->withSecure(true));
+ }
+
+ /**
+ * @covers ::withHttpOnly
+ */
+ public function testWithHttpOnly(): void
+ {
+ $cookie = (new Cookie('test'))->withHttpOnly(false);
+
+ $this->assertSame($cookie, $cookie->withHttpOnly(false));
+ $this->assertNotSame($cookie, $cookie->withHttpOnly(true));
+ }
+
+ /**
+ * @covers ::withSameSite
+ */
+ public function testWithSameSite(): void
+ {
+ $cookie = (new Cookie('test'))->withSameSite('none');
+
+ $this->assertSame($cookie, $cookie->withSameSite('none'));
+ $this->assertNotSame($cookie, $cookie->withSameSite('strict'));
+ }
+
+ /**
+ * @covers ::withSameSite
+ */
+ public function testWithSameSiteWithThrowExceptionOnInvalidArgument(): void
+ {
+ $cookie = new Cookie('test');
+
+ $this->expectException(CookieExceptionInterface::class);
+ $cookie->withSameSite('not valid');
+ }
+
+ /**
+ * @covers ::withPartitioned
+ */
+ public function testWithPartitioned(): void
+ {
+ $cookie = (new Cookie('test'))->withPartitioned(false);
+
+ $this->assertSame($cookie, $cookie->withPartitioned(false));
+ $this->assertNotSame($cookie, $cookie->withPartitioned(true));
+ }
+
+ /**
+ * @covers ::getHeaderValue
+ */
+ public function testHeaderValue(): void
+ {
+ $cookie = (new Cookie('name', 'value'))->withPath('/')->withPartitioned(false)->withHttpOnly(true);
+
+ $this->assertSame('name=value; Path=/; HttpOnly', $cookie->getHeaderValue());
+ }
+
+ /**
+ * @covers ::__toString
+ */
+ public function testToString(): void
+ {
+ $cookie = (new Cookie('name', 'value'))->withPath('/')->withPartitioned(false)->withHttpOnly(true);
+
+ $this->assertSame($cookie->getHeaderValue(), (string) $cookie);
+ }
+
+ /**
+ * @covers ::withMaxAge
+ */
+ public function testMaxAge(): void
+ {
+ $cookie = (new Cookie('name', 'value'))->withMaxAge(0);
+
+ $this->assertSame($cookie, $cookie->withMaxAge(0));
+ $this->assertNotSame($cookie, $cookie->withMaxAge(420));
+
+ $this->assertStringContainsString('Max-Age=', $cookie->getHeaderValue());
+ }
+
+ /**
+ * @covers ::withExpires
+ */
+ public function testExpires(): void
+ {
+ $timestamp = new \DateTimeImmutable('2020-04-20 04:20:00');
+ $cookie = (new Cookie('name', 'value'))->withExpires($timestamp);
+
+ $this->assertSame($cookie, $cookie->withExpires($timestamp));
+ $this->assertNotSame($cookie, $cookie->withExpires(new \DateTimeImmutable()));
+
+ $this->assertStringContainsString('Expires=Mon, 20 Apr 2020 04:20:00 +0000', $cookie->getHeaderValue());
+ }
+}
diff --git a/src/SonsOfPHP/Component/Cookie/composer.json b/src/SonsOfPHP/Component/Cookie/composer.json
new file mode 100644
index 00000000..915ec3e9
--- /dev/null
+++ b/src/SonsOfPHP/Component/Cookie/composer.json
@@ -0,0 +1,54 @@
+{
+ "name": "sonsofphp/cookie",
+ "type": "library",
+ "description": "Manage Cookies with ease",
+ "keywords": [
+ "cookie"
+ ],
+ "homepage": "https://github.com/SonsOfPHP/cookie",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Joshua Estes",
+ "email": "joshua@sonsofphp.com"
+ }
+ ],
+ "support": {
+ "issues": "https://github.com/SonsOfPHP/sonsofphp/issues",
+ "forum": "https://github.com/orgs/SonsOfPHP/discussions",
+ "docs": "https://docs.sonsofphp.com/components/cookie"
+ },
+ "autoload": {
+ "psr-4": {
+ "SonsOfPHP\\Component\\Cookie\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "require": {
+ "php": ">=8.1",
+ "sonsofphp/cookie-contract": "0.3.x-dev"
+ },
+ "provide": {
+ "sonsofphp/cookie-implementation": "0.3.x-dev"
+ },
+ "extra": {
+ "sort-packages": true,
+ "branch-alias": {
+ "dev-main": "0.3.x-dev"
+ }
+ },
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/JoshuaEstes"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/subscription/pkg/packagist-sonsofphp-sonsofphp"
+ }
+ ]
+}
diff --git a/src/SonsOfPHP/Component/Pager/README.md b/src/SonsOfPHP/Component/Pager/README.md
index 79b97e64..94b314db 100644
--- a/src/SonsOfPHP/Component/Pager/README.md
+++ b/src/SonsOfPHP/Component/Pager/README.md
@@ -1,5 +1,5 @@
-Sons of PHP - Logger
-====================
+Sons of PHP - Pager
+===================
## Learn More
@@ -11,6 +11,6 @@ Sons of PHP - Logger
[discussions]: https://github.com/orgs/SonsOfPHP/discussions
[mother-repo]: https://github.com/SonsOfPHP/sonsofphp
[contributing]: https://docs.sonsofphp.com/contributing/
-[docs]: https://docs.sonsofphp.com/components/logger/
-[issues]: https://github.com/SonsOfPHP/sonsofphp/issues?q=is%3Aopen+is%3Aissue+label%3ALogger
-[pull-requests]: https://github.com/SonsOfPHP/sonsofphp/pulls?q=is%3Aopen+is%3Apr+label%3ALogger
+[docs]: https://docs.sonsofphp.com/components/pager/
+[issues]: https://github.com/SonsOfPHP/sonsofphp/issues?q=is%3Aopen+is%3Aissue+label%3APager
+[pull-requests]: https://github.com/SonsOfPHP/sonsofphp/pulls?q=is%3Aopen+is%3Apr+label%3APager
diff --git a/src/SonsOfPHP/Contract/Cookie/.gitattributes b/src/SonsOfPHP/Contract/Cookie/.gitattributes
new file mode 100644
index 00000000..3a01b372
--- /dev/null
+++ b/src/SonsOfPHP/Contract/Cookie/.gitattributes
@@ -0,0 +1,2 @@
+/.gitattributes export-ignore
+/.gitignore export-ignore
diff --git a/src/SonsOfPHP/Contract/Cookie/.gitignore b/src/SonsOfPHP/Contract/Cookie/.gitignore
new file mode 100644
index 00000000..d8a7996a
--- /dev/null
+++ b/src/SonsOfPHP/Contract/Cookie/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor/
diff --git a/src/SonsOfPHP/Contract/Cookie/CookieExceptionInterface.php b/src/SonsOfPHP/Contract/Cookie/CookieExceptionInterface.php
new file mode 100644
index 00000000..90c06107
--- /dev/null
+++ b/src/SonsOfPHP/Contract/Cookie/CookieExceptionInterface.php
@@ -0,0 +1,10 @@
+
+ */
+interface CookieExceptionInterface {}
diff --git a/src/SonsOfPHP/Contract/Cookie/CookieInterface.php b/src/SonsOfPHP/Contract/Cookie/CookieInterface.php
new file mode 100644
index 00000000..a000228e
--- /dev/null
+++ b/src/SonsOfPHP/Contract/Cookie/CookieInterface.php
@@ -0,0 +1,103 @@
+
+ */
+interface CookieInterface extends \Stringable
+{
+ /**
+ * Returns the Header Value for "Set-Cookie"
+ *
+ * __toString and this method MUST return the same value
+ */
+ public function getHeaderValue(): string;
+
+ /**
+ * Set the cookie name
+ *
+ * If the $name is the same, it will return the same object, however if the
+ * $name is different than the current $name, it will return a new instance
+ * of cookie
+ *
+ * @throws CookieExceptionInterface if $name is invalid
+ */
+ public function withName(string $name): static;
+
+ /**
+ * Set the cookie value
+ *
+ * @throws CookieExceptionInterface if $value is invalid
+ */
+ public function withValue(string $value): static;
+
+ /**
+ * Set the "Path="
+ *
+ * If path has the same value as the existing path, this will not return a
+ * new object
+ */
+ public function withPath(string $path): static;
+
+ /**
+ * Set the "Domain="
+ *
+ * If domain has the same value as the existing domain, this will not return a
+ * new object
+ */
+ public function withDomain(string $domain): static;
+
+ /**
+ * Set the "SameSize="
+ *
+ * If sameSite has the same value as the existing sameSite, this will not return a
+ * new object
+ *
+ * Only valid arguments allowed:
+ * - Strict
+ * - Lax
+ * - None
+ *
+ * @throws CookieExceptionInterface if argument is invalid
+ */
+ public function withSameSite(string $sameSite): static;
+
+ /**
+ * Set "Expires="
+ *
+ * If expires has the same value as the existing expires, this will not return a
+ * new object
+ *
+ * @throws CookieExceptionInterface when $expires is invalid
+ */
+ public function withExpires(\DateTimeImmutable $expires): static;
+
+ /**
+ * Set "Max-Age="
+ *
+ * This is the number of seconds before the cookie will expire. For
+ * example, if "69" is passed in, it will expire in one minute and
+ * 9 seconds.
+ *
+ * @throws CookieExceptionInterface when $maxAge is invalid
+ */
+ public function withMaxAge(int $maxAge): static;
+
+ /**
+ * Set "Secure"
+ */
+ public function withSecure(bool $secure): static;
+
+ /**
+ * Set "HttpOnly"
+ */
+ public function withHttpOnly(bool $httpOnly): static;
+
+ /**
+ * Set "Partitioned"
+ */
+ public function withPartitioned(bool $partitioned): static;
+}
diff --git a/src/SonsOfPHP/Contract/Cookie/CookieManagerInterface.php b/src/SonsOfPHP/Contract/Cookie/CookieManagerInterface.php
new file mode 100644
index 00000000..2fa6df7e
--- /dev/null
+++ b/src/SonsOfPHP/Contract/Cookie/CookieManagerInterface.php
@@ -0,0 +1,43 @@
+
+ */
+interface CookieManagerInterface
+{
+ /**
+ * If a cookie does not exists, this will create a new Cookie object and
+ * return that.
+ *
+ * Example:
+ * $cookie = $manager->get('PHPSESSID');
+ */
+ public function get(string $name): CookieInterface;
+
+ /**
+ * Checks to see if "$name" exists in the request cookies
+ *
+ * Example:
+ * if ($manager->has('PHPSESSID')) {
+ * // ...
+ * }
+ */
+ public function has(string $name): bool;
+
+ /**
+ * Removes the cookie, this will remove from the browser as well
+ *
+ * If this return true, everything went ok, if it returns false, something
+ * is broken. If thise throws an exception, something really fucked up
+ * happened
+ *
+ * @throws CookieExceptionInterface
+ */
+ //public function remove(string $name): bool;
+}
diff --git a/src/SonsOfPHP/Contract/Cookie/LICENSE b/src/SonsOfPHP/Contract/Cookie/LICENSE
new file mode 100644
index 00000000..39238382
--- /dev/null
+++ b/src/SonsOfPHP/Contract/Cookie/LICENSE
@@ -0,0 +1,19 @@
+Copyright 2022 to Present Joshua Estes
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/src/SonsOfPHP/Contract/Cookie/README.md b/src/SonsOfPHP/Contract/Cookie/README.md
new file mode 100644
index 00000000..4f25aaa6
--- /dev/null
+++ b/src/SonsOfPHP/Contract/Cookie/README.md
@@ -0,0 +1,16 @@
+Sons of PHP - Cookie Contract
+=============================
+
+## Learn More
+
+* [Documentation][docs]
+* [Contributing][contributing]
+* [Report Issues][issues] and [Submit Pull Requests][pull-requests] in the [Mother Repository][mother-repo]
+* Get Help & Support using [Discussions][discussions]
+
+[discussions]: https://github.com/orgs/SonsOfPHP/discussions
+[mother-repo]: https://github.com/SonsOfPHP/sonsofphp
+[contributing]: https://docs.sonsofphp.com/contributing/
+[docs]: https://docs.sonsofphp.com/contracts/cookie/
+[issues]: https://github.com/SonsOfPHP/sonsofphp/issues?q=is%3Aopen+is%3Aissue+label%3ACookie
+[pull-requests]: https://github.com/SonsOfPHP/sonsofphp/pulls?q=is%3Aopen+is%3Apr+label%3ACookie
diff --git a/src/SonsOfPHP/Contract/Cookie/composer.json b/src/SonsOfPHP/Contract/Cookie/composer.json
new file mode 100644
index 00000000..410d0c89
--- /dev/null
+++ b/src/SonsOfPHP/Contract/Cookie/composer.json
@@ -0,0 +1,52 @@
+{
+ "name": "sonsofphp/cookie-contract",
+ "type": "library",
+ "description": "",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "homepage": "https://github.com/SonsOfPHP/cookie-contract",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Joshua Estes",
+ "email": "joshua@sonsofphp.com"
+ }
+ ],
+ "support": {
+ "issues": "https://github.com/SonsOfPHP/sonsofphp/issues",
+ "forum": "https://github.com/orgs/SonsOfPHP/discussions",
+ "docs": "https://docs.sonsofphp.com/contracts/cookie"
+ },
+ "autoload": {
+ "psr-4": {
+ "SonsOfPHP\\Contract\\Cookie\\": ""
+ }
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "require": {
+ "php": ">=8.1"
+ },
+ "extra": {
+ "sort-packages": true,
+ "branch-alias": {
+ "dev-main": "0.3.x-dev"
+ }
+ },
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/JoshuaEstes"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/subscription/pkg/packagist-sonsofphp-sonsofphp"
+ }
+ ]
+}