diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d2df5560..47caa7eb 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -9,6 +9,8 @@ docs/ @JoshuaEstes # Each component/contract needs a Team /src/SonsOfPHP/**/Cache @JoshuaEstes /src/SonsOfPHP/**/Clock @JoshuaEstes +/src/SonsOfPHP/**/Container @JoshuaEstes +/src/SonsOfPHP/**/Cookie @JoshuaEstes /src/SonsOfPHP/**/Common @JoshuaEstes /src/SonsOfPHP/**/Cqrs @JoshuaEstes /src/SonsOfPHP/**/EventDispatcher @JoshuaEstes @@ -16,10 +18,12 @@ docs/ @JoshuaEstes /src/SonsOfPHP/**/FeatureToggle @JoshuaEstes /src/SonsOfPHP/**/Filesystem @JoshuaEstes /src/SonsOfPHP/**/HttpFactory @JoshuaEstes +/src/SonsOfPHP/**/HttpHandler @JoshuaEstes /src/SonsOfPHP/**/HttpMessage @JoshuaEstes /src/SonsOfPHP/**/Json @JoshuaEstes /src/SonsOfPHP/**/Link @JoshuaEstes /src/SonsOfPHP/**/Logger @JoshuaEstes +/src/SonsOfPHP/**/Mailer @JoshuaEstes /src/SonsOfPHP/**/Money @JoshuaEstes /src/SonsOfPHP/**/Pager @JoshuaEstes /src/SonsOfPHP/**/Version @JoshuaEstes diff --git a/.github/labeler.yml b/.github/labeler.yml index fb9d35e6..e0ece348 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -10,6 +10,14 @@ Clock: - docs/components/clock/* - src/SonsOfPHP/**/Clock/* +Container: + - docs/components/container/* + - src/SonsOfPHP/**/Container/* + +Cookie: + - docs/components/cookie/* + - src/SonsOfPHP/**/Cookie/* + Common: - docs/components/common/* - src/SonsOfPHP/**/Common/* @@ -38,6 +46,10 @@ HttpFactory: - docs/components/http-factory/* - src/SonsOfPHP/**/HttpFactory/* +HttpHandler: + - docs/components/http-handler/* + - src/SonsOfPHP/**/HttpHandler/* + HttpMessage: - docs/components/http-message/* - src/SonsOfPHP/**/HttpMessage/* @@ -54,6 +66,10 @@ Logger: - docs/components/logger/* - src/SonsOfPHP/**/Logger/* +Mailer: + - docs/components/mailer/* + - src/SonsOfPHP/**/Mailer/* + Money: - docs/components/money/* - src/SonsOfPHP/**/Money/* diff --git a/.gitignore b/.gitignore index b5a07c55..5ced1162 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /docs/coverage/ +/docs/infection/ /site/ vendor/ /.php-cs-fixer.cache @@ -10,3 +11,4 @@ phpunit.xml composer.phar packages.json results.sarif +infection.log diff --git a/CHANGELOG.md b/CHANGELOG.md index 671c4f57..691a6733 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,11 @@ To get the diff between two versions, go to https://github.com/SonsOfPHP/sonsofp * [PR #133](https://github.com/SonsOfPHP/sonsofphp/pull/133) [Pager] New Contract * [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 +* [PR #182](https://github.com/SonsOfPHP/sonsofphp/pull/182) [Container] New Component (PSR-11) +* [PR #187](https://github.com/SonsOfPHP/sonsofphp/pull/187) [HttpHandler] New Component (PSR-15) and Contract +* [PR #190](https://github.com/SonsOfPHP/sonsofphp/pull/190) [Mailer] New Component and Contract ## [0.3.8] diff --git a/Makefile b/Makefile index 8edf6ea1..f3fdb73b 100644 --- a/Makefile +++ b/Makefile @@ -43,6 +43,7 @@ purge: # Purge vendor and lock files rm -rf src/SonsOfPHP/Bundle/*/vendor/ src/SonsOfPHP/Bundle/*/composer.lock rm -rf src/SonsOfPHP/Component/*/vendor/ src/SonsOfPHP/Component/*/composer.lock rm -rf src/SonsOfPHP/Contract/*/vendor/ src/SonsOfPHP/Contract/*/composer.lock + rm -rf src/tools/*/vendor/ src/tools/*/composer.lock test: phpunit ## Run PHPUnit Tests @@ -52,15 +53,33 @@ test-cache: phpunit test-clock: PHPUNIT_TESTSUITE=clock test-clock: phpunit +test-container: PHPUNIT_TESTSUITE=container +test-container: phpunit + +test-cookie: PHPUNIT_TESTSUITE=cookie +test-cookie: phpunit + test-cqrs: PHPUNIT_TESTSUITE=cqrs test-cqrs: phpunit +test-http-factory: PHPUNIT_TESTSUITE=http-factory +test-http-factory: phpunit + +test-http-handler: PHPUNIT_TESTSUITE=http-handler +test-http-handler: phpunit + test-link: PHPUNIT_TESTSUITE=link test-link: phpunit test-logger: PHPUNIT_TESTSUITE=logger test-logger: phpunit +test-mailer: PHPUNIT_TESTSUITE=mailer +test-mailer: phpunit + +test-money: PHPUNIT_TESTSUITE=money +test-money: phpunit + test-pager: PHPUNIT_TESTSUITE=pager test-pager: phpunit @@ -96,6 +115,12 @@ coverage-cache: coverage coverage-clock: PHPUNIT_TESTSUITE=clock coverage-clock: coverage +coverage-container: PHPUNIT_TESTSUITE=container +coverage-container: coverage + +coverage-cookie: PHPUNIT_TESTSUITE=cookie +coverage-cookie: coverage + coverage-cqrs: PHPUNIT_TESTSUITE=cqrs coverage-cqrs: coverage @@ -114,6 +139,9 @@ coverage-filesystem: coverage-http-factory: XDEBUG_MODE=coverage $(PHP) -dxdebug.mode=coverage $(PHPUNIT) --testsuite http-factory --coverage-html $(COVERAGE_DIR) +coverage-http-handler: PHPUNIT_TESTSUITE=http-handler +coverage-http-handler: coverage + coverage-http-message: XDEBUG_MODE=coverage $(PHP) -dxdebug.mode=coverage $(PHPUNIT) --testsuite http-message --coverage-html $(COVERAGE_DIR) @@ -126,8 +154,11 @@ coverage-link: coverage coverage-logger: PHPUNIT_TESTSUITE=logger coverage-logger: coverage -coverage-money: - XDEBUG_MODE=coverage $(PHP) -dxdebug.mode=coverage $(PHPUNIT) --testsuite money --coverage-html $(COVERAGE_DIR) +coverage-mailer: PHPUNIT_TESTSUITE=mailer +coverage-mailer: coverage + +coverage-money: PHPUNIT_TESTSUITE=money +coverage-money: coverage coverage-pager: PHPUNIT_TESTSUITE=pager coverage-pager: coverage @@ -162,6 +193,13 @@ php-cs-fixer-upgrade: testdox: ## Run tests and output testdox XDEBUG_MODE=off $(PHP) -dxdebug.mode=off $(PHPUNIT) --testdox +infection: + XDEBUG_MODE=develop \ + $(PHP) \ + -dxdebug.mode=develop \ + -dapc.enable_cli=1 \ + tools/infection/vendor/bin/infection --debug -vvv --show-mutations + tools-install: psalm-install php-cs-fixer-install phpunit-install tools-upgrade: psalm-upgrade php-cs-fixer-upgrade phpunit-upgrade diff --git a/bard.json b/bard.json index 497c40f7..d70c02bc 100644 --- a/bard.json +++ b/bard.json @@ -5,6 +5,14 @@ "path": "src/SonsOfPHP/Bard", "repository": "git@github.com:SonsOfPHP/bard.git" }, + { + "path": "src/SonsOfPHP/Contract/Mailer", + "repository": "git@github.com:SonsOfPHP/mailer-contract.git" + }, + { + "path": "src/SonsOfPHP/Component/Mailer", + "repository": "git@github.com:SonsOfPHP/mailer.git" + }, { "path": "src/SonsOfPHP/Component/Cache", "repository": "git@github.com:SonsOfPHP/cache.git" @@ -13,6 +21,14 @@ "path": "src/SonsOfPHP/Component/Clock", "repository": "git@github.com:SonsOfPHP/clock.git" }, + { + "path": "src/SonsOfPHP/Component/Container", + "repository": "git@github.com:SonsOfPHP/container.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" @@ -29,6 +45,10 @@ "path": "src/SonsOfPHP/Bridge/Symfony/EventSourcing", "repository": "git@github.com:SonsOfPHP/event-sourcing-symfony.git" }, + { + "path": "src/SonsOfPHP/Bridge/Twig/Money", + "repository": "git@github.com:SonsOfPHP/money-twig.git" + }, { "path": "src/SonsOfPHP/Component/EventDispatcher", "repository": "git@github.com:SonsOfPHP/event-dispatcher.git" @@ -53,6 +73,14 @@ "path": "src/SonsOfPHP/Component/HttpFactory", "repository": "git@github.com:SonsOfPHP/http-factory.git" }, + { + "path": "src/SonsOfPHP/Component/HttpHandler", + "repository": "git@github.com:SonsOfPHP/http-handler.git" + }, + { + "path": "src/SonsOfPHP/Contract/HttpHandler", + "repository": "git@github.com:SonsOfPHP/http-handler-contract.git" + }, { "path": "src/SonsOfPHP/Component/HttpMessage", "repository": "git@github.com:SonsOfPHP/http-message.git" @@ -77,6 +105,18 @@ "path": "src/SonsOfPHP/Component/Pager", "repository": "git@github.com:SonsOfPHP/pager.git" }, + { + "path": "src/SonsOfPHP/Bridge/Doctrine/Collections/Pager", + "repository": "git@github.com:SonsOfPHP/pager-doctrine-collections.git" + }, + { + "path": "src/SonsOfPHP/Bridge/Doctrine/DBAL/Pager", + "repository": "git@github.com:SonsOfPHP/pager-doctrine-dbal.git" + }, + { + "path": "src/SonsOfPHP/Bridge/Doctrine/ORM/Pager", + "repository": "git@github.com:SonsOfPHP/pager-doctrine-orm.git" + }, { "path": "src/SonsOfPHP/Component/Version", "repository": "git@github.com:SonsOfPHP/version.git" @@ -85,6 +125,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 f730a17d..cf9d1765 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,13 @@ "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", + "psr/container-implementation": "^1.0 || ^2.0", + "psr/http-server-handler-implementation": "^1.0", + "psr/http-server-middleware-implementation": "^1.0", + "sonsofphp/http-handler-implementation": "0.3.x-dev", + "sonsofphp/mailer-implementation": "0.3.x-dev" }, "require": { "php": ">=8.1", @@ -70,7 +76,14 @@ "psr/cache": "^2.0 || ^3.0", "psr/simple-cache": "^3.0", "psr/log": "^1.0 || ^2.0 || ^3.0", - "psr/link": "^1.0 || ^2.0" + "psr/link": "^1.0 || ^2.0", + "twig/twig": "^3.0", + "ext-intl": "*", + "doctrine/collections": "^2", + "doctrine/orm": "^2", + "psr/container": "^1.0 || ^2.0", + "psr/http-server-handler": "^1.0", + "psr/http-server-middleware": "^1.0" }, "replace": { "sonsofphp/bard": "self.version", @@ -102,31 +115,53 @@ "sonsofphp/logger-contract": "self.version", "sonsofphp/pager-contract": "self.version", "sonsofphp/pager": "self.version", - "sonsofphp/link": "self.version" + "sonsofphp/link": "self.version", + "sonsofphp/money-twig": "self.version", + "sonsofphp/pager-doctrine-collections": "self.version", + "sonsofphp/pager-doctrine-dbal": "self.version", + "sonsofphp/pager-doctrine-orm": "self.version", + "sonsofphp/cookie": "self.version", + "sonsofphp/cookie-contract": "self.version", + "sonsofphp/container": "self.version", + "sonsofphp/http-handler": "self.version", + "sonsofphp/http-handler-contract": "self.version", + "sonsofphp/mailer-contract": "self.version", + "sonsofphp/mailer": "self.version" }, "autoload": { "psr-4": { "SonsOfPHP\\Bard\\": "src/SonsOfPHP/Bard/src", + "SonsOfPHP\\Contract\\Mailer\\": "src/SonsOfPHP/Contract/Mailer", + "SonsOfPHP\\Component\\Mailer\\": "src/SonsOfPHP/Component/Mailer", "SonsOfPHP\\Component\\Cache\\": "src/SonsOfPHP/Component/Cache", "SonsOfPHP\\Component\\Clock\\": "src/SonsOfPHP/Component/Clock", + "SonsOfPHP\\Component\\Container\\": "src/SonsOfPHP/Component/Container", + "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", "SonsOfPHP\\Bridge\\Symfony\\EventSourcing\\": "src/SonsOfPHP/Bridge/Symfony/EventSourcing", + "SonsOfPHP\\Bridge\\Twig\\Money\\": "src/SonsOfPHP/Bridge/Twig/Money", "SonsOfPHP\\Component\\EventDispatcher\\": "src/SonsOfPHP/Component/EventDispatcher", "SonsOfPHP\\Component\\EventSourcing\\": "src/SonsOfPHP/Component/EventSourcing", "SonsOfPHP\\Bridge\\Doctrine\\EventSourcing\\": "src/SonsOfPHP/Bridge/Doctrine/EventSourcing", "SonsOfPHP\\Component\\FeatureToggle\\": "src/SonsOfPHP/Component/FeatureToggle", "SonsOfPHP\\Component\\Filesystem\\": "src/SonsOfPHP/Component/Filesystem", "SonsOfPHP\\Component\\HttpFactory\\": "src/SonsOfPHP/Component/HttpFactory", + "SonsOfPHP\\Component\\HttpHandler\\": "src/SonsOfPHP/Component/HttpHandler", + "SonsOfPHP\\Contract\\HttpHandler\\": "src/SonsOfPHP/Contract/HttpHandler", "SonsOfPHP\\Component\\HttpMessage\\": "src/SonsOfPHP/Component/HttpMessage", "SonsOfPHP\\Component\\Json\\": "src/SonsOfPHP/Component/Json", "SonsOfPHP\\Component\\Link\\": "src/SonsOfPHP/Component/Link", "SonsOfPHP\\Component\\Logger\\": "src/SonsOfPHP/Component/Logger", "SonsOfPHP\\Component\\Money\\": "src/SonsOfPHP/Component/Money", "SonsOfPHP\\Component\\Pager\\": "src/SonsOfPHP/Component/Pager", + "SonsOfPHP\\Bridge\\Doctrine\\Collections\\Pager\\": "src/SonsOfPHP/Bridge/Doctrine/Collections/Pager", + "SonsOfPHP\\Bridge\\Doctrine\\DBAL\\Pager\\": "src/SonsOfPHP/Bridge/Doctrine/DBAL/Pager", + "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", @@ -138,24 +173,32 @@ }, "exclude-from-classmap": [ "src/SonsOfPHP/Bard/Tests", + "src/SonsOfPHP/Component/Mailer/Tests", "src/SonsOfPHP/Component/Cache/Tests", "src/SonsOfPHP/Component/Clock/Tests", + "src/SonsOfPHP/Component/Container/Tests", + "src/SonsOfPHP/Component/Cookie/Tests", "src/SonsOfPHP/Component/Cqrs/Tests", "src/SonsOfPHP/Bundle/Cqrs/Tests", "src/SonsOfPHP/Bridge/Symfony/Cqrs/Tests", "src/SonsOfPHP/Bridge/Symfony/EventSourcing/Tests", + "src/SonsOfPHP/Bridge/Twig/Money/Tests", "src/SonsOfPHP/Component/EventDispatcher/Tests", "src/SonsOfPHP/Component/EventSourcing/Tests", "src/SonsOfPHP/Bridge/Doctrine/EventSourcing/Tests", "src/SonsOfPHP/Component/FeatureToggle/Tests", "src/SonsOfPHP/Component/Filesystem/Tests", "src/SonsOfPHP/Component/HttpFactory/Tests", + "src/SonsOfPHP/Component/HttpHandler/Tests", "src/SonsOfPHP/Component/HttpMessage/Tests", "src/SonsOfPHP/Component/Json/Tests", "src/SonsOfPHP/Component/Link/Tests", "src/SonsOfPHP/Component/Logger/Tests", "src/SonsOfPHP/Component/Money/Tests", "src/SonsOfPHP/Component/Pager/Tests", + "src/SonsOfPHP/Bridge/Doctrine/Collections/Pager/Tests", + "src/SonsOfPHP/Bridge/Doctrine/DBAL/Pager/Tests", + "src/SonsOfPHP/Bridge/Doctrine/ORM/Pager/Tests", "src/SonsOfPHP/Component/Version/Tests" ] }, @@ -181,4 +224,4 @@ "SonsOfPHP\\Bridge\\Symfony\\Cqrs\\Tests\\": "src/SonsOfPHP/Bridge/Symfony/Cqrs/Tests" } } -} \ No newline at end of file +} diff --git a/docs/components/container/index.md b/docs/components/container/index.md new file mode 100644 index 00000000..56fee6e0 --- /dev/null +++ b/docs/components/container/index.md @@ -0,0 +1,34 @@ +--- +title: Container +--- + +This is a very simple lightweight PSR-11 Container implementation. + +## Installation + +```shell +composer require sonsofphp/container +``` + +## Usage + +```php +set('service.id.one', function (ContainerInterface $container) { + return new Service(); +}); +$container->set('service.id.two', function (ContainerInterface $container) { + return new Service($container->get('service.id.one')); +}); + +// Services will not be created until they are called, once called, they will +// always return the same instance of the service. That means that in the +// following code, the "service.id.two" is only constructed once. +$service = $container->get('service.id.two'); +$service2 = $container->get('service.id.two'); +``` 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/components/cqrs/index.md b/docs/components/cqrs/index.md index 80a93123..0ef0ca40 100644 --- a/docs/components/cqrs/index.md +++ b/docs/components/cqrs/index.md @@ -48,11 +48,11 @@ $queryBus->addHandler(GetUser::class, $handler); $query = (new GetUser())->with('id', 123); $user = $queryBus->handle($query); +``` !!! success "Symfony CQRS Bridge" Once the CQRS Symfony Bridge is installed, you can use the Query Bus that comes with that to gain addition features and functionality. -``` ## Messages diff --git a/docs/components/http-handler/index.md b/docs/components/http-handler/index.md new file mode 100644 index 00000000..4f36db66 --- /dev/null +++ b/docs/components/http-handler/index.md @@ -0,0 +1,56 @@ +--- +title: HttpHandler +--- + +Simple PSR-15 Http Handler + +## Installation + +```shell +composer require sonsofphp/http-handler +``` + +## Usage + +Usage is pretty simple. + +```php +add(new RouterMiddleware()); +$stack->add(new CookieMiddleware()); +$stack->add(function ($request, $handler) { + // ... +}); +// ... + +$app = new HttpHandler($stack); +$response = $app->handle($request); +``` + +The `MiddlewareStack` accepts objects that implement `Psr\Http\Server\MiddlewareInterface` +and anonymous functions. + +### Middleware Priorities + +An optional second argument may be passed to the `MiddlewareStack` which is for +the priority of the middleware. Priorities are ordered in ascending order. + +```php +add(new NotFoundMiddleware(), 1025); +$stack->add(new RouterMiddleware(), 255); +$stack->add(new CookieMiddleware(), -255); +$stack->add(new DefaultMiddleware()); +``` + +In the above example, the `CookieMiddleware` will be processed first and +`NotFoundMiddleware` will be processed last. diff --git a/docs/components/mailer/index.md b/docs/components/mailer/index.md new file mode 100644 index 00000000..6c134bdd --- /dev/null +++ b/docs/components/mailer/index.md @@ -0,0 +1,46 @@ +--- +title: Mailer +--- + +Simple PHP Mailer + +## Installation + +```shell +composer require sonsofphp/mailer +``` + +## Usage + +```php +setTo('joshua@sonsofphp.com') + ->setFrom('From', 'joshua@sonsofphp.com') + ->setSubject('Subject', 'Test Subject') + ->setBody($body) +; + +$mailer = new Mailer(new NullTransport()); +$mailer->send($message); +``` + +### Middleware + +The `Mailer` class supports various middleware as well. + +```php +addMiddleware($middleware); +``` diff --git a/docs/components/money/index.md b/docs/components/money/index.md index a034f31d..6359432d 100644 --- a/docs/components/money/index.md +++ b/docs/components/money/index.md @@ -72,6 +72,51 @@ $currency = new Currency('USD'); $currency = Currency::USD(); ``` +### Formatters + +```php +format(Money::USD(4.20)); +echo $output; // $4.20 +``` + +## Twig Bridge + +### Installation + +```shell +composer require sonsofphp/money-twig +``` + +### Usage + +#### Add Extension to Twig Environment + +```php +addExtension($extension); +``` + +#### Usage in Twig Templates + +```twig +Your total is {{ money|format_money }}. +``` + + ## Need Help? Check out [Sons of PHP's Organization Discussions][discussions]. diff --git a/docs/components/pager/index.md b/docs/components/pager/index.md index ab57ea4d..c9a23af6 100644 --- a/docs/components/pager/index.md +++ b/docs/components/pager/index.md @@ -45,9 +45,35 @@ $pager = new Pager(new ArrayAdapter($results), [ // You can also set current page and max per page $pager->setCurrentPage(1); $pager->setMaxPerPage(10); + + +$totalPages = $pager->getTotalPages(); +$totalResults = $pager->getTotalResults(); +$currentPage = $pager->getCurrentPage(); + +if ($pager->haveToPaginate()) { + // ... +} + +if ($pager->hasPreviousPage()) { + $prevPage = $pager->getPreviousPage(); + // ... +} + +if ($pager->hasNextPage()) { + $nextPage = $pager->getNextPage(); + // ... +} ``` -## Adapters +## Custom Adapters + +Creating Custom Adapters is easy. You can take a look at the available adapters +to see how easy it is. + +Please see the [Pager Contract](../../contracts/pager/index.md) to learn more. + +## Available Adapters ### ArrayAdapter @@ -77,3 +103,59 @@ $adapter = new CallableAdapter( }, ); ``` + +### ArrayCollectionAdapter (doctrine/collections) + +!!! warning "Requires `sonsofphp/pager-doctrine-collections`" + ```shell + composer require sonsofphp/pager-doctrine-collections + ``` + +```php +select('COUNT(e.id) as total'); +}); +``` + +### QueryBuilderAdapter (doctrine/orm) + +!!! warning "Requires `sonsofphp/pager-doctrine-orm`" + ```shell + composer require sonsofphp/pager-doctrine-orm + ``` + +```php +createQueryBuilder('e'); + +$adapter = new QueryBuilderAdapter($builder); +``` diff --git a/docs/contracts/common/index.md b/docs/contracts/common/index.md index cc1e0a7d..818c807c 100644 --- a/docs/contracts/common/index.md +++ b/docs/contracts/common/index.md @@ -1,5 +1,6 @@ --- title: Common Contracts - Overview +description: PHP Common Contracts --- # Common Contracts @@ -15,7 +16,8 @@ composer require sonsofphp/common-contract ## Includes Interfaces -* ArrayableInterface -* ComparableInterface -* EquatableInterface -* JsonableInterface +* `ArrayableInterface` +* `ComparableInterface` +* `EquatableInterface` +* `JsonableInterface` +* `TryableInterface` 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/docs/contracts/index.md b/docs/contracts/index.md index 7a160344..6ca2be7f 100644 --- a/docs/contracts/index.md +++ b/docs/contracts/index.md @@ -20,6 +20,64 @@ If you want to provide a concrete library for others to use, add this to your ```json "provide": { - "sonsofphp/core-contract-implementation": "^1.0" + "sonsofphp/common-implementation": "^1.0" }, ``` + +## Method Naming + +Functionality around method names are kept as similar as possible. They SHOULD +follow this convention. + +### with* + +`with*` methods are meant to be used on value objects. They will return a new +object with the value(s) modified. + +Examples: +- `withFirstName` + +### has* + +Will always return a `boolean` value. Used to check if a property or value +exists on the object. Doesn't matter the type of the value. + +Examples +- `hasFirstName` + +### is* + +Will always return a `boolean` value. + +Examples: +- `isNew` +- `isDeleted` + +### get* + +Used to return property values. + +Examples: +- `getFirstName` +- `getLastName` + +### set* + +Used to set property values. SHOULD always return the same instance of the +object to allow chaining. + +Examples: +- `setFirstName` +- `setLastName` + +### to* + +Used to convert an object to a specific format. + +Examples: +- `toJson` +- `toString` +- `toArray` +- `toInteger` +- `toFloat` +- `toBoolean` diff --git a/docs/contracts/mailer/index.md b/docs/contracts/mailer/index.md new file mode 100644 index 00000000..40eeb45d --- /dev/null +++ b/docs/contracts/mailer/index.md @@ -0,0 +1,18 @@ +--- +title: Mailer Contract +--- + +## Installation + +```shell +composer require sonsofphp/mailer-contract +``` + +## Definitions + +- **Message** - The email message that will be sent. +- **Mailer** - Primary API used to send *messages*. +- **Transport** - Transports are what will actually send the message. This could + be SMTP, Null, SendGrid, Amazon SES, or anything like that. +- **Middleware** - Before the message is sent to the transport to be sent, + various middlewares have the ability to modify the message. diff --git a/docs/contracts/pager/index.md b/docs/contracts/pager/index.md index 550b6de1..d2333a30 100644 --- a/docs/contracts/pager/index.md +++ b/docs/contracts/pager/index.md @@ -10,3 +10,37 @@ components. ```shell composer require sonsofphp/pager-contract ``` + +## AdapterInterface + +```php + src/SonsOfPHP/Bridge/*/*/Tests + src/SonsOfPHP/Bridge/*/*/*/Tests src/SonsOfPHP/Bundle/*/Tests src/SonsOfPHP/Component/*/Tests @@ -29,6 +30,14 @@ src/SonsOfPHP/Component/Clock/Tests + + src/SonsOfPHP/Component/Container/Tests + + + + src/SonsOfPHP/Component/Cookie/Tests + + src/SonsOfPHP/Bridge/*/Cqrs/Tests @@ -55,6 +64,10 @@ src/SonsOfPHP/Component/HttpFactory/Tests + + src/SonsOfPHP/Component/HttpHandler/Tests + + src/SonsOfPHP/Component/HttpMessage/Tests @@ -71,11 +84,17 @@ src/SonsOfPHP/Component/Logger/Tests + + src/SonsOfPHP/Component/Mailer/Tests + + + src/SonsOfPHP/Bridge/*/Money/Tests src/SonsOfPHP/Component/Money/Tests + src/SonsOfPHP/Bridge/*/*/Pager/Tests src/SonsOfPHP/Component/Pager/Tests @@ -84,7 +103,7 @@ - + @@ -94,10 +113,10 @@ src/SonsOfPHP/Bard/vendor - src/SonsOfPHP/Bridge/Doctrine/*/Tests - src/SonsOfPHP/Bridge/Doctrine/*/vendor - src/SonsOfPHP/Bridge/Symfony/*/Tests - src/SonsOfPHP/Bridge/Symfony/*/vendor + src/SonsOfPHP/Bridge/*/*/*/Tests + src/SonsOfPHP/Bridge/*/*/*/vendor + src/SonsOfPHP/Bridge/*/*/Tests + src/SonsOfPHP/Bridge/*/*/vendor src/SonsOfPHP/Bundle/*/Tests src/SonsOfPHP/Bundle/*/vendor src/SonsOfPHP/Component/*/Tests diff --git a/src/SonsOfPHP/Bridge/Doctrine/Collections/Pager/.gitattributes b/src/SonsOfPHP/Bridge/Doctrine/Collections/Pager/.gitattributes new file mode 100644 index 00000000..84c7add0 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Doctrine/Collections/Pager/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/SonsOfPHP/Bridge/Doctrine/Collections/Pager/.gitignore b/src/SonsOfPHP/Bridge/Doctrine/Collections/Pager/.gitignore new file mode 100644 index 00000000..5414c2c6 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Doctrine/Collections/Pager/.gitignore @@ -0,0 +1,3 @@ +composer.lock +phpunit.xml +vendor/ diff --git a/src/SonsOfPHP/Bridge/Doctrine/Collections/Pager/ArrayCollectionAdapter.php b/src/SonsOfPHP/Bridge/Doctrine/Collections/Pager/ArrayCollectionAdapter.php new file mode 100644 index 00000000..327b7028 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Doctrine/Collections/Pager/ArrayCollectionAdapter.php @@ -0,0 +1,34 @@ + + */ +class ArrayCollectionAdapter implements AdapterInterface +{ + public function __construct( + private ArrayCollection $collection, + ) {} + + /** + * {@inheritdoc} + */ + public function count(): int + { + return count($this->collection); + } + + /** + * {@inheritdoc} + */ + public function getSlice(int $offset, ?int $length): iterable + { + return $this->collection->slice($offset, $length); + } +} diff --git a/src/SonsOfPHP/Bridge/Doctrine/Collections/Pager/LICENSE b/src/SonsOfPHP/Bridge/Doctrine/Collections/Pager/LICENSE new file mode 100644 index 00000000..39238382 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Doctrine/Collections/Pager/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/Bridge/Doctrine/Collections/Pager/README.md b/src/SonsOfPHP/Bridge/Doctrine/Collections/Pager/README.md new file mode 100644 index 00000000..4b3b2a60 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Doctrine/Collections/Pager/README.md @@ -0,0 +1,17 @@ +Sons of PHP - Pager (doctrine/collections) +========================================== + +## 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/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/Bridge/Doctrine/Collections/Pager/Tests/ArrayCollectionAdapterTest.php b/src/SonsOfPHP/Bridge/Doctrine/Collections/Pager/Tests/ArrayCollectionAdapterTest.php new file mode 100644 index 00000000..ede1c857 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Doctrine/Collections/Pager/Tests/ArrayCollectionAdapterTest.php @@ -0,0 +1,48 @@ +assertInstanceOf(AdapterInterface::class, $adapter); + } + + /** + * @covers ::count + */ + public function testCount(): void + { + $adapter = new ArrayCollectionAdapter(new ArrayCollection(range(0, 9))); + + $this->assertCount(10, $adapter); + } + + /** + * @covers ::getSlice + */ + public function testGetSlice(): void + { + $adapter = new ArrayCollectionAdapter(new ArrayCollection(range(0, 9))); + + $this->assertSame([0], $adapter->getSlice(0, 1)); + } +} diff --git a/src/SonsOfPHP/Bridge/Doctrine/Collections/Pager/composer.json b/src/SonsOfPHP/Bridge/Doctrine/Collections/Pager/composer.json new file mode 100644 index 00000000..9ff71f0f --- /dev/null +++ b/src/SonsOfPHP/Bridge/Doctrine/Collections/Pager/composer.json @@ -0,0 +1,52 @@ +{ + "name": "sonsofphp/pager-doctrine-collections", + "type": "library", + "description": "", + "keywords": [ + "pager" + ], + "homepage": "https://github.com/SonsOfPHP/pager-doctrine-collections", + "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" + }, + "autoload": { + "psr-4": { + "SonsOfPHP\\Bridge\\Doctrine\\Collections\\Pager\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "php": ">=8.1", + "doctrine/collections": "^2", + "sonsofphp/pager": "^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" + } + ] +} \ No newline at end of file diff --git a/src/SonsOfPHP/Bridge/Doctrine/DBAL/Pager/.gitattributes b/src/SonsOfPHP/Bridge/Doctrine/DBAL/Pager/.gitattributes new file mode 100644 index 00000000..84c7add0 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Doctrine/DBAL/Pager/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/SonsOfPHP/Bridge/Doctrine/DBAL/Pager/.gitignore b/src/SonsOfPHP/Bridge/Doctrine/DBAL/Pager/.gitignore new file mode 100644 index 00000000..5414c2c6 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Doctrine/DBAL/Pager/.gitignore @@ -0,0 +1,3 @@ +composer.lock +phpunit.xml +vendor/ diff --git a/src/SonsOfPHP/Bridge/Doctrine/DBAL/Pager/LICENSE b/src/SonsOfPHP/Bridge/Doctrine/DBAL/Pager/LICENSE new file mode 100644 index 00000000..39238382 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Doctrine/DBAL/Pager/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/Bridge/Doctrine/DBAL/Pager/QueryBuilderAdapter.php b/src/SonsOfPHP/Bridge/Doctrine/DBAL/Pager/QueryBuilderAdapter.php new file mode 100644 index 00000000..7f7eefbb --- /dev/null +++ b/src/SonsOfPHP/Bridge/Doctrine/DBAL/Pager/QueryBuilderAdapter.php @@ -0,0 +1,57 @@ +select('COUNT(DISTINCT e.id) AS cnt'); + * }); + * + * @author Joshua Estes + */ +class QueryBuilderAdapter implements AdapterInterface +{ + private $countQuery; + + public function __construct( + private readonly QueryBuilder $builder, + callable $countQuery, + ) { + $this->countQuery = $countQuery; + } + + /** + * {@inheritdoc} + */ + public function count(): int + { + $builder = clone $this->builder; + $callable = $this->countQuery; + + $callable($builder); + $builder->setMaxResults(1); + + return (int) $builder->executeQuery()->fetchOne(); + } + + /** + * {@inheritdoc} + */ + public function getSlice(int $offset, ?int $length): iterable + { + $builder = clone $this->builder; + + return $builder + ->setFirstResult($offset) + ->setMaxResults($length) + ->executeQuery() + ->fetchAllAssociative() + ; + } +} diff --git a/src/SonsOfPHP/Bridge/Doctrine/DBAL/Pager/README.md b/src/SonsOfPHP/Bridge/Doctrine/DBAL/Pager/README.md new file mode 100644 index 00000000..1e8c9a14 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Doctrine/DBAL/Pager/README.md @@ -0,0 +1,17 @@ +Sons of PHP - Pager (doctrine/dbal) +=================================== + +## 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/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/Bridge/Doctrine/DBAL/Pager/Tests/QueryBuilderAdapterTest.php b/src/SonsOfPHP/Bridge/Doctrine/DBAL/Pager/Tests/QueryBuilderAdapterTest.php new file mode 100644 index 00000000..fb2050d0 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Doctrine/DBAL/Pager/Tests/QueryBuilderAdapterTest.php @@ -0,0 +1,78 @@ +builder = $this->createMock(QueryBuilder::class); + $this->result = $this->createMock(Result::class); + } + + /** + * @covers ::__construct + */ + public function testItHasTheRightInterface(): void + { + $adapter = new QueryBuilderAdapter($this->builder, function (QueryBuilder $builder): void {}); + + $this->assertInstanceOf(AdapterInterface::class, $adapter); + } + + /** + * @covers ::count + */ + public function testCount(): void + { + $this->builder->method('executeQuery')->willReturn($this->result); + $this->builder->expects($this->once())->method('select'); + + $this->result->method('fetchOne')->willReturn(123); + + $adapter = new QueryBuilderAdapter($this->builder, function (QueryBuilder $builder): void { + $builder->select('COUNT()'); + }); + + $this->assertCount(123, $adapter); + } + + /** + * @covers ::getSlice + */ + public function testSlice(): void + { + $this->builder + ->expects($this->once()) + ->method('setFirstResult') + ->with($this->identicalTo(0)) + ->willReturn($this->builder) + ; + $this->builder + ->expects($this->once()) + ->method('setMaxResults') + ->with($this->identicalTo(100)) + ->willReturn($this->builder) + ; + + $adapter = new QueryBuilderAdapter($this->builder, function (QueryBuilder $builder): void {}); + + $adapter->getSlice(0, 100); + } +} diff --git a/src/SonsOfPHP/Bridge/Doctrine/DBAL/Pager/composer.json b/src/SonsOfPHP/Bridge/Doctrine/DBAL/Pager/composer.json new file mode 100644 index 00000000..4da91f2e --- /dev/null +++ b/src/SonsOfPHP/Bridge/Doctrine/DBAL/Pager/composer.json @@ -0,0 +1,52 @@ +{ + "name": "sonsofphp/pager-doctrine-dbal", + "type": "library", + "description": "", + "keywords": [ + "pager" + ], + "homepage": "https://github.com/SonsOfPHP/pager-doctrine-dbal", + "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" + }, + "autoload": { + "psr-4": { + "SonsOfPHP\\Bridge\\Doctrine\\DBAL\\Pager\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "php": ">=8.1", + "doctrine/dbal": "^3", + "sonsofphp/pager": "^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" + } + ] +} \ No newline at end of file diff --git a/src/SonsOfPHP/Bridge/Doctrine/ORM/Pager/.gitattributes b/src/SonsOfPHP/Bridge/Doctrine/ORM/Pager/.gitattributes new file mode 100644 index 00000000..84c7add0 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Doctrine/ORM/Pager/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/SonsOfPHP/Bridge/Doctrine/ORM/Pager/.gitignore b/src/SonsOfPHP/Bridge/Doctrine/ORM/Pager/.gitignore new file mode 100644 index 00000000..5414c2c6 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Doctrine/ORM/Pager/.gitignore @@ -0,0 +1,3 @@ +composer.lock +phpunit.xml +vendor/ diff --git a/src/SonsOfPHP/Bridge/Doctrine/ORM/Pager/LICENSE b/src/SonsOfPHP/Bridge/Doctrine/ORM/Pager/LICENSE new file mode 100644 index 00000000..39238382 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Doctrine/ORM/Pager/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/Bridge/Doctrine/ORM/Pager/QueryBuilderAdapter.php b/src/SonsOfPHP/Bridge/Doctrine/ORM/Pager/QueryBuilderAdapter.php new file mode 100644 index 00000000..2f7cb6e6 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Doctrine/ORM/Pager/QueryBuilderAdapter.php @@ -0,0 +1,45 @@ + + */ +class QueryBuilderAdapter implements AdapterInterface +{ + private readonly Paginator $paginator; + + public function __construct( + QueryBuilder $builder, + ) { + $this->paginator = new Paginator($builder, fetchJoinCollection: true); + } + + /** + * {@inheritdoc} + */ + public function count(): int + { + return count($this->paginator); + } + + /** + * {@inheritdoc} + */ + public function getSlice(int $offset, ?int $length): iterable + { + $query = $this->paginator->getQuery(); + $query + ->setFirstResult($offset) + ->setMaxResults($length) + ; + + return $this->paginator->getIterator(); + } +} diff --git a/src/SonsOfPHP/Bridge/Doctrine/ORM/Pager/README.md b/src/SonsOfPHP/Bridge/Doctrine/ORM/Pager/README.md new file mode 100644 index 00000000..871e0150 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Doctrine/ORM/Pager/README.md @@ -0,0 +1,17 @@ +Sons of PHP - Pager (doctrine/orm) +========================================== + +## 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/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/Bridge/Doctrine/ORM/Pager/Tests/QueryBuilderAdapterTest.php b/src/SonsOfPHP/Bridge/Doctrine/ORM/Pager/Tests/QueryBuilderAdapterTest.php new file mode 100644 index 00000000..8536ffec --- /dev/null +++ b/src/SonsOfPHP/Bridge/Doctrine/ORM/Pager/Tests/QueryBuilderAdapterTest.php @@ -0,0 +1,35 @@ +builder = $this->createMock(QueryBuilder::class); + } + + /** + * @covers ::__construct + */ + public function testItHasTheRightInterface(): void + { + $adapter = new QueryBuilderAdapter($this->builder); + + $this->assertInstanceOf(AdapterInterface::class, $adapter); + } +} diff --git a/src/SonsOfPHP/Bridge/Doctrine/ORM/Pager/composer.json b/src/SonsOfPHP/Bridge/Doctrine/ORM/Pager/composer.json new file mode 100644 index 00000000..7b1197a9 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Doctrine/ORM/Pager/composer.json @@ -0,0 +1,52 @@ +{ + "name": "sonsofphp/pager-doctrine-orm", + "type": "library", + "description": "", + "keywords": [ + "pager" + ], + "homepage": "https://github.com/SonsOfPHP/pager-doctrine-orm", + "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" + }, + "autoload": { + "psr-4": { + "SonsOfPHP\\Bridge\\Doctrine\\ORM\\Pager\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "php": ">=8.1", + "doctrine/orm": "^2", + "sonsofphp/pager": "^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" + } + ] +} \ No newline at end of file diff --git a/src/SonsOfPHP/Bridge/Symfony/EventSourcing/Tests/Message/MessageNormalizerTest.php b/src/SonsOfPHP/Bridge/Symfony/EventSourcing/Tests/Message/MessageNormalizerTest.php index 0b0f11a3..5954cf6d 100644 --- a/src/SonsOfPHP/Bridge/Symfony/EventSourcing/Tests/Message/MessageNormalizerTest.php +++ b/src/SonsOfPHP/Bridge/Symfony/EventSourcing/Tests/Message/MessageNormalizerTest.php @@ -10,8 +10,6 @@ use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -class Msg extends AbstractMessage {} - /** * @coversDefaultClass \SonsOfPHP\Bridge\Symfony\EventSourcing\Message\MessageNormalizer * @@ -40,7 +38,7 @@ public function testItWillNormalizeMessage(): void { $normalizer = new MessageNormalizer(); - $message = Msg::new(); + $message = new class () extends AbstractMessage {}; $this->assertTrue($normalizer->supportsNormalization($message)); @@ -49,30 +47,4 @@ public function testItWillNormalizeMessage(): void $this->assertArrayHasKey('payload', $output); $this->assertArrayHasKey('metadata', $output); } - - /** - * @covers ::denormalize - * @covers ::supportsDenormalization - */ - public function testItWillDenormalizeMessage(): void - { - $normalizer = new MessageNormalizer(); - - $data = [ - 'payload' => [ - 'unit' => 'test', - ], - 'metadata' => [ - 'test' => 'unit', - ], - ]; - $type = Msg::class; - - $this->assertTrue($normalizer->supportsDenormalization($data, $type)); - - $output = $normalizer->denormalize($data, $type); - - $this->assertSame('test', $output->getPayload()['unit']); - $this->assertSame('unit', $output->getMetadata()['test']); - } } diff --git a/src/SonsOfPHP/Bridge/Twig/Money/.gitattributes b/src/SonsOfPHP/Bridge/Twig/Money/.gitattributes new file mode 100644 index 00000000..84c7add0 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Twig/Money/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/SonsOfPHP/Bridge/Twig/Money/.gitignore b/src/SonsOfPHP/Bridge/Twig/Money/.gitignore new file mode 100644 index 00000000..5414c2c6 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Twig/Money/.gitignore @@ -0,0 +1,3 @@ +composer.lock +phpunit.xml +vendor/ diff --git a/src/SonsOfPHP/Bridge/Twig/Money/LICENSE b/src/SonsOfPHP/Bridge/Twig/Money/LICENSE new file mode 100644 index 00000000..39238382 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Twig/Money/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/Bridge/Twig/Money/MoneyExtension.php b/src/SonsOfPHP/Bridge/Twig/Money/MoneyExtension.php new file mode 100644 index 00000000..528a528c --- /dev/null +++ b/src/SonsOfPHP/Bridge/Twig/Money/MoneyExtension.php @@ -0,0 +1,40 @@ +formatter->format($money); + } +} diff --git a/src/SonsOfPHP/Bridge/Twig/Money/README.md b/src/SonsOfPHP/Bridge/Twig/Money/README.md new file mode 100644 index 00000000..7ee53445 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Twig/Money/README.md @@ -0,0 +1,17 @@ +Sons of PHP - Money - Twig Bridge +================================= + +## 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/money/ +[issues]: https://github.com/SonsOfPHP/sonsofphp/issues?q=is%3Aopen+is%3Aissue+label%3AMoney +[pull-requests]: https://github.com/SonsOfPHP/sonsofphp/pulls?q=is%3Aopen+is%3Apr+label%3AMoney diff --git a/src/SonsOfPHP/Bridge/Twig/Money/Tests/MoneyExtensionTest.php b/src/SonsOfPHP/Bridge/Twig/Money/Tests/MoneyExtensionTest.php new file mode 100644 index 00000000..82c8a3dd --- /dev/null +++ b/src/SonsOfPHP/Bridge/Twig/Money/Tests/MoneyExtensionTest.php @@ -0,0 +1,60 @@ +formatter = $this->createMock(MoneyFormatterInterface::class); + } + + /** + * @covers ::__construct + */ + public function testItHasTheRightInterface(): void + { + $extension = new MoneyExtension($this->formatter); + + $this->assertInstanceOf(ExtensionInterface::class, $extension); + } + + /** + * @covers ::getFilters + */ + public function testGetFilters(): void + { + $extension = new MoneyExtension($this->formatter); + + $this->assertGreaterThan(0, $extension->getFilters()); + } + + /** + * @covers ::formatMoney + */ + public function testItCanFormatMoney(): void + { + $this->formatter->expects($this->once())->method('format')->willReturn(''); + + $extension = new MoneyExtension($this->formatter); + $extension->formatMoney(Money::USD(10)); + } +} diff --git a/src/SonsOfPHP/Bridge/Twig/Money/composer.json b/src/SonsOfPHP/Bridge/Twig/Money/composer.json new file mode 100644 index 00000000..fbc01884 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Twig/Money/composer.json @@ -0,0 +1,54 @@ +{ + "name": "sonsofphp/money-twig", + "type": "library", + "description": "Twig Extension for the Money Component", + "keywords": [ + "money", + "currency", + "ISO4217" + ], + "homepage": "https://github.com/SonsOfPHP/money", + "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" + }, + "autoload": { + "psr-4": { + "SonsOfPHP\\Bridge\\Twig\\Money\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "php": ">=8.1", + "sonsofphp/money": "^0.3.x-dev", + "twig/twig": "^3.0" + }, + "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" + } + ] +} \ No newline at end of file diff --git a/src/SonsOfPHP/Component/Cache/Tests/CacheItemTest.php b/src/SonsOfPHP/Component/Cache/Tests/CacheItemTest.php index 7f719e67..cd8dc9c0 100644 --- a/src/SonsOfPHP/Component/Cache/Tests/CacheItemTest.php +++ b/src/SonsOfPHP/Component/Cache/Tests/CacheItemTest.php @@ -151,10 +151,10 @@ public function testValidateKeyWithInvalidValues(string $key): void public static function invalidKeysProvider(): iterable { - yield ['']; + yield 'empty' => ['']; - yield ['not allowed']; + yield 'space' => ['not allowed']; - yield ['contains@reserved}characters']; + yield 'reserved' => ['contains@reserved}characters']; } } diff --git a/src/SonsOfPHP/Component/Container/.gitattributes b/src/SonsOfPHP/Component/Container/.gitattributes new file mode 100644 index 00000000..84c7add0 --- /dev/null +++ b/src/SonsOfPHP/Component/Container/.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/Container/.gitignore b/src/SonsOfPHP/Component/Container/.gitignore new file mode 100644 index 00000000..5414c2c6 --- /dev/null +++ b/src/SonsOfPHP/Component/Container/.gitignore @@ -0,0 +1,3 @@ +composer.lock +phpunit.xml +vendor/ diff --git a/src/SonsOfPHP/Component/Container/Container.php b/src/SonsOfPHP/Component/Container/Container.php new file mode 100644 index 00000000..4a7b37fd --- /dev/null +++ b/src/SonsOfPHP/Component/Container/Container.php @@ -0,0 +1,60 @@ +set('service.id', function (ContainerInterface $container) { + * return new Service($container->get('another.service_id')); + * }); + */ +class Container implements ContainerInterface +{ + private array $services = []; + private array $cachedServices = []; + + /** + * {@inheritdoc} + */ + public function get(string $id) + { + if (false === $this->has($id)) { + throw new NotFoundException(sprintf('Service "%s" not found.', $id)); + } + + if (!array_key_exists($id, $this->cachedServices)) { + $this->cachedServices[$id] = call_user_func($this->services[$id], $this); + } + + return $this->cachedServices[$id]; + } + + /** + * {@inheritdoc} + */ + public function has(string $id): bool + { + return array_key_exists($id, $this->services); + } + + /** + * @param callable $callable + */ + public function set(string $id, $callable): self + { + if (!is_callable($callable)) { + throw new ContainerException('MUST pass in a callable'); + } + + $this->services[$id] = $callable; + + return $this; + } +} diff --git a/src/SonsOfPHP/Component/Container/Exception/ContainerException.php b/src/SonsOfPHP/Component/Container/Exception/ContainerException.php new file mode 100644 index 00000000..3be7e636 --- /dev/null +++ b/src/SonsOfPHP/Component/Container/Exception/ContainerException.php @@ -0,0 +1,9 @@ +assertInstanceOf(ContainerInterface::class, $container); + } + + /** + * @covers ::has + */ + public function testhHas(): void + { + $container = new Container(); + + $this->assertFalse($container->has('service.id')); + $container->set('service.id', function (): void {}); + $this->assertTrue($container->has('service.id')); + } + + /** + * @covers ::get + */ + public function testGetWhenServiceNotFound(): void + { + $container = new Container(); + + $this->expectException(NotFoundExceptionInterface::class); + $container->get('nope'); + } + + /** + * @covers ::get + */ + public function testGetAlwaysReturnSameInstance(): void + { + $container = new Container(); + $container->set('service.id', fn() => new \stdClass()); + $service = $container->get('service.id'); + $this->assertSame($service, $container->get('service.id')); + } + + /** + * @covers ::get + */ + public function testGetWillCacheService(): void + { + $container = new Container(); + $cached = new \ReflectionProperty($container, 'cachedServices'); + + $container->set('service.id', fn() => new \stdClass()); + $service = $container->get('service.id'); + $this->assertCount(1, $cached->getValue($container)); + } + + /** + * @covers ::set + */ + public function testSet(): void + { + $container = new Container(); + $services = new \ReflectionProperty($container, 'services'); + $this->assertCount(0, $services->getValue($container)); + + $container->set('service.id', function (): void {}); + $this->assertCount(1, $services->getValue($container)); + } + + /** + * @covers ::set + */ + public function testSetWhenNotCallable(): void + { + $container = new Container(); + + $this->expectException(ContainerExceptionInterface::class); + $container->set('service.id', 'this is not callable'); + } +} diff --git a/src/SonsOfPHP/Component/Container/composer.json b/src/SonsOfPHP/Component/Container/composer.json new file mode 100644 index 00000000..31ef2458 --- /dev/null +++ b/src/SonsOfPHP/Component/Container/composer.json @@ -0,0 +1,56 @@ +{ + "name": "sonsofphp/container", + "type": "library", + "description": "Lightweight PSR-11 Container", + "keywords": [ + "psr11", + "psr-11", + "container" + ], + "homepage": "https://github.com/SonsOfPHP/container", + "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" + }, + "autoload": { + "psr-4": { + "SonsOfPHP\\Component\\Container\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "php": ">=8.1", + "psr/container": "^1.0 || ^2.0" + }, + "provide": { + "psr/container-implementation": "^1.0 || ^2.0" + }, + "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" + } + ] +} \ No newline at end of file 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..8b6b4ff1 --- /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): bool + { + 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..96bc1774 --- /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" + }, + "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" + } + ] +} \ No newline at end of file diff --git a/src/SonsOfPHP/Component/Cqrs/AbstractCommandMessageHandler.php b/src/SonsOfPHP/Component/Cqrs/AbstractCommandMessageHandler.php index 28c73466..7fa52e86 100644 --- a/src/SonsOfPHP/Component/Cqrs/AbstractCommandMessageHandler.php +++ b/src/SonsOfPHP/Component/Cqrs/AbstractCommandMessageHandler.php @@ -4,6 +4,8 @@ namespace SonsOfPHP\Component\Cqrs; +use SonsOfPHP\Contract\Cqrs\CommandMessageHandlerInterface; + /** * @author Joshua Estes */ diff --git a/src/SonsOfPHP/Component/Cqrs/AbstractMessage.php b/src/SonsOfPHP/Component/Cqrs/AbstractMessage.php index 06de1337..ed3ad4fe 100644 --- a/src/SonsOfPHP/Component/Cqrs/AbstractMessage.php +++ b/src/SonsOfPHP/Component/Cqrs/AbstractMessage.php @@ -13,6 +13,9 @@ abstract class AbstractMessage implements MessageInterface, \JsonSerializable, \ { private array $payload = []; + /** + * {@inheritdoc} + */ public function with(string|array $key, mixed $value = null): static { if (is_object($value) && !$value instanceof \Stringable) { @@ -54,6 +57,9 @@ public function with(string|array $key, mixed $value = null): static return $that; } + /** + * {@inheritdoc} + */ public function get(?string $key = null): mixed { if (null === $key) { diff --git a/src/SonsOfPHP/Component/Cqrs/AbstractQueryMessageHandler.php b/src/SonsOfPHP/Component/Cqrs/AbstractQueryMessageHandler.php index e4b76bf6..6b7d99de 100644 --- a/src/SonsOfPHP/Component/Cqrs/AbstractQueryMessageHandler.php +++ b/src/SonsOfPHP/Component/Cqrs/AbstractQueryMessageHandler.php @@ -4,6 +4,8 @@ namespace SonsOfPHP\Component\Cqrs; +use SonsOfPHP\Contract\Cqrs\QueryMessageHandlerInterface; + /** * @author Joshua Estes */ diff --git a/src/SonsOfPHP/Component/EventSourcing/Tests/Aggregate/Repository/AggregateRepositoryTest.php b/src/SonsOfPHP/Component/EventSourcing/Tests/Aggregate/Repository/AggregateRepositoryTest.php index 328da0c6..55c63e7f 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Tests/Aggregate/Repository/AggregateRepositoryTest.php +++ b/src/SonsOfPHP/Component/EventSourcing/Tests/Aggregate/Repository/AggregateRepositoryTest.php @@ -14,8 +14,6 @@ use SonsOfPHP\Component\EventSourcing\Message\Repository\MessageRepositoryInterface; use SonsOfPHP\Component\EventSourcing\Tests\FakeAggregate; -class Msg extends AbstractMessage {} - /** * @coversDefaultClass \SonsOfPHP\Component\EventSourcing\Aggregate\Repository\AggregateRepository * @@ -70,7 +68,7 @@ public function testPersistWillUseEventDispatcher(): void $aggregate = new FakeAggregate('unique-id'); - $message = Msg::new(); + $message = new class () extends AbstractMessage {}; $aggregate->raiseThisEvent($message); $repository->persist($aggregate); @@ -90,7 +88,7 @@ public function testPersistAndFind(): void $aggregate = new FakeAggregate('unique-id'); - $message = Msg::new(); + $message = new class () extends AbstractMessage {}; $aggregate->raiseThisEvent($message); $repository->persist($aggregate); @@ -113,7 +111,7 @@ public function testPersistAndFindWithoutUsingAggregateId(): void $aggregate = new FakeAggregate('unique-id'); - $message = Msg::new(); + $message = new class () extends AbstractMessage {}; $aggregate->raiseThisEvent($message); $repository->persist($aggregate); diff --git a/src/SonsOfPHP/Component/EventSourcing/Tests/Message/AbstractMessageTest.php b/src/SonsOfPHP/Component/EventSourcing/Tests/Message/AbstractMessageTest.php index 06d12356..6a5309b7 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Tests/Message/AbstractMessageTest.php +++ b/src/SonsOfPHP/Component/EventSourcing/Tests/Message/AbstractMessageTest.php @@ -12,8 +12,6 @@ use SonsOfPHP\Component\EventSourcing\Message\MessageInterface; use SonsOfPHP\Component\EventSourcing\Metadata; -class Msg extends AbstractMessage {} - /** * @coversDefaultClass \SonsOfPHP\Component\EventSourcing\Message\AbstractMessage * @@ -31,7 +29,7 @@ final class AbstractMessageTest extends TestCase */ public function testItHasTheRightInterface(): void { - $message = Msg::new(); + $message = $this->createMock(AbstractMessage::class); $this->assertInstanceOf(MessageInterface::class, $message); } @@ -41,7 +39,7 @@ public function testItHasTheRightInterface(): void */ public function testGetMetadataHasEmptyArraryAsDefaultValue(): void { - $message = Msg::new(); + $message = $this->createMock(AbstractMessage::class)::new(); $this->assertCount(6, $message->getMetadata()); } @@ -51,7 +49,7 @@ public function testGetMetadataHasEmptyArraryAsDefaultValue(): void */ public function testWithMetadataReturnsNewStatic(): void { - $message = Msg::new(); + $message = $this->createMock(AbstractMessage::class)::new(); $return = $message->withMetadata([ Metadata::EVENT_TYPE => 'test', @@ -64,7 +62,8 @@ public function testWithMetadataReturnsNewStatic(): void */ public function testWithMetadataWorksCorrectly(): void { - $message = Msg::new()->withMetadata([ + $message = $this->createMock(AbstractMessage::class); + $message = $message::new()->withMetadata([ Metadata::EVENT_TYPE => 'test', ]); @@ -81,7 +80,7 @@ public function testWithMetadataWorksCorrectly(): void */ public function testGettersWithEmptyMetadata(): void { - $message = Msg::new(); + $message = $this->createMock(AbstractMessage::class)::new(); $this->expectException(EventSourcingException::class); $this->assertSame('', $message->getEventId()); @@ -103,7 +102,8 @@ public function testGettersWithEmptyMetadata(): void */ public function testGettersWithMetadata(): void { - $message = Msg::new()->withMetadata([ + $message = $this->createMock(AbstractMessage::class); + $message = $message::new()->withMetadata([ Metadata::EVENT_ID => 'event-id', Metadata::EVENT_TYPE => 'event.type', Metadata::TIMESTAMP => '2022-04-20', @@ -126,7 +126,8 @@ public function testGettersWithMetadata(): void */ public function testGetAggregateIdReturnsCorrectInterface(): void { - $message = Msg::new()->withMetadata([ + $message = $this->createMock(AbstractMessage::class); + $message = $message::new()->withMetadata([ Metadata::AGGREGATE_ID => 'aggregate-id', ]); @@ -139,7 +140,8 @@ public function testGetAggregateIdReturnsCorrectInterface(): void */ public function testGetAggregateVersionReturnsCorrectInterface(): void { - $message = Msg::new()->withMetadata([ + $message = $this->createMock(AbstractMessage::class); + $message = $message::new()->withMetadata([ Metadata::AGGREGATE_VERSION => 123, ]); @@ -151,7 +153,7 @@ public function testGetAggregateVersionReturnsCorrectInterface(): void */ public function testGetPayloadHasEmptyArraryAsDefaultValue(): void { - $message = Msg::new(); + $message = $this->createMock(AbstractMessage::class)::new(); $this->assertCount(0, $message->getPayload()); } @@ -161,7 +163,7 @@ public function testGetPayloadHasEmptyArraryAsDefaultValue(): void */ public function testWithPayloadReturnsNewStatic(): void { - $message = Msg::new(); + $message = $this->createMock(AbstractMessage::class)::new(); $return = $message->withPayload([ 'key' => 'val', @@ -174,7 +176,8 @@ public function testWithPayloadReturnsNewStatic(): void */ public function testWithPayloadWorksCorrectly(): void { - $message = Msg::new()->withPayload([ + $message = $this->createMock(AbstractMessage::class); + $message = $message::new()->withPayload([ 'key' => 'val', ]); diff --git a/src/SonsOfPHP/Component/EventSourcing/Tests/Message/Enricher/MessageEnricherTest.php b/src/SonsOfPHP/Component/EventSourcing/Tests/Message/Enricher/MessageEnricherTest.php index 8f87ef2d..a0c5d434 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Tests/Message/Enricher/MessageEnricherTest.php +++ b/src/SonsOfPHP/Component/EventSourcing/Tests/Message/Enricher/MessageEnricherTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\EventSourcing\Tests\Message; +namespace SonsOfPHP\Component\EventSourcing\Tests\Message\Enricher; use PHPUnit\Framework\TestCase; use SonsOfPHP\Component\EventSourcing\Message\Enricher\Handler\NullMessageEnricherHandler; diff --git a/src/SonsOfPHP/Component/EventSourcing/Tests/Test/CountEventsRaisedTest.php b/src/SonsOfPHP/Component/EventSourcing/Tests/Test/CountEventsRaisedTest.php index 69a764ca..9e118635 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Tests/Test/CountEventsRaisedTest.php +++ b/src/SonsOfPHP/Component/EventSourcing/Tests/Test/CountEventsRaisedTest.php @@ -10,6 +10,8 @@ /** * @coversDefaultClass \SonsOfPHP\Component\EventSourcing\Test\CountEventsRaised + * + * @uses \SonsOfPHP\Component\EventSourcing\Test\CountEventsRaised */ final class CountEventsRaisedTest extends TestCase { diff --git a/src/SonsOfPHP/Component/EventSourcing/Tests/Test/EventRaisedTest.php b/src/SonsOfPHP/Component/EventSourcing/Tests/Test/EventRaisedTest.php index 006792c4..7b1dd465 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Tests/Test/EventRaisedTest.php +++ b/src/SonsOfPHP/Component/EventSourcing/Tests/Test/EventRaisedTest.php @@ -10,6 +10,8 @@ /** * @coversDefaultClass \SonsOfPHP\Component\EventSourcing\Test\EventRaised + * + * @uses \SonsOfPHP\Component\EventSourcing\Test\EventRaised */ final class EventRaisedTest extends TestCase { diff --git a/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/ChainAdapterTest.php b/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/ChainAdapterTest.php index 75b8c36c..426550e8 100644 --- a/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/ChainAdapterTest.php +++ b/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/ChainAdapterTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\Filesystem\Tests; +namespace SonsOfPHP\Component\Filesystem\Tests\Adapter; use PHPUnit\Framework\TestCase; use SonsOfPHP\Component\Filesystem\Adapter\AdapterInterface; diff --git a/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/InMemoryAdapterTest.php b/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/InMemoryAdapterTest.php index ef9fa5b9..135fa225 100644 --- a/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/InMemoryAdapterTest.php +++ b/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/InMemoryAdapterTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\Filesystem\Tests; +namespace SonsOfPHP\Component\Filesystem\Tests\Adapter; use PHPUnit\Framework\TestCase; use SonsOfPHP\Component\Filesystem\Adapter\AdapterInterface; diff --git a/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/NativeAdapterTest.php b/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/NativeAdapterTest.php index 833833d1..2474974a 100644 --- a/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/NativeAdapterTest.php +++ b/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/NativeAdapterTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\Filesystem\Tests; +namespace SonsOfPHP\Component\Filesystem\Tests\Adapter; use PHPUnit\Framework\TestCase; use SonsOfPHP\Component\Filesystem\Adapter\AdapterInterface; diff --git a/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/NullAdapterTest.php b/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/NullAdapterTest.php index 654d747a..f95a1084 100644 --- a/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/NullAdapterTest.php +++ b/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/NullAdapterTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\Filesystem\Tests; +namespace SonsOfPHP\Component\Filesystem\Tests\Adapter; use PHPUnit\Framework\TestCase; use SonsOfPHP\Component\Filesystem\Adapter\AdapterInterface; diff --git a/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/ReadOnlyAdapterTest.php b/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/ReadOnlyAdapterTest.php index 939058de..32f9597b 100644 --- a/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/ReadOnlyAdapterTest.php +++ b/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/ReadOnlyAdapterTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\Filesystem\Tests; +namespace SonsOfPHP\Component\Filesystem\Tests\Adapter; use PHPUnit\Framework\MockObject; use PHPUnit\Framework\TestCase; diff --git a/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/WormAdapterTest.php b/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/WormAdapterTest.php index dbaae807..66569e7e 100644 --- a/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/WormAdapterTest.php +++ b/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/WormAdapterTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\Filesystem\Tests; +namespace SonsOfPHP\Component\Filesystem\Tests\Adapter; use PHPUnit\Framework\TestCase; use SonsOfPHP\Component\Filesystem\Adapter\AdapterInterface; diff --git a/src/SonsOfPHP/Component/HttpFactory/HttpFactory.php b/src/SonsOfPHP/Component/HttpFactory/HttpFactory.php index e8ad321a..1a63bd7a 100644 --- a/src/SonsOfPHP/Component/HttpFactory/HttpFactory.php +++ b/src/SonsOfPHP/Component/HttpFactory/HttpFactory.php @@ -15,7 +15,7 @@ final class HttpFactory implements RequestFactoryInterface { use RequestFactoryTrait; use ResponseFactoryTrait; - use ServerResponseFactoryTrait; + use ServerRequestFactoryTrait; use StreamFactoryTrait; use UploadedFileFactoryTrait; use UriFactoryTrait; diff --git a/src/SonsOfPHP/Component/HttpFactory/Tests/ResponseFactoryTest.php b/src/SonsOfPHP/Component/HttpFactory/Tests/ResponseFactoryTest.php index 13bb0373..b94da85b 100644 --- a/src/SonsOfPHP/Component/HttpFactory/Tests/ResponseFactoryTest.php +++ b/src/SonsOfPHP/Component/HttpFactory/Tests/ResponseFactoryTest.php @@ -11,6 +11,7 @@ /** * @coversDefaultClass \SonsOfPHP\Component\HttpFactory\ResponseFactory + * @uses \SonsOfPHP\Component\HttpMessage\Response */ final class ResponseFactoryTest extends TestCase { @@ -23,13 +24,21 @@ public function testItImplementsCorrectInterface(): void } /** + * @dataProvider validCreateResponseProvider + * * @covers ::createResponse - * @uses \SonsOfPHP\Component\HttpMessage\Response */ - public function testCreateResponseWorksAsExpected(): void + public function testCreateResponseWorksAsExpected(int $code, string $reasonPhrase): void { $factory = new ResponseFactory(); - $this->assertInstanceOf(ResponseInterface::class, $factory->createResponse()); + $this->assertInstanceOf(ResponseInterface::class, $factory->createResponse($code, $reasonPhrase)); + } + + public static function validCreateResponseProvider(): iterable + { + yield [200, 'OK']; + yield [201, 'Not Content']; + yield [404, 'Not Found']; } } diff --git a/src/SonsOfPHP/Component/HttpHandler/.gitattributes b/src/SonsOfPHP/Component/HttpHandler/.gitattributes new file mode 100644 index 00000000..84c7add0 --- /dev/null +++ b/src/SonsOfPHP/Component/HttpHandler/.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/HttpHandler/.gitignore b/src/SonsOfPHP/Component/HttpHandler/.gitignore new file mode 100644 index 00000000..5414c2c6 --- /dev/null +++ b/src/SonsOfPHP/Component/HttpHandler/.gitignore @@ -0,0 +1,3 @@ +composer.lock +phpunit.xml +vendor/ diff --git a/src/SonsOfPHP/Component/HttpHandler/Exception/HttpHandlerException.php b/src/SonsOfPHP/Component/HttpHandler/Exception/HttpHandlerException.php new file mode 100644 index 00000000..f2dbb6ef --- /dev/null +++ b/src/SonsOfPHP/Component/HttpHandler/Exception/HttpHandlerException.php @@ -0,0 +1,12 @@ + + */ +class HttpHandlerException extends \Exception implements HttpHandlerExceptionInterface {} diff --git a/src/SonsOfPHP/Component/HttpHandler/HttpHandler.php b/src/SonsOfPHP/Component/HttpHandler/HttpHandler.php new file mode 100644 index 00000000..dba8c823 --- /dev/null +++ b/src/SonsOfPHP/Component/HttpHandler/HttpHandler.php @@ -0,0 +1,31 @@ + + */ +class HttpHandler implements RequestHandlerInterface +{ + public function __construct(private MiddlewareStack $stack) {} + + /** + * {@inheritdoc} + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + if (0 === $this->stack->count()) { + throw new \Exception('No Middleware in the queue.'); + } + + $middleware = $this->stack->next(); + + return $middleware->process($request, $this); + } +} diff --git a/src/SonsOfPHP/Component/HttpHandler/LICENSE b/src/SonsOfPHP/Component/HttpHandler/LICENSE new file mode 100644 index 00000000..39238382 --- /dev/null +++ b/src/SonsOfPHP/Component/HttpHandler/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/HttpHandler/MiddlewareStack.php b/src/SonsOfPHP/Component/HttpHandler/MiddlewareStack.php new file mode 100644 index 00000000..cc5dbbee --- /dev/null +++ b/src/SonsOfPHP/Component/HttpHandler/MiddlewareStack.php @@ -0,0 +1,65 @@ + + */ +class MiddlewareStack implements MiddlewareStackInterface +{ + private array $middlewares = []; + //private $resolver; + + //public function __construct($resolver) + //{ + // $this->resolver = $resolver; + //} + + /** + * Adds a new middleware to the stack. Middlewares can be prioritized and + * will be ordered from the lowest number to the highest number (ascending + * order). + */ + public function add(MiddlewareInterface|\Closure $middleware, int $priority = 0): self + { + $this->middlewares[$priority][] = $middleware; + ksort($this->middlewares); + + return $this; + } + + public function next(): MiddlewareInterface + { + $priorityStack = array_shift($this->middlewares); + $middleware = array_shift($priorityStack); + if (0 !== count($priorityStack)) { + array_unshift($this->middlewares, $priorityStack); + } + + if ($middleware instanceof \Closure) { + return new class ($middleware) implements MiddlewareInterface { + public function __construct(private \Closure $closure) {} + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + return $this->closure($request, $handler); + } + }; + } + + return $middleware; + } + + public function count(): int + { + return count($this->middlewares); + } +} diff --git a/src/SonsOfPHP/Component/HttpHandler/README.md b/src/SonsOfPHP/Component/HttpHandler/README.md new file mode 100644 index 00000000..9e0547d0 --- /dev/null +++ b/src/SonsOfPHP/Component/HttpHandler/README.md @@ -0,0 +1,16 @@ +Sons of PHP - HttpHandler +========================= + +## 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/http-handler/ +[issues]: https://github.com/SonsOfPHP/sonsofphp/issues?q=is%3Aopen+is%3Aissue+label%3AHttpHandler +[pull-requests]: https://github.com/SonsOfPHP/sonsofphp/pulls?q=is%3Aopen+is%3Apr+label%3AHttpHandler diff --git a/src/SonsOfPHP/Component/HttpHandler/Tests/HttpHandlerTest.php b/src/SonsOfPHP/Component/HttpHandler/Tests/HttpHandlerTest.php new file mode 100644 index 00000000..ce9ff9d0 --- /dev/null +++ b/src/SonsOfPHP/Component/HttpHandler/Tests/HttpHandlerTest.php @@ -0,0 +1,60 @@ +request = $this->createMock(ServerRequestInterface::class); + $this->response = $this->createMock(ResponseInterface::class); + $this->stack = new MiddlewareStack(); + } + + /** + * @covers ::__construct + */ + public function testItHasTheCorrectInterface(): void + { + $handler = new HttpHandler($this->stack); + + $this->assertInstanceOf(RequestHandlerInterface::class, $handler); + } + + /** + * @covers ::handle + */ + public function testHandle(): void + { + $this->stack->add(new class () implements MiddlewareInterface { + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + return new Response(); + } + }); + $handler = new HttpHandler($this->stack); + + $this->assertNotNull($handler->handle($this->request)); + } +} diff --git a/src/SonsOfPHP/Component/HttpHandler/Tests/MiddlewareStackTest.php b/src/SonsOfPHP/Component/HttpHandler/Tests/MiddlewareStackTest.php new file mode 100644 index 00000000..cd1a5652 --- /dev/null +++ b/src/SonsOfPHP/Component/HttpHandler/Tests/MiddlewareStackTest.php @@ -0,0 +1,164 @@ +assertInstanceOf(MiddlewareStackInterface::class, $stack); + } + + /** + * @covers ::add + */ + public function testAdd(): void + { + $stack = new MiddlewareStack(); + $middlewares = new \ReflectionProperty($stack, 'middlewares'); + $this->assertCount(0, $middlewares->getValue($stack)); + + $stack->add(function (): void {}); + $this->assertCount(1, $middlewares->getValue($stack)); + } + + /** + * @covers ::count + */ + public function testCount(): void + { + $stack = new MiddlewareStack(); + $this->assertCount(0, $stack); + + $stack->add(function (): void {}); + $this->assertCount(1, $stack); + } + + /** + * @covers ::add + */ + public function testAddWillPrioritizeCorrectly(): void + { + $stack = new MiddlewareStack(); + $middlewares = new \ReflectionProperty($stack, 'middlewares'); + $this->assertCount(0, $middlewares->getValue($stack)); + + $one = function (): void {}; + $two = function (): void {}; + $three = function (): void {}; + + $stack->add($three, 255); + $stack->add($two); // default + $stack->add($one, -255); + + $middlewareStack = $middlewares->getValue($stack); + $this->assertCount(3, $middlewareStack); + $this->assertSame($one, $middlewareStack[-255][0]); + $this->assertSame($two, $middlewareStack[0][0]); + $this->assertSame($three, $middlewareStack[255][0]); + } + + /** + * @covers ::next + */ + public function testNextReturnsMiddlewareIfClosure(): void + { + $stack = new MiddlewareStack(); + + $stack->add(function (): void {}); + + $this->assertInstanceOf(MiddlewareInterface::class, $stack->next()); + } + + /** + * @covers ::next + */ + public function testNextReturnsCorrectlyWhenMultipleMiddlewareHasSamePriority(): void + { + $stack = new MiddlewareStack(); + + $one = new class () implements MiddlewareInterface { + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + return $handler->handle($request); + } + }; + $two = new class () implements MiddlewareInterface { + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + return $handler->handle($request); + } + }; + $three = new class () implements MiddlewareInterface { + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + return $handler->handle($request); + } + }; + + + $stack->add($one); + $stack->add($two); + $stack->add($three); + + $this->assertSame($one, $stack->next()); + $this->assertSame($two, $stack->next()); + $this->assertSame($three, $stack->next()); + } + + /** + * @covers ::next + */ + public function testNextReturnsMiddlewareInCorrectOrder(): void + { + $stack = new MiddlewareStack(); + + $one = new class () implements MiddlewareInterface { + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + return $handler->handle($request); + } + }; + $two = new class () implements MiddlewareInterface { + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + return $handler->handle($request); + } + }; + $three = new class () implements MiddlewareInterface { + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + return $handler->handle($request); + } + }; + + + $stack->add($three, 255); + $stack->add($two); + $stack->add($one, -255); + + $this->assertSame($one, $stack->next()); + $this->assertSame($two, $stack->next()); + $this->assertSame($three, $stack->next()); + } +} diff --git a/src/SonsOfPHP/Component/HttpHandler/composer.json b/src/SonsOfPHP/Component/HttpHandler/composer.json new file mode 100644 index 00000000..6df3c765 --- /dev/null +++ b/src/SonsOfPHP/Component/HttpHandler/composer.json @@ -0,0 +1,63 @@ +{ + "name": "sonsofphp/http-handler", + "type": "library", + "description": "", + "keywords": [ + "psr15", + "psr-15", + "http-handler" + ], + "homepage": "https://github.com/SonsOfPHP/http-handler", + "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" + }, + "autoload": { + "psr-4": { + "SonsOfPHP\\Component\\HttpHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "php": ">=8.1", + "sonsofphp/http-handler-contract": "0.3.x-dev", + "psr/http-server-handler": "^1.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "sonsofphp/http-message": "0.3.x-dev" + }, + "provide": { + "sonsofphp/http-handler-implementation": "0.3.x-dev", + "psr/http-server-handler-implementation": "^1.0", + "psr/http-server-middleware-implementation": "^1.0" + }, + "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" + } + ] +} \ No newline at end of file diff --git a/src/SonsOfPHP/Component/Link/Link.php b/src/SonsOfPHP/Component/Link/Link.php index a5d3b484..bc1e577c 100644 --- a/src/SonsOfPHP/Component/Link/Link.php +++ b/src/SonsOfPHP/Component/Link/Link.php @@ -5,7 +5,6 @@ namespace SonsOfPHP\Component\Link; use Psr\Link\LinkInterface; -use Psr\Link\EvolvableLinkInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Link/LinkProvider.php b/src/SonsOfPHP/Component/Link/LinkProvider.php index 63a7ab37..63fe5ee9 100644 --- a/src/SonsOfPHP/Component/Link/LinkProvider.php +++ b/src/SonsOfPHP/Component/Link/LinkProvider.php @@ -4,8 +4,8 @@ namespace SonsOfPHP\Component\Link; -use Psr\Link\LinkProviderInterface; use Psr\Link\LinkInterface; +use Psr\Link\LinkProviderInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Link/Tests/EvolvableLinkProviderTest.php b/src/SonsOfPHP/Component/Link/Tests/EvolvableLinkProviderTest.php index 8d2e0506..77aaa876 100644 --- a/src/SonsOfPHP/Component/Link/Tests/EvolvableLinkProviderTest.php +++ b/src/SonsOfPHP/Component/Link/Tests/EvolvableLinkProviderTest.php @@ -5,9 +5,9 @@ namespace SonsOfPHP\Component\Link\Tests; use PHPUnit\Framework\TestCase; -use SonsOfPHP\Component\Link\Link; -use SonsOfPHP\Component\Link\EvolvableLinkProvider; use Psr\Link\EvolvableLinkProviderInterface; +use SonsOfPHP\Component\Link\EvolvableLinkProvider; +use SonsOfPHP\Component\Link\Link; /** * @coversDefaultClass \SonsOfPHP\Component\Link\EvolvableLinkProvider diff --git a/src/SonsOfPHP/Component/Link/Tests/EvolvableLinkTest.php b/src/SonsOfPHP/Component/Link/Tests/EvolvableLinkTest.php index 647685ad..20d6ba9b 100644 --- a/src/SonsOfPHP/Component/Link/Tests/EvolvableLinkTest.php +++ b/src/SonsOfPHP/Component/Link/Tests/EvolvableLinkTest.php @@ -5,8 +5,8 @@ namespace SonsOfPHP\Component\Link\Tests; use PHPUnit\Framework\TestCase; -use SonsOfPHP\Component\Link\EvolvableLink; use Psr\Link\EvolvableLinkInterface; +use SonsOfPHP\Component\Link\EvolvableLink; /** * @coversDefaultClass \SonsOfPHP\Component\Link\EvolvableLink @@ -31,7 +31,7 @@ public function testItHasTheCorrectInterface(): void */ public function testWithHrefWhenStringable(): void { - $href = new class implements \Stringable { + $href = new class () implements \Stringable { public function __toString(): string { return 'https://docs.sonsofphp.com'; diff --git a/src/SonsOfPHP/Component/Link/Tests/LinkProviderTest.php b/src/SonsOfPHP/Component/Link/Tests/LinkProviderTest.php index db385853..e217117b 100644 --- a/src/SonsOfPHP/Component/Link/Tests/LinkProviderTest.php +++ b/src/SonsOfPHP/Component/Link/Tests/LinkProviderTest.php @@ -5,9 +5,9 @@ namespace SonsOfPHP\Component\Link\Tests; use PHPUnit\Framework\TestCase; +use Psr\Link\LinkProviderInterface; use SonsOfPHP\Component\Link\Link; use SonsOfPHP\Component\Link\LinkProvider; -use Psr\Link\LinkProviderInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Link\LinkProvider diff --git a/src/SonsOfPHP/Component/Link/Tests/LinkTest.php b/src/SonsOfPHP/Component/Link/Tests/LinkTest.php index 261494b7..698c6e11 100644 --- a/src/SonsOfPHP/Component/Link/Tests/LinkTest.php +++ b/src/SonsOfPHP/Component/Link/Tests/LinkTest.php @@ -5,8 +5,8 @@ namespace SonsOfPHP\Component\Link\Tests; use PHPUnit\Framework\TestCase; -use SonsOfPHP\Component\Link\Link; use Psr\Link\LinkInterface; +use SonsOfPHP\Component\Link\Link; /** * @coversDefaultClass \SonsOfPHP\Component\Link\Link diff --git a/src/SonsOfPHP/Component/Link/composer.json b/src/SonsOfPHP/Component/Link/composer.json index e96f4117..b3a8b004 100644 --- a/src/SonsOfPHP/Component/Link/composer.json +++ b/src/SonsOfPHP/Component/Link/composer.json @@ -53,4 +53,4 @@ "url": "https://tidelift.com/subscription/pkg/packagist-sonsofphp-sonsofphp" } ] -} +} \ No newline at end of file diff --git a/src/SonsOfPHP/Component/Mailer/.gitattributes b/src/SonsOfPHP/Component/Mailer/.gitattributes new file mode 100644 index 00000000..84c7add0 --- /dev/null +++ b/src/SonsOfPHP/Component/Mailer/.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/Mailer/.gitignore b/src/SonsOfPHP/Component/Mailer/.gitignore new file mode 100644 index 00000000..5414c2c6 --- /dev/null +++ b/src/SonsOfPHP/Component/Mailer/.gitignore @@ -0,0 +1,3 @@ +composer.lock +phpunit.xml +vendor/ diff --git a/src/SonsOfPHP/Component/Mailer/Address.php b/src/SonsOfPHP/Component/Mailer/Address.php new file mode 100644 index 00000000..00e7542d --- /dev/null +++ b/src/SonsOfPHP/Component/Mailer/Address.php @@ -0,0 +1,78 @@ + + */ +final class Address implements AddressInterface +{ + public function __construct(private string $email, private ?string $name = null) {} + + public function __toString(): string + { + if (null === $this->name) { + return $this->email; + } + + return sprintf('%s <%s>', $this->name, $this->email); + } + + /** + * {@inheritdoc} + */ + public static function from(string $address): self + { + return new self($address); + } + + /** + * {@inheritdoc} + */ + public function getEmail(): string + { + return $this->email; + } + + /** + * {@inheritdoc} + */ + public function withEmail(string $email): static + { + if ($email === $this->email) { + return $this; + } + + $that = clone $this; + $that->email = $email; + + return $that; + } + + /** + * {@inheritdoc} + */ + public function getName(): ?string + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function withName(?string $name): static + { + if ($name === $this->name) { + return $this; + } + + $that = clone $this; + $that->name = $name; + + return $that; + } +} diff --git a/src/SonsOfPHP/Component/Mailer/LICENSE b/src/SonsOfPHP/Component/Mailer/LICENSE new file mode 100644 index 00000000..39238382 --- /dev/null +++ b/src/SonsOfPHP/Component/Mailer/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/Mailer/Mailer.php b/src/SonsOfPHP/Component/Mailer/Mailer.php new file mode 100644 index 00000000..df56acbb --- /dev/null +++ b/src/SonsOfPHP/Component/Mailer/Mailer.php @@ -0,0 +1,34 @@ + + */ +final class Mailer implements MailerInterface +{ + public function __construct( + private TransportInterface $transport, + private MiddlewareHandlerInterface $handler = new MiddlewareHandler(), + ) {} + + public function addMiddleware(MiddlewareInterface $middleware): void + { + $this->handler->getMiddlewareStack()->add($middleware); + } + + public function send(MessageInterface $message): void + { + $message = $this->handler->handle($message); + + $this->transport->send($message); + } +} diff --git a/src/SonsOfPHP/Component/Mailer/Message.php b/src/SonsOfPHP/Component/Mailer/Message.php new file mode 100644 index 00000000..a8e617fa --- /dev/null +++ b/src/SonsOfPHP/Component/Mailer/Message.php @@ -0,0 +1,107 @@ + + */ +class Message implements MessageInterface +{ + private array $headers = []; + private string $body; + + public function setSubject(string $subject): self + { + $this->addHeader('subject', $subject); + } + + public function getSubject(): ?string + { + return $this->getHeader('subject'); + } + + public function setFrom(AddressInterface|string $address): self + { + $this->addHeader('from', $address); + } + + public function getFrom(): ?string + { + return $this->getHeader('from'); + } + + public function setTo(AddressInterface|string $address): self + { + $this->addHeader('to', $address); + } + + public function getTo(): ?string + { + return $this->getHeader('to'); + } + + /** + * {@inheritdoc} + */ + public function getBody(): ?string + { + return $this->body ?? null; + } + + /** + * {@inheritdoc} + */ + public function setBody(string $body): self + { + $this->body = $body; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getHeaders(): array + { + return $this->headers; + } + + /** + * {@inheritdoc} + */ + public function getHeader(string $name): ?string + { + return $this->headers[strtolower($name)] ?? null; + } + + /** + * {@inheritdoc} + */ + public function hasHeader(string $name): bool + { + return array_key_exists(strtolower($name), $this->headers); + } + + /** + * {@inheritdoc} + */ + public function addHeader(string $name, AddressInterface|string $value): self + { + if ($value instanceof AddressInterface) { + $value = (string) $value; + } + + if ($this->hasHeader($name)) { + $value = $this->headers[strtolower($name)] . ', ' . $value; + } + + $this->headers[strtolower($name)] = $value; + + return $this; + } +} diff --git a/src/SonsOfPHP/Component/Mailer/MiddlewareHandler.php b/src/SonsOfPHP/Component/Mailer/MiddlewareHandler.php new file mode 100644 index 00000000..21c96cbe --- /dev/null +++ b/src/SonsOfPHP/Component/Mailer/MiddlewareHandler.php @@ -0,0 +1,38 @@ + + */ +class MiddlewareHandler implements MiddlewareHandlerInterface +{ + public function __construct( + private MiddlewareStackInterface $stack = new MiddlewareStack() + ) {} + + public function getMiddlewareStack(): MiddlewareStackInterface + { + return $this->stack; + } + + /** + * {@inheritdoc} + */ + public function handle(MessageInterface $message): MessageInterface + { + if (0 === count($this->stack)) { + return $message; + } + + $next = $this->stack->next(); + + return $next($message, $this); + } +} diff --git a/src/SonsOfPHP/Component/Mailer/MiddlewareStack.php b/src/SonsOfPHP/Component/Mailer/MiddlewareStack.php new file mode 100644 index 00000000..7a932966 --- /dev/null +++ b/src/SonsOfPHP/Component/Mailer/MiddlewareStack.php @@ -0,0 +1,31 @@ + + */ +class MiddlewareStack implements MiddlewareStackInterface, \Countable +{ + private array $middlewares = []; + + public function add(MiddlewareInterface $middleware): void + { + $this->middlewares[] = $middleware; + } + + public function next(): MiddlewareInterface + { + return array_shift($this->middlewares); + } + + public function count(): int + { + return count($this->middlewares); + } +} diff --git a/src/SonsOfPHP/Component/Mailer/README.md b/src/SonsOfPHP/Component/Mailer/README.md new file mode 100644 index 00000000..4662eb3f --- /dev/null +++ b/src/SonsOfPHP/Component/Mailer/README.md @@ -0,0 +1,16 @@ +Sons of PHP - Mailer +==================== + +## 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/mailer/ +[issues]: https://github.com/SonsOfPHP/sonsofphp/issues?q=is%3Aopen+is%3Aissue+label%3AMailer +[pull-requests]: https://github.com/SonsOfPHP/sonsofphp/pulls?q=is%3Aopen+is%3Apr+label%3AMailer diff --git a/src/SonsOfPHP/Component/Mailer/Tests/AddressTest.php b/src/SonsOfPHP/Component/Mailer/Tests/AddressTest.php new file mode 100644 index 00000000..523a8310 --- /dev/null +++ b/src/SonsOfPHP/Component/Mailer/Tests/AddressTest.php @@ -0,0 +1,77 @@ +assertInstanceOf(AddressInterface::class, $address); + } + + /** + * @covers ::getEmail + * @covers ::withEmail + */ + public function testEmail(): void + { + $address = new Address('joshua@sonsofphp.com'); + + $this->assertSame('joshua@sonsofphp.com', $address->getEmail()); + + $this->assertSame($address, $address->withEmail('joshua@sonsofphp.com')); + $this->assertNotSame($address, $address->withEmail('joshua.estes@sonsofphp.com')); + } + + /** + * @covers ::getName + * @covers ::withName + */ + public function testName(): void + { + $address = new Address('joshua@sonsofphp.com'); + + $this->assertNull($address->getName()); + + $this->assertNotSame($address, $address->withName('Joshua')); + + $address = $address->withName('Joshua'); + $this->assertSame('Joshua', $address->getName()); + } + + /** + * @covers ::__toString + */ + public function testToStringMagicMethod(): void + { + $this->assertSame('joshua@sonsofphp.com', (string) new Address('joshua@sonsofphp.com')); + $this->assertSame('Joshua Estes ', (string) new Address('joshua@sonsofphp.com', 'Joshua Estes')); + } + + /** + * @covers ::from + */ + public function testFrom(): void + { + // Would be best to use a data provider here + $address = Address::from('joshua@sonsofphp.com'); + + $this->assertSame('joshua@sonsofphp.com', $address->getEmail()); + } +} diff --git a/src/SonsOfPHP/Component/Mailer/Tests/MailerTest.php b/src/SonsOfPHP/Component/Mailer/Tests/MailerTest.php new file mode 100644 index 00000000..5017423a --- /dev/null +++ b/src/SonsOfPHP/Component/Mailer/Tests/MailerTest.php @@ -0,0 +1,52 @@ +transport = $this->createMock(TransportInterface::class); + $this->message = $this->createMock(MessageInterface::class); + } + + /** + * @covers ::__construct + */ + public function testItHasTheCorrectInterface(): void + { + $mailer = new Mailer($this->transport); + + $this->assertInstanceOf(MailerInterface::class, $mailer); + } + + /** + * @covers ::send + */ + public function testSend(): void + { + $this->transport->expects($this->once())->method('send'); + + $mailer = new Mailer($this->transport); + + $mailer->send($this->message); + } +} diff --git a/src/SonsOfPHP/Component/Mailer/Tests/MessageTest.php b/src/SonsOfPHP/Component/Mailer/Tests/MessageTest.php new file mode 100644 index 00000000..6a6a6a6b --- /dev/null +++ b/src/SonsOfPHP/Component/Mailer/Tests/MessageTest.php @@ -0,0 +1,96 @@ +assertInstanceOf(MessageInterface::class, $message); + } + + /** + * @covers ::setBody + */ + public function testSetBody(): void + { + $message = new Message(); + $message->setBody('body'); + $this->assertSame('body', $message->getBody()); + } + + /** + * @covers ::getBody + */ + public function testGetBody(): void + { + $message = new Message(); + $this->assertNull($message->getBody()); + + $message->setBody('body'); + $this->assertSame('body', $message->getBody()); + } + + /** + * @covers ::getHeaders + */ + public function testGetHeadersWhenEmpty(): void + { + $message = new Message(); + $this->assertCount(0, $message->getHeaders()); + } + + /** + * @covers ::getHeader + */ + public function testGetHeader(): void + { + $message = new Message(); + $this->assertNull($message->getHeader('to')); + + $message->addHeader('to', 'joshua@sonsofphp.com'); + $this->assertSame('joshua@sonsofphp.com', $message->getHeader('to')); + } + + /** + * @covers ::hasHeader + */ + public function testHasHeader(): void + { + $message = new Message(); + $this->assertFalse($message->hasHeader('to')); + + $message->addHeader('to', 'joshua@sonsofphp.com'); + $this->assertTrue($message->hasHeader('TO')); + } + + /** + * @covers ::addHeader + */ + public function testAddHeader(): void + { + $message = new Message(); + + $message->addHeader('To', 'joshua@sonsofphp.com'); + $this->assertSame('joshua@sonsofphp.com', $message->getHeader('to')); + + $message->addHeader('To', 'joshua.estes@sonsofphp.com'); + $this->assertSame('joshua@sonsofphp.com, joshua.estes@sonsofphp.com', $message->getHeader('to')); + } +} diff --git a/src/SonsOfPHP/Component/Mailer/Tests/MiddlewareHandlerTest.php b/src/SonsOfPHP/Component/Mailer/Tests/MiddlewareHandlerTest.php new file mode 100644 index 00000000..e5042be0 --- /dev/null +++ b/src/SonsOfPHP/Component/Mailer/Tests/MiddlewareHandlerTest.php @@ -0,0 +1,81 @@ +message = $this->createMock(MessageInterface::class); + $this->stack = new MiddlewareStack(); + } + + /** + * @covers ::__construct + */ + public function testItHasTheCorrectInterface(): void + { + $handler = new MiddlewareHandler(); + + $this->assertInstanceOf(MiddlewareHandlerInterface::class, $handler); + } + + /** + * @covers ::getMiddlewareStack + */ + public function testGetMiddlewareStack(): void + { + $handler = new MiddlewareHandler(); + $this->assertInstanceOf(MiddlewareStackInterface::class, $handler->getMiddlewareStack()); + + $handler = new MiddlewareHandler($this->stack); + $this->assertSame($this->stack, $handler->getMiddlewareStack()); + } + + /** + * @covers ::handle + */ + public function testHandleWhenNoMoreMiddleware(): void + { + $handler = new MiddlewareHandler(); + + $this->assertSame($this->message, $handler->handle($this->message)); + } + + /** + * @covers ::handle + */ + public function testHandle(): void + { + $middleware = new class () implements MiddlewareInterface { + public function __invoke(MessageInterface $message, MiddlewareHandlerInterface $handler) + { + return $message; + } + }; + + $handler = new MiddlewareHandler($this->stack); + $handler->getMiddlewareStack()->add($middleware); + + $this->assertSame($this->message, $handler->handle($this->message)); + } +} diff --git a/src/SonsOfPHP/Component/Mailer/Tests/MiddlewareStackTest.php b/src/SonsOfPHP/Component/Mailer/Tests/MiddlewareStackTest.php new file mode 100644 index 00000000..fa904845 --- /dev/null +++ b/src/SonsOfPHP/Component/Mailer/Tests/MiddlewareStackTest.php @@ -0,0 +1,84 @@ +assertInstanceOf(MiddlewareStackInterface::class, $stack); + } + + /** + * @covers ::add + */ + public function testAdd(): void + { + $middleware = new class () implements MiddlewareInterface { + public function __invoke(MessageInterface $message, MiddlewareHandlerInterface $handler) + { + return $message; + } + }; + $property = new \ReflectionProperty(MiddlewareStack::class, 'middlewares'); + + $stack = new MiddlewareStack(); + + $this->assertCount(0, $property->getValue($stack)); + $stack->add($middleware); + $this->assertCount(1, $property->getValue($stack)); + } + + /** + * @covers ::next + */ + public function testNext(): void + { + $middleware = new class () implements MiddlewareInterface { + public function __invoke(MessageInterface $message, MiddlewareHandlerInterface $handler) + { + return $message; + } + }; + $stack = new MiddlewareStack(); + $stack->add($middleware); + + $this->assertSame($middleware, $stack->next()); + } + + /** + * @covers ::count + */ + public function testCount(): void + { + $middleware = new class () implements MiddlewareInterface { + public function __invoke(MessageInterface $message, MiddlewareHandlerInterface $handler) + { + return $message; + } + }; + $stack = new MiddlewareStack(); + $stack->add($middleware); + + $this->assertCount(1, $stack); + } +} diff --git a/src/SonsOfPHP/Component/Mailer/Transport/NativeMailTransport.php b/src/SonsOfPHP/Component/Mailer/Transport/NativeMailTransport.php new file mode 100644 index 00000000..aa6244e2 --- /dev/null +++ b/src/SonsOfPHP/Component/Mailer/Transport/NativeMailTransport.php @@ -0,0 +1,27 @@ + + */ +class NativeMailTransport implements TransportInterface +{ + /** + * {@inheritdoc} + */ + public function send(MessageInterface $message): void + { + mail($message->getHeader('to'), $message->getHeader('subject'), $message->getBody(), $message->getHeaders()); + } +} diff --git a/src/SonsOfPHP/Component/Mailer/Transport/NullTransport.php b/src/SonsOfPHP/Component/Mailer/Transport/NullTransport.php new file mode 100644 index 00000000..1109c305 --- /dev/null +++ b/src/SonsOfPHP/Component/Mailer/Transport/NullTransport.php @@ -0,0 +1,22 @@ + + */ +class NullTransport implements TransportInterface +{ + /** + * {@inheritdoc} + */ + public function send(MessageInterface $message): void {} +} diff --git a/src/SonsOfPHP/Component/Mailer/composer.json b/src/SonsOfPHP/Component/Mailer/composer.json new file mode 100644 index 00000000..477553d9 --- /dev/null +++ b/src/SonsOfPHP/Component/Mailer/composer.json @@ -0,0 +1,54 @@ +{ + "name": "sonsofphp/mailer", + "type": "library", + "description": "", + "keywords": [ + "mailer" + ], + "homepage": "https://github.com/SonsOfPHP/mailer", + "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" + }, + "autoload": { + "psr-4": { + "SonsOfPHP\\Component\\Mailer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "php": ">=8.1", + "sonsofphp/mailer-contract": "0.3.x-dev" + }, + "provide": { + "sonsofphp/mailer-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" + } + ] +} \ No newline at end of file diff --git a/src/SonsOfPHP/Component/Money/Amount.php b/src/SonsOfPHP/Component/Money/Amount.php index 5fe7cecd..d986f484 100644 --- a/src/SonsOfPHP/Component/Money/Amount.php +++ b/src/SonsOfPHP/Component/Money/Amount.php @@ -17,8 +17,8 @@ use SonsOfPHP\Component\Money\Query\Amount\IsPositiveAmountQuery; use SonsOfPHP\Component\Money\Query\Amount\IsZeroAmountQuery; use SonsOfPHP\Contract\Money\AmountInterface; -use SonsOfPHP\Contract\Money\Operator\Amount\AmountOperatorInterface; -use SonsOfPHP\Contract\Money\Query\Amount\AmountQueryInterface; +use SonsOfPHP\Contract\Money\AmountOperatorInterface; +use SonsOfPHP\Contract\Money\AmountQueryInterface; /** * @author Joshua Estes @@ -60,32 +60,32 @@ public function getAmount(): string return $this->amount; } - public function with(AmountOperatorInterface $operator): AmountInterface + public function with(AmountOperatorInterface $operator): static { return $operator->apply($this); } - public function query(AmountQueryInterface $query) + public function query(AmountQueryInterface $query)/*: mixed*/ { return $query->queryFrom($this); } - public function add(AmountInterface $amount): AmountInterface + public function add(AmountInterface $amount): static { return $this->with(new AddAmountOperator($amount)); } - public function subtract(AmountInterface $amount): AmountInterface + public function subtract(AmountInterface $amount): static { return $this->with(new SubtractAmountOperator($amount)); } - public function multiply($multiplier): AmountInterface + public function multiply($multiplier): static { return $this->with(new MultiplyAmountOperator($multiplier)); } - public function divide($divisor): AmountInterface + public function divide($divisor): static { return $this->with(new DivideAmountOperator($divisor)); } diff --git a/src/SonsOfPHP/Component/Money/Currency.php b/src/SonsOfPHP/Component/Money/Currency.php index 470536db..f1188243 100644 --- a/src/SonsOfPHP/Component/Money/Currency.php +++ b/src/SonsOfPHP/Component/Money/Currency.php @@ -6,22 +6,19 @@ use SonsOfPHP\Component\Money\Query\Currency\IsEqualToCurrencyQuery; use SonsOfPHP\Contract\Money\CurrencyInterface; -use SonsOfPHP\Contract\Money\Query\Currency\CurrencyQueryInterface; +use SonsOfPHP\Contract\Money\CurrencyQueryInterface; /** * @author Joshua Estes */ final class Currency implements CurrencyInterface { - private string $currencyCode; - private ?int $numericCode; - private ?int $minorUnit; - - public function __construct(string $currencyCode, int $numericCode = null, int $minorUnit = null) - { + public function __construct( + private string $currencyCode, + private ?int $numericCode = null, + private ?int $minorUnit = null + ) { $this->currencyCode = strtoupper($currencyCode); - $this->numericCode = $numericCode; - $this->minorUnit = $minorUnit; } /** @@ -47,26 +44,41 @@ public static function __callStatic(string $currencyCode, array $args): Currency return new static($currencyCode, $numericCode, $minorUnit); } + /** + * {@inheritdoc} + */ public function query(CurrencyQueryInterface $query) { return $query->queryFrom($this); } + /** + * {@inheritdoc} + */ public function getCurrencyCode(): string { return $this->currencyCode; } + /** + * {@inheritdoc} + */ public function getNumericCode(): ?int { return $this->numericCode; } + /** + * {@inheritdoc} + */ public function getMinorUnit(): ?int { return $this->minorUnit; } + /** + * {@inheritdoc} + */ public function isEqualTo(CurrencyInterface $currency): bool { return $this->query(new IsEqualToCurrencyQuery($currency)); diff --git a/src/SonsOfPHP/Component/Money/CurrencyProvider/AbstractCurrencyProvider.php b/src/SonsOfPHP/Component/Money/CurrencyProvider/AbstractCurrencyProvider.php index ca077e69..83204bed 100644 --- a/src/SonsOfPHP/Component/Money/CurrencyProvider/AbstractCurrencyProvider.php +++ b/src/SonsOfPHP/Component/Money/CurrencyProvider/AbstractCurrencyProvider.php @@ -8,24 +8,33 @@ use SonsOfPHP\Component\Money\Query\CurrencyProvider\HasCurrencyQuery; use SonsOfPHP\Contract\Money\CurrencyInterface; use SonsOfPHP\Contract\Money\CurrencyProviderInterface; -use SonsOfPHP\Contract\Money\Query\CurrencyProvider\CurrencyProviderQueryInterface; +use SonsOfPHP\Contract\Money\CurrencyProviderQueryInterface; /** * @author Joshua Estes */ abstract class AbstractCurrencyProvider implements CurrencyProviderInterface { + /** + * {@inheritdoc} + */ public function query(CurrencyProviderQueryInterface $query) { return $query->queryFrom($this); } - public function hasCurrency($currency): bool + /** + * {@inheritdoc} + */ + public function hasCurrency(CurrencyInterface|string $currency): bool { return $this->query(new HasCurrencyQuery($currency)); } - public function getCurrency($currency): CurrencyInterface + /** + * {@inheritdoc} + */ + public function getCurrency(CurrencyInterface|string $currency): CurrencyInterface { return $this->query(new GetCurrencyQuery($currency)); } diff --git a/src/SonsOfPHP/Component/Money/Formatter/IntlMoneyFormatter.php b/src/SonsOfPHP/Component/Money/Formatter/IntlMoneyFormatter.php new file mode 100644 index 00000000..1847c06b --- /dev/null +++ b/src/SonsOfPHP/Component/Money/Formatter/IntlMoneyFormatter.php @@ -0,0 +1,32 @@ + + */ +class IntlMoneyFormatter implements MoneyFormatterInterface +{ + public function __construct( + private \NumberFormatter $formatter, + ) {} + + /** + * {@inheritdoc} + */ + public function format(MoneyInterface $money): string + { + $amount = $money->getAmount()->toFloat(); + $currencyCode = $money->getCurrency()->getCurrencyCode(); + + return $this->formatter->formatCurrency($amount, $currencyCode); + } +} diff --git a/src/SonsOfPHP/Component/Money/Money.php b/src/SonsOfPHP/Component/Money/Money.php index b4e57beb..6ac58c35 100644 --- a/src/SonsOfPHP/Component/Money/Money.php +++ b/src/SonsOfPHP/Component/Money/Money.php @@ -19,13 +19,13 @@ use SonsOfPHP\Contract\Money\AmountInterface; use SonsOfPHP\Contract\Money\CurrencyInterface; use SonsOfPHP\Contract\Money\MoneyInterface; -use SonsOfPHP\Contract\Money\Operator\Money\MoneyOperatorInterface; -use SonsOfPHP\Contract\Money\Query\Money\MoneyQueryInterface; +use SonsOfPHP\Contract\Money\MoneyOperatorInterface; +use SonsOfPHP\Contract\Money\MoneyQueryInterface; /** * @author Joshua Estes */ -final class Money implements MoneyInterface +final class Money implements MoneyInterface, \JsonSerializable { private AmountInterface $amount; private CurrencyInterface $currency; @@ -151,4 +151,12 @@ public function divide($divisor): MoneyInterface { return $this->with(new DivideMoneyOperator($divisor)); } + + public function jsonSerialize(): array + { + return [ + 'amount' => $this->getAmount()->toInt(), + 'currency' => $this->getCurrency()->getCurrencyCode(), + ]; + } } diff --git a/src/SonsOfPHP/Component/Money/Operator/Amount/AddAmountOperator.php b/src/SonsOfPHP/Component/Money/Operator/Amount/AddAmountOperator.php index 2d5cbce2..0c987e5c 100644 --- a/src/SonsOfPHP/Component/Money/Operator/Amount/AddAmountOperator.php +++ b/src/SonsOfPHP/Component/Money/Operator/Amount/AddAmountOperator.php @@ -6,7 +6,7 @@ use SonsOfPHP\Component\Money\Amount; use SonsOfPHP\Contract\Money\AmountInterface; -use SonsOfPHP\Contract\Money\Operator\Amount\AmountOperatorInterface; +use SonsOfPHP\Contract\Money\AmountOperatorInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Operator/Amount/DivideAmountOperator.php b/src/SonsOfPHP/Component/Money/Operator/Amount/DivideAmountOperator.php index 689f59b3..0cc403be 100644 --- a/src/SonsOfPHP/Component/Money/Operator/Amount/DivideAmountOperator.php +++ b/src/SonsOfPHP/Component/Money/Operator/Amount/DivideAmountOperator.php @@ -6,7 +6,7 @@ use SonsOfPHP\Component\Money\Amount; use SonsOfPHP\Contract\Money\AmountInterface; -use SonsOfPHP\Contract\Money\Operator\Amount\AmountOperatorInterface; +use SonsOfPHP\Contract\Money\AmountOperatorInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Operator/Amount/MultiplyAmountOperator.php b/src/SonsOfPHP/Component/Money/Operator/Amount/MultiplyAmountOperator.php index 70120c86..8ac20c58 100644 --- a/src/SonsOfPHP/Component/Money/Operator/Amount/MultiplyAmountOperator.php +++ b/src/SonsOfPHP/Component/Money/Operator/Amount/MultiplyAmountOperator.php @@ -6,7 +6,7 @@ use SonsOfPHP\Component\Money\Amount; use SonsOfPHP\Contract\Money\AmountInterface; -use SonsOfPHP\Contract\Money\Operator\Amount\AmountOperatorInterface; +use SonsOfPHP\Contract\Money\AmountOperatorInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Operator/Amount/SubtractAmountOperator.php b/src/SonsOfPHP/Component/Money/Operator/Amount/SubtractAmountOperator.php index 7881fad3..4fa9dfcf 100644 --- a/src/SonsOfPHP/Component/Money/Operator/Amount/SubtractAmountOperator.php +++ b/src/SonsOfPHP/Component/Money/Operator/Amount/SubtractAmountOperator.php @@ -6,7 +6,7 @@ use SonsOfPHP\Component\Money\Amount; use SonsOfPHP\Contract\Money\AmountInterface; -use SonsOfPHP\Contract\Money\Operator\Amount\AmountOperatorInterface; +use SonsOfPHP\Contract\Money\AmountOperatorInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Operator/Money/AddMoneyOperator.php b/src/SonsOfPHP/Component/Money/Operator/Money/AddMoneyOperator.php index 9a54ccf1..9becfbcf 100644 --- a/src/SonsOfPHP/Component/Money/Operator/Money/AddMoneyOperator.php +++ b/src/SonsOfPHP/Component/Money/Operator/Money/AddMoneyOperator.php @@ -7,7 +7,7 @@ use SonsOfPHP\Component\Money\Exception\MoneyException; use SonsOfPHP\Component\Money\Money; use SonsOfPHP\Contract\Money\MoneyInterface; -use SonsOfPHP\Contract\Money\Operator\Money\MoneyOperatorInterface; +use SonsOfPHP\Contract\Money\MoneyOperatorInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Operator/Money/DivideMoneyOperator.php b/src/SonsOfPHP/Component/Money/Operator/Money/DivideMoneyOperator.php index 224760c3..eb7f98c0 100644 --- a/src/SonsOfPHP/Component/Money/Operator/Money/DivideMoneyOperator.php +++ b/src/SonsOfPHP/Component/Money/Operator/Money/DivideMoneyOperator.php @@ -6,7 +6,7 @@ use SonsOfPHP\Component\Money\Money; use SonsOfPHP\Contract\Money\MoneyInterface; -use SonsOfPHP\Contract\Money\Operator\Money\MoneyOperatorInterface; +use SonsOfPHP\Contract\Money\MoneyOperatorInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Operator/Money/MultiplyMoneyOperator.php b/src/SonsOfPHP/Component/Money/Operator/Money/MultiplyMoneyOperator.php index 2e1d0302..f5f22ebc 100644 --- a/src/SonsOfPHP/Component/Money/Operator/Money/MultiplyMoneyOperator.php +++ b/src/SonsOfPHP/Component/Money/Operator/Money/MultiplyMoneyOperator.php @@ -6,7 +6,7 @@ use SonsOfPHP\Component\Money\Money; use SonsOfPHP\Contract\Money\MoneyInterface; -use SonsOfPHP\Contract\Money\Operator\Money\MoneyOperatorInterface; +use SonsOfPHP\Contract\Money\MoneyOperatorInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Operator/Money/SubtractMoneyOperator.php b/src/SonsOfPHP/Component/Money/Operator/Money/SubtractMoneyOperator.php index 5822b388..d3402bc4 100644 --- a/src/SonsOfPHP/Component/Money/Operator/Money/SubtractMoneyOperator.php +++ b/src/SonsOfPHP/Component/Money/Operator/Money/SubtractMoneyOperator.php @@ -7,7 +7,7 @@ use SonsOfPHP\Component\Money\Exception\MoneyException; use SonsOfPHP\Component\Money\Money; use SonsOfPHP\Contract\Money\MoneyInterface; -use SonsOfPHP\Contract\Money\Operator\Money\MoneyOperatorInterface; +use SonsOfPHP\Contract\Money\MoneyOperatorInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Query/Amount/IsEqualToAmountQuery.php b/src/SonsOfPHP/Component/Money/Query/Amount/IsEqualToAmountQuery.php index f6e86581..c3b32926 100644 --- a/src/SonsOfPHP/Component/Money/Query/Amount/IsEqualToAmountQuery.php +++ b/src/SonsOfPHP/Component/Money/Query/Amount/IsEqualToAmountQuery.php @@ -5,7 +5,7 @@ namespace SonsOfPHP\Component\Money\Query\Amount; use SonsOfPHP\Contract\Money\AmountInterface; -use SonsOfPHP\Contract\Money\Query\Amount\AmountQueryInterface; +use SonsOfPHP\Contract\Money\AmountQueryInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Query/Amount/IsGreaterThanAmountQuery.php b/src/SonsOfPHP/Component/Money/Query/Amount/IsGreaterThanAmountQuery.php index 6fba3d12..65d71bd3 100644 --- a/src/SonsOfPHP/Component/Money/Query/Amount/IsGreaterThanAmountQuery.php +++ b/src/SonsOfPHP/Component/Money/Query/Amount/IsGreaterThanAmountQuery.php @@ -5,7 +5,7 @@ namespace SonsOfPHP\Component\Money\Query\Amount; use SonsOfPHP\Contract\Money\AmountInterface; -use SonsOfPHP\Contract\Money\Query\Amount\AmountQueryInterface; +use SonsOfPHP\Contract\Money\AmountQueryInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Query/Amount/IsGreaterThanOrEqualToAmountQuery.php b/src/SonsOfPHP/Component/Money/Query/Amount/IsGreaterThanOrEqualToAmountQuery.php index 15204625..7e63aa4c 100644 --- a/src/SonsOfPHP/Component/Money/Query/Amount/IsGreaterThanOrEqualToAmountQuery.php +++ b/src/SonsOfPHP/Component/Money/Query/Amount/IsGreaterThanOrEqualToAmountQuery.php @@ -5,7 +5,7 @@ namespace SonsOfPHP\Component\Money\Query\Amount; use SonsOfPHP\Contract\Money\AmountInterface; -use SonsOfPHP\Contract\Money\Query\Amount\AmountQueryInterface; +use SonsOfPHP\Contract\Money\AmountQueryInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Query/Amount/IsLessThanAmountQuery.php b/src/SonsOfPHP/Component/Money/Query/Amount/IsLessThanAmountQuery.php index dee98c25..86d98f03 100644 --- a/src/SonsOfPHP/Component/Money/Query/Amount/IsLessThanAmountQuery.php +++ b/src/SonsOfPHP/Component/Money/Query/Amount/IsLessThanAmountQuery.php @@ -5,7 +5,7 @@ namespace SonsOfPHP\Component\Money\Query\Amount; use SonsOfPHP\Contract\Money\AmountInterface; -use SonsOfPHP\Contract\Money\Query\Amount\AmountQueryInterface; +use SonsOfPHP\Contract\Money\AmountQueryInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Query/Amount/IsLessThanOrEqualToAmountQuery.php b/src/SonsOfPHP/Component/Money/Query/Amount/IsLessThanOrEqualToAmountQuery.php index c27ae797..e7bd7fa8 100644 --- a/src/SonsOfPHP/Component/Money/Query/Amount/IsLessThanOrEqualToAmountQuery.php +++ b/src/SonsOfPHP/Component/Money/Query/Amount/IsLessThanOrEqualToAmountQuery.php @@ -5,7 +5,7 @@ namespace SonsOfPHP\Component\Money\Query\Amount; use SonsOfPHP\Contract\Money\AmountInterface; -use SonsOfPHP\Contract\Money\Query\Amount\AmountQueryInterface; +use SonsOfPHP\Contract\Money\AmountQueryInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Query/Amount/IsNegativeAmountQuery.php b/src/SonsOfPHP/Component/Money/Query/Amount/IsNegativeAmountQuery.php index 82fbf59f..aba098fd 100644 --- a/src/SonsOfPHP/Component/Money/Query/Amount/IsNegativeAmountQuery.php +++ b/src/SonsOfPHP/Component/Money/Query/Amount/IsNegativeAmountQuery.php @@ -5,7 +5,7 @@ namespace SonsOfPHP\Component\Money\Query\Amount; use SonsOfPHP\Contract\Money\AmountInterface; -use SonsOfPHP\Contract\Money\Query\Amount\AmountQueryInterface; +use SonsOfPHP\Contract\Money\AmountQueryInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Query/Amount/IsPositiveAmountQuery.php b/src/SonsOfPHP/Component/Money/Query/Amount/IsPositiveAmountQuery.php index d5d16881..e707149f 100644 --- a/src/SonsOfPHP/Component/Money/Query/Amount/IsPositiveAmountQuery.php +++ b/src/SonsOfPHP/Component/Money/Query/Amount/IsPositiveAmountQuery.php @@ -5,7 +5,7 @@ namespace SonsOfPHP\Component\Money\Query\Amount; use SonsOfPHP\Contract\Money\AmountInterface; -use SonsOfPHP\Contract\Money\Query\Amount\AmountQueryInterface; +use SonsOfPHP\Contract\Money\AmountQueryInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Query/Amount/IsZeroAmountQuery.php b/src/SonsOfPHP/Component/Money/Query/Amount/IsZeroAmountQuery.php index 34baf643..9a855e03 100644 --- a/src/SonsOfPHP/Component/Money/Query/Amount/IsZeroAmountQuery.php +++ b/src/SonsOfPHP/Component/Money/Query/Amount/IsZeroAmountQuery.php @@ -5,7 +5,7 @@ namespace SonsOfPHP\Component\Money\Query\Amount; use SonsOfPHP\Contract\Money\AmountInterface; -use SonsOfPHP\Contract\Money\Query\Amount\AmountQueryInterface; +use SonsOfPHP\Contract\Money\AmountQueryInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Query/Currency/IsEqualToCurrencyQuery.php b/src/SonsOfPHP/Component/Money/Query/Currency/IsEqualToCurrencyQuery.php index 94746b7a..188982e9 100644 --- a/src/SonsOfPHP/Component/Money/Query/Currency/IsEqualToCurrencyQuery.php +++ b/src/SonsOfPHP/Component/Money/Query/Currency/IsEqualToCurrencyQuery.php @@ -5,7 +5,7 @@ namespace SonsOfPHP\Component\Money\Query\Currency; use SonsOfPHP\Contract\Money\CurrencyInterface; -use SonsOfPHP\Contract\Money\Query\Currency\CurrencyQueryInterface; +use SonsOfPHP\Contract\Money\CurrencyQueryInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Query/CurrencyProvider/GetCurrencyQuery.php b/src/SonsOfPHP/Component/Money/Query/CurrencyProvider/GetCurrencyQuery.php index 6e978295..bb6ca981 100644 --- a/src/SonsOfPHP/Component/Money/Query/CurrencyProvider/GetCurrencyQuery.php +++ b/src/SonsOfPHP/Component/Money/Query/CurrencyProvider/GetCurrencyQuery.php @@ -8,7 +8,7 @@ use SonsOfPHP\Component\Money\Exception\MoneyException; use SonsOfPHP\Contract\Money\CurrencyInterface; use SonsOfPHP\Contract\Money\CurrencyProviderInterface; -use SonsOfPHP\Contract\Money\Query\CurrencyProvider\CurrencyProviderQueryInterface; +use SonsOfPHP\Contract\Money\CurrencyProviderQueryInterface; /** * @author Joshua Estes @@ -17,7 +17,7 @@ class GetCurrencyQuery implements CurrencyProviderQueryInterface { private CurrencyInterface $currency; - public function __construct($currency) + public function __construct(CurrencyInterface|string $currency) { if ($currency instanceof CurrencyInterface) { $this->currency = $currency; diff --git a/src/SonsOfPHP/Component/Money/Query/CurrencyProvider/HasCurrencyQuery.php b/src/SonsOfPHP/Component/Money/Query/CurrencyProvider/HasCurrencyQuery.php index 1e4fbb1a..3f2b9669 100644 --- a/src/SonsOfPHP/Component/Money/Query/CurrencyProvider/HasCurrencyQuery.php +++ b/src/SonsOfPHP/Component/Money/Query/CurrencyProvider/HasCurrencyQuery.php @@ -8,7 +8,7 @@ use SonsOfPHP\Component\Money\Exception\MoneyException; use SonsOfPHP\Contract\Money\CurrencyInterface; use SonsOfPHP\Contract\Money\CurrencyProviderInterface; -use SonsOfPHP\Contract\Money\Query\CurrencyProvider\CurrencyProviderQueryInterface; +use SonsOfPHP\Contract\Money\CurrencyProviderQueryInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Query/Money/IsEqualToMoneyQuery.php b/src/SonsOfPHP/Component/Money/Query/Money/IsEqualToMoneyQuery.php index cd8334eb..cdb09659 100644 --- a/src/SonsOfPHP/Component/Money/Query/Money/IsEqualToMoneyQuery.php +++ b/src/SonsOfPHP/Component/Money/Query/Money/IsEqualToMoneyQuery.php @@ -5,7 +5,7 @@ namespace SonsOfPHP\Component\Money\Query\Money; use SonsOfPHP\Contract\Money\MoneyInterface; -use SonsOfPHP\Contract\Money\Query\Money\MoneyQueryInterface; +use SonsOfPHP\Contract\Money\MoneyQueryInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Query/Money/IsGreaterThanMoneyQuery.php b/src/SonsOfPHP/Component/Money/Query/Money/IsGreaterThanMoneyQuery.php index dd8ac01e..c63bde93 100644 --- a/src/SonsOfPHP/Component/Money/Query/Money/IsGreaterThanMoneyQuery.php +++ b/src/SonsOfPHP/Component/Money/Query/Money/IsGreaterThanMoneyQuery.php @@ -6,7 +6,7 @@ use SonsOfPHP\Component\Money\Exception\MoneyException; use SonsOfPHP\Contract\Money\MoneyInterface; -use SonsOfPHP\Contract\Money\Query\Money\MoneyQueryInterface; +use SonsOfPHP\Contract\Money\MoneyQueryInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Query/Money/IsGreaterThanOrEqualToMoneyQuery.php b/src/SonsOfPHP/Component/Money/Query/Money/IsGreaterThanOrEqualToMoneyQuery.php index 76ea907d..a474db26 100644 --- a/src/SonsOfPHP/Component/Money/Query/Money/IsGreaterThanOrEqualToMoneyQuery.php +++ b/src/SonsOfPHP/Component/Money/Query/Money/IsGreaterThanOrEqualToMoneyQuery.php @@ -6,7 +6,7 @@ use SonsOfPHP\Component\Money\Exception\MoneyException; use SonsOfPHP\Contract\Money\MoneyInterface; -use SonsOfPHP\Contract\Money\Query\Money\MoneyQueryInterface; +use SonsOfPHP\Contract\Money\MoneyQueryInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Query/Money/IsLessThanMoneyQuery.php b/src/SonsOfPHP/Component/Money/Query/Money/IsLessThanMoneyQuery.php index a79fc4ed..2b24df34 100644 --- a/src/SonsOfPHP/Component/Money/Query/Money/IsLessThanMoneyQuery.php +++ b/src/SonsOfPHP/Component/Money/Query/Money/IsLessThanMoneyQuery.php @@ -6,7 +6,7 @@ use SonsOfPHP\Component\Money\Exception\MoneyException; use SonsOfPHP\Contract\Money\MoneyInterface; -use SonsOfPHP\Contract\Money\Query\Money\MoneyQueryInterface; +use SonsOfPHP\Contract\Money\MoneyQueryInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Query/Money/IsLessThanOrEqualToMoneyQuery.php b/src/SonsOfPHP/Component/Money/Query/Money/IsLessThanOrEqualToMoneyQuery.php index 38f88b9b..82880b88 100644 --- a/src/SonsOfPHP/Component/Money/Query/Money/IsLessThanOrEqualToMoneyQuery.php +++ b/src/SonsOfPHP/Component/Money/Query/Money/IsLessThanOrEqualToMoneyQuery.php @@ -6,7 +6,7 @@ use SonsOfPHP\Component\Money\Exception\MoneyException; use SonsOfPHP\Contract\Money\MoneyInterface; -use SonsOfPHP\Contract\Money\Query\Money\MoneyQueryInterface; +use SonsOfPHP\Contract\Money\MoneyQueryInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Query/Money/IsNegativeMoneyQuery.php b/src/SonsOfPHP/Component/Money/Query/Money/IsNegativeMoneyQuery.php index 441e4602..ba29a77f 100644 --- a/src/SonsOfPHP/Component/Money/Query/Money/IsNegativeMoneyQuery.php +++ b/src/SonsOfPHP/Component/Money/Query/Money/IsNegativeMoneyQuery.php @@ -5,7 +5,7 @@ namespace SonsOfPHP\Component\Money\Query\Money; use SonsOfPHP\Contract\Money\MoneyInterface; -use SonsOfPHP\Contract\Money\Query\Money\MoneyQueryInterface; +use SonsOfPHP\Contract\Money\MoneyQueryInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Query/Money/IsPositiveMoneyQuery.php b/src/SonsOfPHP/Component/Money/Query/Money/IsPositiveMoneyQuery.php index b1597f44..3a1fb918 100644 --- a/src/SonsOfPHP/Component/Money/Query/Money/IsPositiveMoneyQuery.php +++ b/src/SonsOfPHP/Component/Money/Query/Money/IsPositiveMoneyQuery.php @@ -5,7 +5,7 @@ namespace SonsOfPHP\Component\Money\Query\Money; use SonsOfPHP\Contract\Money\MoneyInterface; -use SonsOfPHP\Contract\Money\Query\Money\MoneyQueryInterface; +use SonsOfPHP\Contract\Money\MoneyQueryInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Query/Money/IsZeroMoneyQuery.php b/src/SonsOfPHP/Component/Money/Query/Money/IsZeroMoneyQuery.php index 9d7f29d6..b43ffba1 100644 --- a/src/SonsOfPHP/Component/Money/Query/Money/IsZeroMoneyQuery.php +++ b/src/SonsOfPHP/Component/Money/Query/Money/IsZeroMoneyQuery.php @@ -5,7 +5,7 @@ namespace SonsOfPHP\Component\Money\Query\Money; use SonsOfPHP\Contract\Money\MoneyInterface; -use SonsOfPHP\Contract\Money\Query\Money\MoneyQueryInterface; +use SonsOfPHP\Contract\Money\MoneyQueryInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Component/Money/Tests/AmountTest.php b/src/SonsOfPHP/Component/Money/Tests/AmountTest.php index 700bf28c..07bb9a43 100644 --- a/src/SonsOfPHP/Component/Money/Tests/AmountTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/AmountTest.php @@ -27,6 +27,22 @@ */ final class AmountTest extends TestCase { + public static function validAmountProvider(): iterable + { + yield [420]; + yield ['420']; + yield [-420]; + yield ['-420']; + } + + public static function invalidAmountProvider(): iterable + { + yield [4.20]; + yield ['4.20']; + yield [-4.20]; + yield ['-4.20']; + } + /** * @covers ::__construct */ diff --git a/src/SonsOfPHP/Component/Money/Tests/CurrencyTest.php b/src/SonsOfPHP/Component/Money/Tests/CurrencyTest.php index 299e6ccc..764d13a3 100644 --- a/src/SonsOfPHP/Component/Money/Tests/CurrencyTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/CurrencyTest.php @@ -16,6 +16,16 @@ */ final class CurrencyTest extends TestCase { + /** + * @covers ::__construct + */ + public function testContructWillValidateCurrencyCode(): void + { + $currency = new Currency('usd'); + + $this->assertSame('USD', $currency->getCurrencyCode()); + } + /** * @covers ::__callStatic * @covers ::__construct diff --git a/src/SonsOfPHP/Component/Money/Tests/Formatter/IntlMoneyFormatterTest.php b/src/SonsOfPHP/Component/Money/Tests/Formatter/IntlMoneyFormatterTest.php new file mode 100644 index 00000000..232d19e9 --- /dev/null +++ b/src/SonsOfPHP/Component/Money/Tests/Formatter/IntlMoneyFormatterTest.php @@ -0,0 +1,43 @@ +assertInstanceOf(MoneyFormatterInterface::class, $formatter); + } + + /** + * @covers ::format + */ + public function testFormat(): void + { + $formatter = new IntlMoneyFormatter(new \NumberFormatter('en_US', \NumberFormatter::CURRENCY)); + + $this->assertSame('$4.20', $formatter->format(Money::USD(4.20))); + } +} diff --git a/src/SonsOfPHP/Component/Money/Tests/MoneyTest.php b/src/SonsOfPHP/Component/Money/Tests/MoneyTest.php index 8f8ab3ad..2ed99691 100644 --- a/src/SonsOfPHP/Component/Money/Tests/MoneyTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/MoneyTest.php @@ -44,6 +44,14 @@ */ final class MoneyTest extends TestCase { + public static function validMoneyConstructorArgumentsProvider(): iterable + { + yield [420, 'usd']; + yield [420, new Currency('usd')]; + yield [-420, 'usd']; + yield [-420, new Currency('usd')]; + } + /** * @covers ::__callStatic * @covers ::__construct @@ -363,4 +371,14 @@ public function testDivide(): void $output = $money1->divide(5); $this->assertSame('20', (string) $output->getAmount()); } + + /** + * @covers ::jsonSerialize + */ + public function testJsonSerialize(): void + { + $money = Money::USD(420); + + $this->assertSame('{"amount":420,"currency":"USD"}', json_encode($money)); + } } diff --git a/src/SonsOfPHP/Component/Money/Tests/Operator/Amount/AddAmountOperatorTest.php b/src/SonsOfPHP/Component/Money/Tests/Operator/Amount/AddAmountOperatorTest.php index b593eeed..a09f3d86 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Operator/Amount/AddAmountOperatorTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Operator/Amount/AddAmountOperatorTest.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase; use SonsOfPHP\Component\Money\Amount; use SonsOfPHP\Component\Money\Operator\Amount\AddAmountOperator; -use SonsOfPHP\Contract\Money\Operator\Amount\AmountOperatorInterface; +use SonsOfPHP\Contract\Money\AmountOperatorInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Operator\Amount\AddAmountOperator diff --git a/src/SonsOfPHP/Component/Money/Tests/Operator/Amount/DivideAmountOperatorTest.php b/src/SonsOfPHP/Component/Money/Tests/Operator/Amount/DivideAmountOperatorTest.php index e143641e..fcd6afb2 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Operator/Amount/DivideAmountOperatorTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Operator/Amount/DivideAmountOperatorTest.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase; use SonsOfPHP\Component\Money\Amount; use SonsOfPHP\Component\Money\Operator\Amount\DivideAmountOperator; -use SonsOfPHP\Contract\Money\Operator\Amount\AmountOperatorInterface; +use SonsOfPHP\Contract\Money\AmountOperatorInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Operator\Amount\DivideAmountOperator diff --git a/src/SonsOfPHP/Component/Money/Tests/Operator/Amount/MultiplyAmountOperatorTest.php b/src/SonsOfPHP/Component/Money/Tests/Operator/Amount/MultiplyAmountOperatorTest.php index 21844e66..6b8ee735 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Operator/Amount/MultiplyAmountOperatorTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Operator/Amount/MultiplyAmountOperatorTest.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase; use SonsOfPHP\Component\Money\Amount; use SonsOfPHP\Component\Money\Operator\Amount\MultiplyAmountOperator; -use SonsOfPHP\Contract\Money\Operator\Amount\AmountOperatorInterface; +use SonsOfPHP\Contract\Money\AmountOperatorInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Operator\Amount\MultiplyAmountOperator diff --git a/src/SonsOfPHP/Component/Money/Tests/Operator/Amount/SubtractAmountOperatorTest.php b/src/SonsOfPHP/Component/Money/Tests/Operator/Amount/SubtractAmountOperatorTest.php index 0f5cc314..80e08c18 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Operator/Amount/SubtractAmountOperatorTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Operator/Amount/SubtractAmountOperatorTest.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase; use SonsOfPHP\Component\Money\Amount; use SonsOfPHP\Component\Money\Operator\Amount\SubtractAmountOperator; -use SonsOfPHP\Contract\Money\Operator\Amount\AmountOperatorInterface; +use SonsOfPHP\Contract\Money\AmountOperatorInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Operator\Amount\SubtractAmountOperator diff --git a/src/SonsOfPHP/Component/Money/Tests/Operator/Money/AddMoneyOperatorTest.php b/src/SonsOfPHP/Component/Money/Tests/Operator/Money/AddMoneyOperatorTest.php index e3f29f50..22bf8ade 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Operator/Money/AddMoneyOperatorTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Operator/Money/AddMoneyOperatorTest.php @@ -9,7 +9,7 @@ use SonsOfPHP\Component\Money\Exception\MoneyException; use SonsOfPHP\Component\Money\Money; use SonsOfPHP\Component\Money\Operator\Money\AddMoneyOperator; -use SonsOfPHP\Contract\Money\Operator\Money\MoneyOperatorInterface; +use SonsOfPHP\Contract\Money\MoneyOperatorInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Operator\Money\AddMoneyOperator diff --git a/src/SonsOfPHP/Component/Money/Tests/Operator/Money/DivideMoneyOperatorTest.php b/src/SonsOfPHP/Component/Money/Tests/Operator/Money/DivideMoneyOperatorTest.php index 7948d979..66dbf5a7 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Operator/Money/DivideMoneyOperatorTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Operator/Money/DivideMoneyOperatorTest.php @@ -8,7 +8,7 @@ use SonsOfPHP\Component\Money\Currency; use SonsOfPHP\Component\Money\Money; use SonsOfPHP\Component\Money\Operator\Money\DivideMoneyOperator; -use SonsOfPHP\Contract\Money\Operator\Money\MoneyOperatorInterface; +use SonsOfPHP\Contract\Money\MoneyOperatorInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Operator\Money\DivideMoneyOperator diff --git a/src/SonsOfPHP/Component/Money/Tests/Operator/Money/MultiplyMoneyOperatorTest.php b/src/SonsOfPHP/Component/Money/Tests/Operator/Money/MultiplyMoneyOperatorTest.php index 4ca68b93..0c443f1e 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Operator/Money/MultiplyMoneyOperatorTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Operator/Money/MultiplyMoneyOperatorTest.php @@ -8,7 +8,7 @@ use SonsOfPHP\Component\Money\Currency; use SonsOfPHP\Component\Money\Money; use SonsOfPHP\Component\Money\Operator\Money\MultiplyMoneyOperator; -use SonsOfPHP\Contract\Money\Operator\Money\MoneyOperatorInterface; +use SonsOfPHP\Contract\Money\MoneyOperatorInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Operator\Money\MultiplyMoneyOperator diff --git a/src/SonsOfPHP/Component/Money/Tests/Operator/Money/SubtractMoneyOperatorTest.php b/src/SonsOfPHP/Component/Money/Tests/Operator/Money/SubtractMoneyOperatorTest.php index 5bbc6b9a..7e161ae3 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Operator/Money/SubtractMoneyOperatorTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Operator/Money/SubtractMoneyOperatorTest.php @@ -9,7 +9,7 @@ use SonsOfPHP\Component\Money\Exception\MoneyException; use SonsOfPHP\Component\Money\Money; use SonsOfPHP\Component\Money\Operator\Money\SubtractMoneyOperator; -use SonsOfPHP\Contract\Money\Operator\Money\MoneyOperatorInterface; +use SonsOfPHP\Contract\Money\MoneyOperatorInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Operator\Money\SubtractMoneyOperator diff --git a/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsEqualToAmountQueryTest.php b/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsEqualToAmountQueryTest.php index 669e8a85..efbe16e8 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsEqualToAmountQueryTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsEqualToAmountQueryTest.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase; use SonsOfPHP\Component\Money\Amount; use SonsOfPHP\Component\Money\Query\Amount\IsEqualToAmountQuery; -use SonsOfPHP\Contract\Money\Query\Amount\AmountQueryInterface; +use SonsOfPHP\Contract\Money\AmountQueryInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Query\Amount\IsEqualToAmountQuery diff --git a/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsGreaterThanAmountQueryTest.php b/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsGreaterThanAmountQueryTest.php index fc17a297..2b9f85a2 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsGreaterThanAmountQueryTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsGreaterThanAmountQueryTest.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase; use SonsOfPHP\Component\Money\Amount; use SonsOfPHP\Component\Money\Query\Amount\IsGreaterThanAmountQuery; -use SonsOfPHP\Contract\Money\Query\Amount\AmountQueryInterface; +use SonsOfPHP\Contract\Money\AmountQueryInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Query\Amount\IsGreaterThanAmountQuery diff --git a/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsGreaterThanOrEqualToAmountQueryTest.php b/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsGreaterThanOrEqualToAmountQueryTest.php index c975fe43..2157df5a 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsGreaterThanOrEqualToAmountQueryTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsGreaterThanOrEqualToAmountQueryTest.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase; use SonsOfPHP\Component\Money\Amount; use SonsOfPHP\Component\Money\Query\Amount\IsGreaterThanOrEqualToAmountQuery; -use SonsOfPHP\Contract\Money\Query\Amount\AmountQueryInterface; +use SonsOfPHP\Contract\Money\AmountQueryInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Query\Amount\IsGreaterThanOrEqualToAmountQuery diff --git a/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsLessThanAmountQueryTest.php b/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsLessThanAmountQueryTest.php index 1318ed67..f17b64f2 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsLessThanAmountQueryTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsLessThanAmountQueryTest.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase; use SonsOfPHP\Component\Money\Amount; use SonsOfPHP\Component\Money\Query\Amount\IsLessThanAmountQuery; -use SonsOfPHP\Contract\Money\Query\Amount\AmountQueryInterface; +use SonsOfPHP\Contract\Money\AmountQueryInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Query\Amount\IsLessThanAmountQuery diff --git a/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsLessThanOrEqualToAmountQueryTest.php b/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsLessThanOrEqualToAmountQueryTest.php index 6f422fc4..2fb87f97 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsLessThanOrEqualToAmountQueryTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsLessThanOrEqualToAmountQueryTest.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase; use SonsOfPHP\Component\Money\Amount; use SonsOfPHP\Component\Money\Query\Amount\IsLessThanOrEqualToAmountQuery; -use SonsOfPHP\Contract\Money\Query\Amount\AmountQueryInterface; +use SonsOfPHP\Contract\Money\AmountQueryInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Query\Amount\IsLessThanOrEqualToAmountQuery diff --git a/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsNegativeAmountQueryTest.php b/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsNegativeAmountQueryTest.php index f5ed3247..3076b82d 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsNegativeAmountQueryTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsNegativeAmountQueryTest.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase; use SonsOfPHP\Component\Money\Amount; use SonsOfPHP\Component\Money\Query\Amount\IsNegativeAmountQuery; -use SonsOfPHP\Contract\Money\Query\Amount\AmountQueryInterface; +use SonsOfPHP\Contract\Money\AmountQueryInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Query\Amount\IsNegativeAmountQuery diff --git a/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsPositiveAmountQueryTest.php b/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsPositiveAmountQueryTest.php index 28944e8e..dd8a6050 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsPositiveAmountQueryTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsPositiveAmountQueryTest.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase; use SonsOfPHP\Component\Money\Amount; use SonsOfPHP\Component\Money\Query\Amount\IsPositiveAmountQuery; -use SonsOfPHP\Contract\Money\Query\Amount\AmountQueryInterface; +use SonsOfPHP\Contract\Money\AmountQueryInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Query\Amount\IsPositiveAmountQuery diff --git a/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsZeroAmountQueryTest.php b/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsZeroAmountQueryTest.php index 87e66965..1f3946c3 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsZeroAmountQueryTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Query/Amount/IsZeroAmountQueryTest.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase; use SonsOfPHP\Component\Money\Amount; use SonsOfPHP\Component\Money\Query\Amount\IsZeroAmountQuery; -use SonsOfPHP\Contract\Money\Query\Amount\AmountQueryInterface; +use SonsOfPHP\Contract\Money\AmountQueryInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Query\Amount\IsZeroAmountQuery diff --git a/src/SonsOfPHP/Component/Money/Tests/Query/Currency/IsEqualToCurrencyQueryTest.php b/src/SonsOfPHP/Component/Money/Tests/Query/Currency/IsEqualToCurrencyQueryTest.php index 055424b6..78dff0f6 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Query/Currency/IsEqualToCurrencyQueryTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Query/Currency/IsEqualToCurrencyQueryTest.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase; use SonsOfPHP\Component\Money\Currency; use SonsOfPHP\Component\Money\Query\Currency\IsEqualToCurrencyQuery; -use SonsOfPHP\Contract\Money\Query\Currency\CurrencyQueryInterface; +use SonsOfPHP\Contract\Money\CurrencyQueryInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Query\Currency\IsEqualToCurrencyQuery diff --git a/src/SonsOfPHP/Component/Money/Tests/Query/CurrencyProvider/GetCurrencyQueryTest.php b/src/SonsOfPHP/Component/Money/Tests/Query/CurrencyProvider/GetCurrencyQueryTest.php index 31ca2e67..a3fae2cc 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Query/CurrencyProvider/GetCurrencyQueryTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Query/CurrencyProvider/GetCurrencyQueryTest.php @@ -2,14 +2,14 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\Money\Tests\Query\Currency; +namespace SonsOfPHP\Component\Money\Tests\Query\CurrencyProvider; use PHPUnit\Framework\TestCase; use SonsOfPHP\Component\Money\Currency; use SonsOfPHP\Component\Money\CurrencyProvider\XCurrencyProvider; use SonsOfPHP\Component\Money\Query\CurrencyProvider\GetCurrencyQuery; +use SonsOfPHP\Contract\Money\CurrencyProviderQueryInterface; use SonsOfPHP\Contract\Money\Exception\MoneyExceptionInterface; -use SonsOfPHP\Contract\Money\Query\CurrencyProvider\CurrencyProviderQueryInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Query\CurrencyProvider\GetCurrencyQuery diff --git a/src/SonsOfPHP/Component/Money/Tests/Query/CurrencyProvider/HasCurrencyQueryTest.php b/src/SonsOfPHP/Component/Money/Tests/Query/CurrencyProvider/HasCurrencyQueryTest.php index 9c2681d2..ce889b40 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Query/CurrencyProvider/HasCurrencyQueryTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Query/CurrencyProvider/HasCurrencyQueryTest.php @@ -2,14 +2,14 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\Money\Tests\Query\Currency; +namespace SonsOfPHP\Component\Money\Tests\Query\CurrencyProvider; use PHPUnit\Framework\TestCase; use SonsOfPHP\Component\Money\Currency; use SonsOfPHP\Component\Money\CurrencyProvider\XCurrencyProvider; use SonsOfPHP\Component\Money\Query\CurrencyProvider\HasCurrencyQuery; +use SonsOfPHP\Contract\Money\CurrencyProviderQueryInterface; use SonsOfPHP\Contract\Money\Exception\MoneyExceptionInterface; -use SonsOfPHP\Contract\Money\Query\CurrencyProvider\CurrencyProviderQueryInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Query\CurrencyProvider\HasCurrencyQuery diff --git a/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsEqualToMoneyQueryTest.php b/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsEqualToMoneyQueryTest.php index b1ca02df..27adb027 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsEqualToMoneyQueryTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsEqualToMoneyQueryTest.php @@ -8,7 +8,7 @@ use SonsOfPHP\Component\Money\Currency; use SonsOfPHP\Component\Money\Money; use SonsOfPHP\Component\Money\Query\Money\IsEqualToMoneyQuery; -use SonsOfPHP\Contract\Money\Query\Money\MoneyQueryInterface; +use SonsOfPHP\Contract\Money\MoneyQueryInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Query\Money\IsEqualToMoneyQuery diff --git a/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsGreaterThanMoneyQueryTest.php b/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsGreaterThanMoneyQueryTest.php index 4df2f9de..a13d2aec 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsGreaterThanMoneyQueryTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsGreaterThanMoneyQueryTest.php @@ -8,7 +8,8 @@ use SonsOfPHP\Component\Money\Currency; use SonsOfPHP\Component\Money\Money; use SonsOfPHP\Component\Money\Query\Money\IsGreaterThanMoneyQuery; -use SonsOfPHP\Contract\Money\Query\Money\MoneyQueryInterface; +use SonsOfPHP\Contract\Money\Exception\MoneyExceptionInterface; +use SonsOfPHP\Contract\Money\MoneyQueryInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Query\Money\IsGreaterThanMoneyQuery @@ -42,4 +43,15 @@ public function testQuery(): void $this->assertTrue($query->queryFrom(new Money(200, Currency::USD()))); } + + /** + * @covers ::queryFrom + */ + public function testQueryThrowsExceptionWhenCurrencyIsDifferent(): void + { + $query = new IsGreaterThanMoneyQuery(new Money(100, Currency::USD())); + + $this->expectException(MoneyExceptionInterface::class); + $query->queryFrom(new Money(200, Currency::EUR())); + } } diff --git a/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsGreaterThanOrEqualToMoneyQueryTest.php b/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsGreaterThanOrEqualToMoneyQueryTest.php index 674fdaaf..592f0dff 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsGreaterThanOrEqualToMoneyQueryTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsGreaterThanOrEqualToMoneyQueryTest.php @@ -8,7 +8,8 @@ use SonsOfPHP\Component\Money\Currency; use SonsOfPHP\Component\Money\Money; use SonsOfPHP\Component\Money\Query\Money\IsGreaterThanOrEqualToMoneyQuery; -use SonsOfPHP\Contract\Money\Query\Money\MoneyQueryInterface; +use SonsOfPHP\Contract\Money\Exception\MoneyExceptionInterface; +use SonsOfPHP\Contract\Money\MoneyQueryInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Query\Money\IsGreaterThanOrEqualToMoneyQuery @@ -42,4 +43,15 @@ public function testQuery(): void $this->assertTrue($query->queryFrom(new Money(2000, Currency::USD()))); } + + /** + * @covers ::queryFrom + */ + public function testQueryFromThrowsExceptionWhenCurrencyIsDifferent(): void + { + $query = new IsGreaterThanOrEqualToMoneyQuery(new Money(100, Currency::USD())); + + $this->expectException(MoneyExceptionInterface::class); + $query->queryFrom(new Money(200, Currency::EUR())); + } } diff --git a/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsLessThanMoneyQueryTest.php b/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsLessThanMoneyQueryTest.php index 196d6fec..40ea0b1c 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsLessThanMoneyQueryTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsLessThanMoneyQueryTest.php @@ -8,7 +8,8 @@ use SonsOfPHP\Component\Money\Currency; use SonsOfPHP\Component\Money\Money; use SonsOfPHP\Component\Money\Query\Money\IsLessThanMoneyQuery; -use SonsOfPHP\Contract\Money\Query\Money\MoneyQueryInterface; +use SonsOfPHP\Contract\Money\Exception\MoneyExceptionInterface; +use SonsOfPHP\Contract\Money\MoneyQueryInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Query\Money\IsLessThanMoneyQuery @@ -42,4 +43,15 @@ public function testQuery(): void $this->assertTrue($query->queryFrom(new Money(50, Currency::USD()))); } + + /** + * @covers ::queryFrom + */ + public function testQueryFromThrowsExceptionWhenCurrencyIsDifferent(): void + { + $query = new IsLessThanMoneyQuery(new Money(100, Currency::USD())); + + $this->expectException(MoneyExceptionInterface::class); + $query->queryFrom(new Money(200, Currency::EUR())); + } } diff --git a/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsLessThanOrEqualToMoneyQueryTest.php b/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsLessThanOrEqualToMoneyQueryTest.php index 59f3ad0e..10cf66b7 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsLessThanOrEqualToMoneyQueryTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsLessThanOrEqualToMoneyQueryTest.php @@ -8,7 +8,8 @@ use SonsOfPHP\Component\Money\Currency; use SonsOfPHP\Component\Money\Money; use SonsOfPHP\Component\Money\Query\Money\IsLessThanOrEqualToMoneyQuery; -use SonsOfPHP\Contract\Money\Query\Money\MoneyQueryInterface; +use SonsOfPHP\Contract\Money\Exception\MoneyExceptionInterface; +use SonsOfPHP\Contract\Money\MoneyQueryInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Query\Money\IsLessThanOrEqualToMoneyQuery @@ -42,4 +43,15 @@ public function testQuery(): void $this->assertTrue($query->queryFrom(new Money(10, Currency::USD()))); } + + /** + * @covers ::queryFrom + */ + public function testQueryFromThrowsExceptionWhenCurrencyIsDifferent(): void + { + $query = new IsLessThanOrEqualToMoneyQuery(new Money(100, Currency::USD())); + + $this->expectException(MoneyExceptionInterface::class); + $query->queryFrom(new Money(200, Currency::EUR())); + } } diff --git a/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsNegativeMoneyQueryTest.php b/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsNegativeMoneyQueryTest.php index d2b6fbda..cfd9e990 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsNegativeMoneyQueryTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsNegativeMoneyQueryTest.php @@ -8,7 +8,7 @@ use SonsOfPHP\Component\Money\Currency; use SonsOfPHP\Component\Money\Money; use SonsOfPHP\Component\Money\Query\Money\IsNegativeMoneyQuery; -use SonsOfPHP\Contract\Money\Query\Money\MoneyQueryInterface; +use SonsOfPHP\Contract\Money\MoneyQueryInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Query\Money\IsNegativeMoneyQuery diff --git a/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsPositiveMoneyQueryTest.php b/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsPositiveMoneyQueryTest.php index 8f9ee2e0..335d2a89 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsPositiveMoneyQueryTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsPositiveMoneyQueryTest.php @@ -8,7 +8,7 @@ use SonsOfPHP\Component\Money\Currency; use SonsOfPHP\Component\Money\Money; use SonsOfPHP\Component\Money\Query\Money\IsPositiveMoneyQuery; -use SonsOfPHP\Contract\Money\Query\Money\MoneyQueryInterface; +use SonsOfPHP\Contract\Money\MoneyQueryInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Query\Money\IsPositiveMoneyQuery diff --git a/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsZeroMoneyQueryTest.php b/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsZeroMoneyQueryTest.php index cd7f8991..ee85b349 100644 --- a/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsZeroMoneyQueryTest.php +++ b/src/SonsOfPHP/Component/Money/Tests/Query/Money/IsZeroMoneyQueryTest.php @@ -8,7 +8,7 @@ use SonsOfPHP\Component\Money\Currency; use SonsOfPHP\Component\Money\Money; use SonsOfPHP\Component\Money\Query\Money\IsZeroMoneyQuery; -use SonsOfPHP\Contract\Money\Query\Money\MoneyQueryInterface; +use SonsOfPHP\Contract\Money\MoneyQueryInterface; /** * @coversDefaultClass \SonsOfPHP\Component\Money\Query\Money\IsZeroMoneyQuery diff --git a/src/SonsOfPHP/Component/Money/composer.json b/src/SonsOfPHP/Component/Money/composer.json index 8e8e44d9..22fe072c 100644 --- a/src/SonsOfPHP/Component/Money/composer.json +++ b/src/SonsOfPHP/Component/Money/composer.json @@ -33,6 +33,7 @@ "prefer-stable": true, "require": { "php": ">=8.1", + "ext-intl": "*", "sonsofphp/money-contract": "0.3.x-dev" }, "provide": { diff --git a/src/SonsOfPHP/Component/Pager/Pager.php b/src/SonsOfPHP/Component/Pager/Pager.php index 1c40f569..5612fa16 100644 --- a/src/SonsOfPHP/Component/Pager/Pager.php +++ b/src/SonsOfPHP/Component/Pager/Pager.php @@ -35,6 +35,9 @@ public function __construct( } } + /** + * {@inheritdoc} + */ public function getCurrentPageResults(): iterable { if (null === $this->results) { @@ -49,6 +52,9 @@ public function getCurrentPageResults(): iterable return $this->results; } + /** + * {@inheritdoc} + */ public function getTotalResults(): int { if (null === $this->count) { @@ -58,6 +64,9 @@ public function getTotalResults(): int return $this->count; } + /** + * {@inheritdoc} + */ public function getTotalPages(): int { if (null === $this->getMaxPerPage() || 0 === $this->getTotalResults()) { @@ -67,6 +76,9 @@ public function getTotalPages(): int return (int) ceil($this->getTotalResults() / $this->getMaxPerPage()); } + /** + * {@inheritdoc} + */ public function haveToPaginate(): bool { if (null === $this->getMaxPerPage()) { @@ -76,11 +88,17 @@ public function haveToPaginate(): bool return $this->getTotalResults() > $this->getMaxPerPage(); } + /** + * {@inheritdoc} + */ public function hasPreviousPage(): bool { return $this->getCurrentPage() > 1; } + /** + * {@inheritdoc} + */ public function getPreviousPage(): ?int { if ($this->hasPreviousPage()) { @@ -90,11 +108,17 @@ public function getPreviousPage(): ?int return null; } + /** + * {@inheritdoc} + */ public function hasNextPage(): bool { return $this->getCurrentPage() < $this->getTotalPages(); } + /** + * {@inheritdoc} + */ public function getNextPage(): ?int { if ($this->hasNextPage()) { @@ -104,11 +128,17 @@ public function getNextPage(): ?int return null; } + /** + * {@inheritdoc} + */ public function getCurrentPage(): int { return $this->currentPage; } + /** + * {@inheritdoc} + */ public function setCurrentPage(int $page): void { if (1 > $page) { @@ -119,11 +149,17 @@ public function setCurrentPage(int $page): void $this->results = null; } + /** + * {@inheritdoc} + */ public function getMaxPerPage(): ?int { return $this->maxPerPage; } + /** + * {@inheritdoc} + */ public function setMaxPerPage(?int $maxPerPage): void { if (is_int($maxPerPage) && 1 > $maxPerPage) { 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/Common/ArrayableInterface.php b/src/SonsOfPHP/Contract/Common/ArrayableInterface.php index 1771476a..823452a3 100644 --- a/src/SonsOfPHP/Contract/Common/ArrayableInterface.php +++ b/src/SonsOfPHP/Contract/Common/ArrayableInterface.php @@ -9,7 +9,5 @@ */ interface ArrayableInterface { - /** - */ public function toArray(): array; } diff --git a/src/SonsOfPHP/Contract/Common/TryableInterface.php b/src/SonsOfPHP/Contract/Common/TryableInterface.php new file mode 100644 index 00000000..e74e2f14 --- /dev/null +++ b/src/SonsOfPHP/Contract/Common/TryableInterface.php @@ -0,0 +1,24 @@ + + */ +interface TryableInterface +{ + /** + * Pass in $data and the object is built. + * + * @throws \InvalidArgumentException + * If $data is invalid + */ + public static function from(mixed $data): static; + + /** + * Pass in data and if the data is invalid it will return null + */ + public function tryFrom(mixed $data): ?static; +} 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..6aed6a6c --- /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" + }, + "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" + } + ] +} \ No newline at end of file diff --git a/src/SonsOfPHP/Contract/HttpHandler/.gitattributes b/src/SonsOfPHP/Contract/HttpHandler/.gitattributes new file mode 100644 index 00000000..3a01b372 --- /dev/null +++ b/src/SonsOfPHP/Contract/HttpHandler/.gitattributes @@ -0,0 +1,2 @@ +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/SonsOfPHP/Contract/HttpHandler/.gitignore b/src/SonsOfPHP/Contract/HttpHandler/.gitignore new file mode 100644 index 00000000..d8a7996a --- /dev/null +++ b/src/SonsOfPHP/Contract/HttpHandler/.gitignore @@ -0,0 +1,2 @@ +composer.lock +vendor/ diff --git a/src/SonsOfPHP/Contract/HttpHandler/HttpHandlerExceptionInterface.php b/src/SonsOfPHP/Contract/HttpHandler/HttpHandlerExceptionInterface.php new file mode 100644 index 00000000..076fbeee --- /dev/null +++ b/src/SonsOfPHP/Contract/HttpHandler/HttpHandlerExceptionInterface.php @@ -0,0 +1,10 @@ + + */ +interface HttpHandlerExceptionInterface {} diff --git a/src/SonsOfPHP/Contract/HttpHandler/LICENSE b/src/SonsOfPHP/Contract/HttpHandler/LICENSE new file mode 100644 index 00000000..39238382 --- /dev/null +++ b/src/SonsOfPHP/Contract/HttpHandler/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/HttpHandler/MiddlewareStackInterface.php b/src/SonsOfPHP/Contract/HttpHandler/MiddlewareStackInterface.php new file mode 100644 index 00000000..dda2ea9b --- /dev/null +++ b/src/SonsOfPHP/Contract/HttpHandler/MiddlewareStackInterface.php @@ -0,0 +1,21 @@ + + */ +interface MiddlewareStackInterface extends \Countable +{ + /** + * Returns the next Middleware in the stack + * + * @throws \SonsOfPHP\Contract\HttpHandler\HttpHandlerExceptionInterface + * When there is some type of error + */ + public function next(): MiddlewareInterface; +} diff --git a/src/SonsOfPHP/Contract/HttpHandler/README.md b/src/SonsOfPHP/Contract/HttpHandler/README.md new file mode 100644 index 00000000..f6f34ae3 --- /dev/null +++ b/src/SonsOfPHP/Contract/HttpHandler/README.md @@ -0,0 +1,18 @@ +Sons of PHP - HttpHandler Contract +================================== + +Expands on the PSR-15 interfaces + +## 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/http-handler/ +[issues]: https://github.com/SonsOfPHP/sonsofphp/issues?q=is%3Aopen+is%3Aissue+label%3AHttpHandler +[pull-requests]: https://github.com/SonsOfPHP/sonsofphp/pulls?q=is%3Aopen+is%3Apr+label%3AHttpHandler diff --git a/src/SonsOfPHP/Contract/HttpHandler/composer.json b/src/SonsOfPHP/Contract/HttpHandler/composer.json new file mode 100644 index 00000000..e4e91617 --- /dev/null +++ b/src/SonsOfPHP/Contract/HttpHandler/composer.json @@ -0,0 +1,54 @@ +{ + "name": "sonsofphp/http-handler-contract", + "type": "library", + "description": "", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "homepage": "https://github.com/SonsOfPHP/http-handler-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" + }, + "autoload": { + "psr-4": { + "SonsOfPHP\\Contract\\HttpHandler\\": "" + } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "php": ">=8.1", + "psr/http-server-handler": "^1.0", + "psr/http-server-middleware": "^1.0" + }, + "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" + } + ] +} \ No newline at end of file diff --git a/src/SonsOfPHP/Contract/Mailer/.gitattributes b/src/SonsOfPHP/Contract/Mailer/.gitattributes new file mode 100644 index 00000000..3a01b372 --- /dev/null +++ b/src/SonsOfPHP/Contract/Mailer/.gitattributes @@ -0,0 +1,2 @@ +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/SonsOfPHP/Contract/Mailer/.gitignore b/src/SonsOfPHP/Contract/Mailer/.gitignore new file mode 100644 index 00000000..d8a7996a --- /dev/null +++ b/src/SonsOfPHP/Contract/Mailer/.gitignore @@ -0,0 +1,2 @@ +composer.lock +vendor/ diff --git a/src/SonsOfPHP/Contract/Mailer/AddressInterface.php b/src/SonsOfPHP/Contract/Mailer/AddressInterface.php new file mode 100644 index 00000000..7717cf65 --- /dev/null +++ b/src/SonsOfPHP/Contract/Mailer/AddressInterface.php @@ -0,0 +1,45 @@ +" + * + * @author Joshua Estes + */ +interface AddressInterface extends \Stringable +{ + /** + * @throws InvalidArgumentException + * If the $address is invalid, for example if the string does not contain + * an email address + */ + public static function from(string $address): self; + + /** + * This MUST return the email address ONLY + */ + public function getEmail(): string; + + /** + * Returns a new instance of the class if $email is different, if $email is + * the same, this MUST return the same instance. + * + * @throws \InvalidArgumentException + */ + public function withEmail(string $email): static; + + /** + * Just will return null if no name was set + * + * If a name was set, this will return just the name, ie "Joshua Estes" + */ + public function getName(): ?string; + + /** + * @throws \InvalidArgumentException + */ + public function withName(?string $name): static; +} diff --git a/src/SonsOfPHP/Contract/Mailer/LICENSE b/src/SonsOfPHP/Contract/Mailer/LICENSE new file mode 100644 index 00000000..39238382 --- /dev/null +++ b/src/SonsOfPHP/Contract/Mailer/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/Mailer/MailerExceptionInterface.php b/src/SonsOfPHP/Contract/Mailer/MailerExceptionInterface.php new file mode 100644 index 00000000..f3fce5f8 --- /dev/null +++ b/src/SonsOfPHP/Contract/Mailer/MailerExceptionInterface.php @@ -0,0 +1,10 @@ + + */ +interface MailerExceptionInterface {} diff --git a/src/SonsOfPHP/Contract/Mailer/MailerInterface.php b/src/SonsOfPHP/Contract/Mailer/MailerInterface.php new file mode 100644 index 00000000..39bf4577 --- /dev/null +++ b/src/SonsOfPHP/Contract/Mailer/MailerInterface.php @@ -0,0 +1,15 @@ + + */ +interface MailerInterface +{ + public function send(MessageInterface $message); +} diff --git a/src/SonsOfPHP/Contract/Mailer/MessageInterface.php b/src/SonsOfPHP/Contract/Mailer/MessageInterface.php new file mode 100644 index 00000000..d070574a --- /dev/null +++ b/src/SonsOfPHP/Contract/Mailer/MessageInterface.php @@ -0,0 +1,54 @@ + + */ +interface MessageInterface +{ + /** + * Returns the contents of the message + * + * If there has been no body set, this will return null + */ + public function getBody(): ?string; + + /** + * This will set the body contents, if there is already a body, this will + * overwrite that content + */ + public function setBody(string $body): self; + + /** + * Returns headers in key => value format + */ + public function getHeaders(): array; + + /** + * If the header is not found or has no value, this will return null + * + * When getting a header, the header name MUST NOT be case sensitive + * + * Example: + * $from = $message->getHeader('From'); + */ + public function getHeader(string $name): ?string; + + /** + * If a header is set, this will return true + */ + public function hasHeader(string $name): bool; + + /** + * If the header already exists, this will append the new value + * + * If the header does not already exist, this will add it + * + * @throws \InvalidArgumentException + * If name or value is invalid + */ + public function addHeader(string $name, AddressInterface|string $value): self; +} diff --git a/src/SonsOfPHP/Contract/Mailer/MiddlewareHandlerInterface.php b/src/SonsOfPHP/Contract/Mailer/MiddlewareHandlerInterface.php new file mode 100644 index 00000000..e6e70109 --- /dev/null +++ b/src/SonsOfPHP/Contract/Mailer/MiddlewareHandlerInterface.php @@ -0,0 +1,13 @@ + + */ +interface MiddlewareHandlerInterface +{ + public function handle(MessageInterface $message): MessageInterface; +} diff --git a/src/SonsOfPHP/Contract/Mailer/MiddlewareInterface.php b/src/SonsOfPHP/Contract/Mailer/MiddlewareInterface.php new file mode 100644 index 00000000..3409778c --- /dev/null +++ b/src/SonsOfPHP/Contract/Mailer/MiddlewareInterface.php @@ -0,0 +1,13 @@ + + */ +interface MiddlewareInterface +{ + public function __invoke(MessageInterface $message, MiddlewareHandlerInterface $handler); +} diff --git a/src/SonsOfPHP/Contract/Mailer/MiddlewareStackInterface.php b/src/SonsOfPHP/Contract/Mailer/MiddlewareStackInterface.php new file mode 100644 index 00000000..e7ee01ea --- /dev/null +++ b/src/SonsOfPHP/Contract/Mailer/MiddlewareStackInterface.php @@ -0,0 +1,16 @@ + + */ +interface MiddlewareStackInterface +{ + /** + * @throw MailerExceptionInterface + */ + public function next(): MiddlewareInterface; +} diff --git a/src/SonsOfPHP/Contract/Mailer/README.md b/src/SonsOfPHP/Contract/Mailer/README.md new file mode 100644 index 00000000..db89e21a --- /dev/null +++ b/src/SonsOfPHP/Contract/Mailer/README.md @@ -0,0 +1,16 @@ +Sons of PHP - Mailer 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/mailer/ +[issues]: https://github.com/SonsOfPHP/sonsofphp/issues?q=is%3Aopen+is%3Aissue+label%3AMailer +[pull-requests]: https://github.com/SonsOfPHP/sonsofphp/pulls?q=is%3Aopen+is%3Apr+label%3AMailer diff --git a/src/SonsOfPHP/Contract/Mailer/TransportInterface.php b/src/SonsOfPHP/Contract/Mailer/TransportInterface.php new file mode 100644 index 00000000..b0364ca3 --- /dev/null +++ b/src/SonsOfPHP/Contract/Mailer/TransportInterface.php @@ -0,0 +1,19 @@ + + */ +interface TransportInterface +{ + /** + * @throws MailerExceptionInterface + */ + public function send(MessageInterface $message); +} diff --git a/src/SonsOfPHP/Contract/Mailer/composer.json b/src/SonsOfPHP/Contract/Mailer/composer.json new file mode 100644 index 00000000..1bff7fec --- /dev/null +++ b/src/SonsOfPHP/Contract/Mailer/composer.json @@ -0,0 +1,52 @@ +{ + "name": "sonsofphp/mailer-contract", + "type": "library", + "description": "", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "homepage": "https://github.com/SonsOfPHP/mailer-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" + }, + "autoload": { + "psr-4": { + "SonsOfPHP\\Contract\\Mailer\\": "" + } + }, + "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" + } + ] +} \ No newline at end of file diff --git a/src/SonsOfPHP/Contract/Money/AmountInterface.php b/src/SonsOfPHP/Contract/Money/AmountInterface.php index f5f7a456..a2e2f5f4 100644 --- a/src/SonsOfPHP/Contract/Money/AmountInterface.php +++ b/src/SonsOfPHP/Contract/Money/AmountInterface.php @@ -4,34 +4,28 @@ namespace SonsOfPHP\Contract\Money; -use SonsOfPHP\Contract\Money\Operator\Amount\AmountOperatorInterface; -use SonsOfPHP\Contract\Money\Query\Amount\AmountQueryInterface; - /** * Amount. * * The amount is used to represent the Numerical Value of the Money. * + * The amount SHOULD be represented in the smallest form of the currency. So + * for USD a value of `420` would represent `$4.20`. + * * @author Joshua Estes */ -interface AmountInterface +interface AmountInterface // extends \Stringable { - /** - * Considering some of these methods. - */ - // public function getPrecision(): int; - // public function getScale(): int; - /** * Allows you to run you own operations of the amount. */ - public function with(AmountOperatorInterface $operator): self; + public function with(AmountOperatorInterface $operator): static; /** * Allows you to ask different questions about the amount and get * different results returned to you. */ - public function query(AmountQueryInterface $query); + public function query(AmountQueryInterface $query)/*: mixed*/; /** * Returns the value for this amount as a string. @@ -46,7 +40,7 @@ public function toInt(): int; /** * Returns the value for this amount as a float. */ - public function toFloat(): float; + //public function toFloat(): float; /** * Returns the value that this is for the object. @@ -56,7 +50,7 @@ public function getAmount(): string; /** * Add amounts together. */ - public function add(self $amount): self; + public function add(AmountInterface $amount): static; /** * Subtract amounts from each other. @@ -67,27 +61,27 @@ public function add(self $amount): self; * $amount1->subtract($amount2) == 50 * $amount2->subtract($amount1) == -50 */ - public function subtract(self $amount): self; + public function subtract(AmountInterface $amount): static; /** * Multiply amount by a specific amount. */ - public function multiply($multiplier): self; + public function multiply(/*int */$multiplier): static; /** * Divide the amount by a specific amount. */ - public function divide($divisor): self; + public function divide(/*int */$divisor): static; - public function isEqualTo(self $amount): bool; + public function isEqualTo(AmountInterface $amount): bool; - public function isGreaterThan(self $amount): bool; + public function isGreaterThan(AmountInterface $amount): bool; - public function isGreaterThanOrEqualTo(self $amount): bool; + public function isGreaterThanOrEqualTo(AmountInterface $amount): bool; - public function isLessThan(self $amount): bool; + public function isLessThan(AmountInterface $amount): bool; - public function isLessThanOrEqualTo(self $amount): bool; + public function isLessThanOrEqualTo(AmountInterface $amount): bool; public function isNegative(): bool; diff --git a/src/SonsOfPHP/Contract/Money/Operator/Amount/AmountOperatorInterface.php b/src/SonsOfPHP/Contract/Money/AmountOperatorInterface.php similarity index 76% rename from src/SonsOfPHP/Contract/Money/Operator/Amount/AmountOperatorInterface.php rename to src/SonsOfPHP/Contract/Money/AmountOperatorInterface.php index be298f24..a1617d21 100644 --- a/src/SonsOfPHP/Contract/Money/Operator/Amount/AmountOperatorInterface.php +++ b/src/SonsOfPHP/Contract/Money/AmountOperatorInterface.php @@ -2,9 +2,8 @@ declare(strict_types=1); -namespace SonsOfPHP\Contract\Money\Operator\Amount; +namespace SonsOfPHP\Contract\Money; -use SonsOfPHP\Contract\Money\AmountInterface; use SonsOfPHP\Contract\Money\Exception\MoneyExceptionInterface; /** diff --git a/src/SonsOfPHP/Contract/Money/Query/Amount/AmountQueryInterface.php b/src/SonsOfPHP/Contract/Money/AmountQueryInterface.php similarity index 75% rename from src/SonsOfPHP/Contract/Money/Query/Amount/AmountQueryInterface.php rename to src/SonsOfPHP/Contract/Money/AmountQueryInterface.php index ae4e8272..07a22221 100644 --- a/src/SonsOfPHP/Contract/Money/Query/Amount/AmountQueryInterface.php +++ b/src/SonsOfPHP/Contract/Money/AmountQueryInterface.php @@ -2,9 +2,8 @@ declare(strict_types=1); -namespace SonsOfPHP\Contract\Money\Query\Amount; +namespace SonsOfPHP\Contract\Money; -use SonsOfPHP\Contract\Money\AmountInterface; use SonsOfPHP\Contract\Money\Exception\MoneyExceptionInterface; /** diff --git a/src/SonsOfPHP/Contract/Money/CurrencyInterface.php b/src/SonsOfPHP/Contract/Money/CurrencyInterface.php index c1d55275..ca99d6d4 100644 --- a/src/SonsOfPHP/Contract/Money/CurrencyInterface.php +++ b/src/SonsOfPHP/Contract/Money/CurrencyInterface.php @@ -4,8 +4,6 @@ namespace SonsOfPHP\Contract\Money; -use SonsOfPHP\Contract\Money\Query\Currency\CurrencyQueryInterface; - /** * Currency Interface. * @@ -18,14 +16,14 @@ interface CurrencyInterface /** * Returns the Alphabetic Code of the currency. * - * Defined by the ISO 4217 standard + * Defined by the ISO-4217 standard */ public function getCurrencyCode(): string; /** * The Currency's Numeric Code. * - * Defined by the ISO 4217 standard + * Defined by the ISO-4217 standard * * @return int|null This may return null if the either does not have one or is unknown */ @@ -34,7 +32,7 @@ public function getNumericCode(): ?int; /** * The Currency's Minor Unit. * - * Defined by the ISO 4217 standard + * Defined by the ISO-4217 standard * * “0” means that there is no minor unit for that currency, whereas “1”, * “2” and “3” signify a ratio of 10:1, 100:1 and 1000:1 respectively. @@ -48,5 +46,8 @@ public function getMinorUnit(): ?int; */ public function isEqualTo(self $currency): bool; + /** + * Able to pass in custom queries + */ public function query(CurrencyQueryInterface $query); } diff --git a/src/SonsOfPHP/Contract/Money/CurrencyProviderInterface.php b/src/SonsOfPHP/Contract/Money/CurrencyProviderInterface.php index d3684399..1da619ba 100644 --- a/src/SonsOfPHP/Contract/Money/CurrencyProviderInterface.php +++ b/src/SonsOfPHP/Contract/Money/CurrencyProviderInterface.php @@ -5,7 +5,6 @@ namespace SonsOfPHP\Contract\Money; use SonsOfPHP\Contract\Money\Exception\MoneyExceptionInterface; -use SonsOfPHP\Contract\Money\Query\CurrencyProvider\CurrencyProviderQueryInterface; /** * Currency Provider. diff --git a/src/SonsOfPHP/Contract/Money/Query/CurrencyProvider/CurrencyProviderQueryInterface.php b/src/SonsOfPHP/Contract/Money/CurrencyProviderQueryInterface.php similarity index 73% rename from src/SonsOfPHP/Contract/Money/Query/CurrencyProvider/CurrencyProviderQueryInterface.php rename to src/SonsOfPHP/Contract/Money/CurrencyProviderQueryInterface.php index f13d949b..37925163 100644 --- a/src/SonsOfPHP/Contract/Money/Query/CurrencyProvider/CurrencyProviderQueryInterface.php +++ b/src/SonsOfPHP/Contract/Money/CurrencyProviderQueryInterface.php @@ -2,9 +2,8 @@ declare(strict_types=1); -namespace SonsOfPHP\Contract\Money\Query\CurrencyProvider; +namespace SonsOfPHP\Contract\Money; -use SonsOfPHP\Contract\Money\CurrencyProviderInterface; use SonsOfPHP\Contract\Money\Exception\MoneyExceptionInterface; /** diff --git a/src/SonsOfPHP/Contract/Money/Query/Currency/CurrencyQueryInterface.php b/src/SonsOfPHP/Contract/Money/CurrencyQueryInterface.php similarity index 75% rename from src/SonsOfPHP/Contract/Money/Query/Currency/CurrencyQueryInterface.php rename to src/SonsOfPHP/Contract/Money/CurrencyQueryInterface.php index bd253722..52dc9395 100644 --- a/src/SonsOfPHP/Contract/Money/Query/Currency/CurrencyQueryInterface.php +++ b/src/SonsOfPHP/Contract/Money/CurrencyQueryInterface.php @@ -2,9 +2,8 @@ declare(strict_types=1); -namespace SonsOfPHP\Contract\Money\Query\Currency; +namespace SonsOfPHP\Contract\Money; -use SonsOfPHP\Contract\Money\CurrencyInterface; use SonsOfPHP\Contract\Money\Exception\MoneyExceptionInterface; /** diff --git a/src/SonsOfPHP/Contract/Money/ExchangeRateProviderInterface.php b/src/SonsOfPHP/Contract/Money/ExchangeRateProviderInterface.php new file mode 100644 index 00000000..0f8952ee --- /dev/null +++ b/src/SonsOfPHP/Contract/Money/ExchangeRateProviderInterface.php @@ -0,0 +1,10 @@ + + */ +interface ExchangeRateProviderInterface {} diff --git a/src/SonsOfPHP/Contract/Money/MoneyFormatterInterface.php b/src/SonsOfPHP/Contract/Money/MoneyFormatterInterface.php new file mode 100644 index 00000000..212988b7 --- /dev/null +++ b/src/SonsOfPHP/Contract/Money/MoneyFormatterInterface.php @@ -0,0 +1,13 @@ + + */ +interface MoneyFormatterInterface +{ + public function format(MoneyInterface $money): string; +} diff --git a/src/SonsOfPHP/Contract/Money/MoneyInterface.php b/src/SonsOfPHP/Contract/Money/MoneyInterface.php index 70f3ebfb..b8669e4e 100644 --- a/src/SonsOfPHP/Contract/Money/MoneyInterface.php +++ b/src/SonsOfPHP/Contract/Money/MoneyInterface.php @@ -4,9 +4,6 @@ namespace SonsOfPHP\Contract\Money; -use SonsOfPHP\Contract\Money\Operator\Money\MoneyOperatorInterface; -use SonsOfPHP\Contract\Money\Query\Money\MoneyQueryInterface; - /** * Money Interface. * @@ -14,7 +11,7 @@ * * @author Joshua Estes */ -interface MoneyInterface +interface MoneyInterface // extends \JsonSerializable { public function getAmount(): AmountInterface; diff --git a/src/SonsOfPHP/Contract/Money/Operator/Money/MoneyOperatorInterface.php b/src/SonsOfPHP/Contract/Money/MoneyOperatorInterface.php similarity index 76% rename from src/SonsOfPHP/Contract/Money/Operator/Money/MoneyOperatorInterface.php rename to src/SonsOfPHP/Contract/Money/MoneyOperatorInterface.php index df17d9d3..19cea13d 100644 --- a/src/SonsOfPHP/Contract/Money/Operator/Money/MoneyOperatorInterface.php +++ b/src/SonsOfPHP/Contract/Money/MoneyOperatorInterface.php @@ -2,10 +2,9 @@ declare(strict_types=1); -namespace SonsOfPHP\Contract\Money\Operator\Money; +namespace SonsOfPHP\Contract\Money; use SonsOfPHP\Contract\Money\Exception\MoneyExceptionInterface; -use SonsOfPHP\Contract\Money\MoneyInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Contract/Money/MoneyParserInterface.php b/src/SonsOfPHP/Contract/Money/MoneyParserInterface.php new file mode 100644 index 00000000..7f2869f4 --- /dev/null +++ b/src/SonsOfPHP/Contract/Money/MoneyParserInterface.php @@ -0,0 +1,19 @@ + + */ +interface MoneyParserInterface +{ + /** + * Examples + * parse('$4.20') + * parse('4.20', 'usd') + * parse('4.20', Currency::USD()) + */ + public function parse(strin $money, Currency|string|null $currency = null): string; +} diff --git a/src/SonsOfPHP/Contract/Money/Query/Money/MoneyQueryInterface.php b/src/SonsOfPHP/Contract/Money/MoneyQueryInterface.php similarity index 76% rename from src/SonsOfPHP/Contract/Money/Query/Money/MoneyQueryInterface.php rename to src/SonsOfPHP/Contract/Money/MoneyQueryInterface.php index b7a3cfaf..268e627b 100644 --- a/src/SonsOfPHP/Contract/Money/Query/Money/MoneyQueryInterface.php +++ b/src/SonsOfPHP/Contract/Money/MoneyQueryInterface.php @@ -2,10 +2,9 @@ declare(strict_types=1); -namespace SonsOfPHP\Contract\Money\Query\Money; +namespace SonsOfPHP\Contract\Money; use SonsOfPHP\Contract\Money\Exception\MoneyExceptionInterface; -use SonsOfPHP\Contract\Money\MoneyInterface; /** * @author Joshua Estes diff --git a/src/SonsOfPHP/Contract/Pager/AdapterInterface.php b/src/SonsOfPHP/Contract/Pager/AdapterInterface.php index 518c8187..a157d13e 100644 --- a/src/SonsOfPHP/Contract/Pager/AdapterInterface.php +++ b/src/SonsOfPHP/Contract/Pager/AdapterInterface.php @@ -20,7 +20,7 @@ interface AdapterInterface extends \Countable * Length is how many results to return. For example, if length is 10, a * MAXIMUM of 10 results will be returned * - * Length should always be a positive number that is 1 or greater + * Length MUST always be a positive number that is 1 or greater OR be null * * If null is passed in as length, this should return ALL the results. * diff --git a/src/SonsOfPHP/Contract/Pager/PagerInterface.php b/src/SonsOfPHP/Contract/Pager/PagerInterface.php index 3272a565..38e2f8df 100644 --- a/src/SonsOfPHP/Contract/Pager/PagerInterface.php +++ b/src/SonsOfPHP/Contract/Pager/PagerInterface.php @@ -20,12 +20,25 @@ interface PagerInterface extends \Countable, \IteratorAggregate, \JsonSerializab */ public function getCurrentPageResults(): iterable; + /** + * Returns the total number of results that the adapter can return + */ public function getTotalResults(): int; + /** + * Returns the total number of pages that exist based on the total results + * and the max results per page + */ public function getTotalPages(): int; + /** + * Returns true if there are 2 or more total pages + */ public function haveToPaginate(): bool; + /** + * If there is a previous page available, this will return true + */ public function hasPreviousPage(): bool; /** @@ -33,6 +46,9 @@ public function hasPreviousPage(): bool; */ public function getPreviousPage(): ?int; + /** + * If there is a next page available, this will return true + */ public function hasNextPage(): bool; /** diff --git a/tools/infection/composer.json b/tools/infection/composer.json new file mode 100644 index 00000000..52c4ca17 --- /dev/null +++ b/tools/infection/composer.json @@ -0,0 +1,10 @@ +{ + "require": { + "infection/infection": "^0.27.8" + }, + "config": { + "allow-plugins": { + "infection/extension-installer": true + } + } +}