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" + } + ] +}