Started CommandBus. Changed Psalm for PHPStan as it's more strict and code ends up being more type-safe
This commit is contained in:
4
assets/js/custom.d.ts
vendored
Normal file
4
assets/js/custom.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
declare module "*.svg" {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
@@ -39,6 +39,13 @@
|
||||
"infection/infection": "^0.16.0",
|
||||
"keyvanakbary/mimic": "^1.0",
|
||||
"mockery/mockery": "^1.3",
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "^0.12.30",
|
||||
"phpstan/phpstan-beberlei-assert": "^0.12.2",
|
||||
"phpstan/phpstan-deprecation-rules": "^0.12.4",
|
||||
"phpstan/phpstan-doctrine": "^0.12.16",
|
||||
"phpstan/phpstan-strict-rules": "^0.12.2",
|
||||
"phpstan/phpstan-symfony": "^0.12.6",
|
||||
"phpunit/phpunit": "^9.0",
|
||||
"predis/predis": "^1.1",
|
||||
"spatie/async": "^1.1",
|
||||
@@ -46,6 +53,7 @@
|
||||
"symfony/debug-pack": "^1.0",
|
||||
"symfony/maker-bundle": "^1.14",
|
||||
"symfony/profiler-pack": "^1.0",
|
||||
"thecodingmachine/phpstan-safe-rule": "^1.0",
|
||||
"theofidry/psysh-bundle": "^4.3",
|
||||
"vimeo/psalm": "^3.9",
|
||||
"weirdan/doctrine-psalm-plugin": "^0.11",
|
||||
@@ -89,7 +97,7 @@
|
||||
],
|
||||
"fix-cs": "php bin/php-cs-fixer --verbose --config=.php_cs.dist --using-cache=no --path-mode=intersection fix",
|
||||
"unit-tests": "php vendor/bin/phpunit",
|
||||
"psalm": "php vendor/bin/psalm --config=psalm.xml --show-info=true --no-cache",
|
||||
"phpstan": "php vendor/bin/phpstan analyse",
|
||||
"clear-db": [
|
||||
"php bin/console doctrine:database:drop --force",
|
||||
"php bin/console doctrine:database:create",
|
||||
|
||||
447
composer.lock
generated
447
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "6270ddc380e804074cd7a0b84b947a87",
|
||||
"content-hash": "f40392689f639eeeed4fd44d5bb777a1",
|
||||
"packages": [
|
||||
{
|
||||
"name": "api-platform/core",
|
||||
@@ -9358,6 +9358,398 @@
|
||||
],
|
||||
"time": "2020-03-05T15:02:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/extension-installer",
|
||||
"version": "1.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/extension-installer.git",
|
||||
"reference": "2e041def501d661b806f50000c8a4dccbd4907b4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/extension-installer/zipball/2e041def501d661b806f50000c8a4dccbd4907b4",
|
||||
"reference": "2e041def501d661b806f50000c8a4dccbd4907b4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"composer-plugin-api": "^1.1 || ^2.0",
|
||||
"php": "^7.1",
|
||||
"phpstan/phpstan": ">=0.11.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"composer/composer": "^1.8",
|
||||
"consistence/coding-standard": "^3.8",
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "^0.4.4",
|
||||
"ergebnis/composer-normalize": "^2.0.2",
|
||||
"jakub-onderka/php-parallel-lint": "^1.0",
|
||||
"phing/phing": "^2.16",
|
||||
"phpstan/phpstan-strict-rules": "^0.11",
|
||||
"slevomat/coding-standard": "^5.0.4"
|
||||
},
|
||||
"type": "composer-plugin",
|
||||
"extra": {
|
||||
"class": "PHPStan\\ExtensionInstaller\\Plugin"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PHPStan\\ExtensionInstaller\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Composer plugin for automatic installation of PHPStan extensions",
|
||||
"time": "2020-03-31T16:00:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "0.12.30",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "1f2c16d3fbb5eec6e55fbe2358e32570cefa20e5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/1f2c16d3fbb5eec6e55fbe2358e32570cefa20e5",
|
||||
"reference": "1f2c16d3fbb5eec6e55fbe2358e32570cefa20e5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1"
|
||||
},
|
||||
"conflict": {
|
||||
"phpstan/phpstan-shim": "*"
|
||||
},
|
||||
"bin": [
|
||||
"phpstan",
|
||||
"phpstan.phar"
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "0.12-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "PHPStan - PHP Static Analysis Tool",
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/ondrejmirtes",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/phpstan",
|
||||
"type": "patreon"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-06-21T14:08:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-beberlei-assert",
|
||||
"version": "0.12.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-beberlei-assert.git",
|
||||
"reference": "c13141d2548ded3724a56ff0f7df37384187a149"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-beberlei-assert/zipball/c13141d2548ded3724a56ff0f7df37384187a149",
|
||||
"reference": "c13141d2548ded3724a56ff0f7df37384187a149",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "~7.1",
|
||||
"phpstan/phpstan": "^0.12.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"beberlei/assert": "^2.9.5",
|
||||
"consistence/coding-standard": "^3.0.1",
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "^0.4.4",
|
||||
"ergebnis/composer-normalize": "^2.0.2",
|
||||
"jakub-onderka/php-parallel-lint": "^1.0",
|
||||
"phing/phing": "^2.16.0",
|
||||
"phpstan/phpstan-phpunit": "^0.12.3",
|
||||
"phpstan/phpstan-strict-rules": "^0.12",
|
||||
"phpunit/phpunit": "^7.5.18",
|
||||
"slevomat/coding-standard": "^4.5.2"
|
||||
},
|
||||
"type": "phpstan-extension",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "0.12-dev"
|
||||
},
|
||||
"phpstan": {
|
||||
"includes": [
|
||||
"extension.neon"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PHPStan\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "PHPStan beberlei/assert extension",
|
||||
"time": "2020-01-03T10:02:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-deprecation-rules",
|
||||
"version": "0.12.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-deprecation-rules.git",
|
||||
"reference": "9b4b8851fb5d59fd0eed00fbe9c22cfc328e0187"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/9b4b8851fb5d59fd0eed00fbe9c22cfc328e0187",
|
||||
"reference": "9b4b8851fb5d59fd0eed00fbe9c22cfc328e0187",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "~7.1",
|
||||
"phpstan/phpstan": "^0.12"
|
||||
},
|
||||
"require-dev": {
|
||||
"consistence/coding-standard": "^3.0.1",
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "^0.4.4",
|
||||
"ergebnis/composer-normalize": "^2.0.2",
|
||||
"jakub-onderka/php-parallel-lint": "^1.0",
|
||||
"phing/phing": "^2.16.0",
|
||||
"phpstan/phpstan-phpunit": "^0.12",
|
||||
"phpunit/phpunit": "^7.0",
|
||||
"slevomat/coding-standard": "^4.5.2"
|
||||
},
|
||||
"type": "phpstan-extension",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "0.12-dev"
|
||||
},
|
||||
"phpstan": {
|
||||
"includes": [
|
||||
"rules.neon"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PHPStan\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.",
|
||||
"time": "2020-05-30T18:02:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-doctrine",
|
||||
"version": "0.12.16",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-doctrine.git",
|
||||
"reference": "65146e35905478bfb4e2ba078ffca1a16029d4ee"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/65146e35905478bfb4e2ba078ffca1a16029d4ee",
|
||||
"reference": "65146e35905478bfb4e2ba078ffca1a16029d4ee",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "~7.1",
|
||||
"phpstan/phpstan": "^0.12.26"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/collections": "<1.0",
|
||||
"doctrine/common": "<2.7",
|
||||
"doctrine/mongodb-odm": "<1.2",
|
||||
"doctrine/orm": "<2.5",
|
||||
"doctrine/persistence": "<1.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"consistence/coding-standard": "^3.0.1",
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "^0.4.4",
|
||||
"doctrine/collections": "^1.0",
|
||||
"doctrine/common": "^2.7 || ^3.0",
|
||||
"doctrine/mongodb-odm": "^1.3 || ^2.1",
|
||||
"doctrine/orm": "^2.5",
|
||||
"doctrine/persistence": "^1.1 || ^2.0",
|
||||
"ergebnis/composer-normalize": "^2.0.2",
|
||||
"jakub-onderka/php-parallel-lint": "^1.0",
|
||||
"phing/phing": "^2.16.0",
|
||||
"phpstan/phpstan-phpunit": "^0.12",
|
||||
"phpstan/phpstan-strict-rules": "^0.12",
|
||||
"phpunit/phpunit": "^7.0",
|
||||
"ramsey/uuid-doctrine": "^1.5.0",
|
||||
"slevomat/coding-standard": "^4.5.2"
|
||||
},
|
||||
"type": "phpstan-extension",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "0.12-dev"
|
||||
},
|
||||
"phpstan": {
|
||||
"includes": [
|
||||
"extension.neon",
|
||||
"rules.neon"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PHPStan\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Doctrine extensions for PHPStan",
|
||||
"time": "2020-06-14T11:03:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-strict-rules",
|
||||
"version": "0.12.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
|
||||
"reference": "a670a59aff7cf96f75d21b974860ada10e25b2ee"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/a670a59aff7cf96f75d21b974860ada10e25b2ee",
|
||||
"reference": "a670a59aff7cf96f75d21b974860ada10e25b2ee",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "~7.1",
|
||||
"phpstan/phpstan": "^0.12.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"consistence/coding-standard": "^3.0.1",
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "^0.4.4",
|
||||
"ergebnis/composer-normalize": "^2.0.2",
|
||||
"jakub-onderka/php-parallel-lint": "^1.0",
|
||||
"phing/phing": "^2.16.0",
|
||||
"phpstan/phpstan-phpunit": "^0.12",
|
||||
"phpunit/phpunit": "^7.0",
|
||||
"slevomat/coding-standard": "^4.5.2"
|
||||
},
|
||||
"type": "phpstan-extension",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "0.12-dev"
|
||||
},
|
||||
"phpstan": {
|
||||
"includes": [
|
||||
"rules.neon"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PHPStan\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Extra strict and opinionated rules for PHPStan",
|
||||
"time": "2020-01-20T13:08:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-symfony",
|
||||
"version": "0.12.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-symfony.git",
|
||||
"reference": "ba69dcd8e57c1a8580bf190e0554bea0fc37fe2f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/ba69dcd8e57c1a8580bf190e0554bea0fc37fe2f",
|
||||
"reference": "ba69dcd8e57c1a8580bf190e0554bea0fc37fe2f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-simplexml": "*",
|
||||
"php": "^7.1",
|
||||
"phpstan/phpstan": "^0.12"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/framework-bundle": "<3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"consistence/coding-standard": "^3.0.1",
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "^0.4.4",
|
||||
"ergebnis/composer-normalize": "^2.0.2",
|
||||
"jakub-onderka/php-parallel-lint": "^1.0",
|
||||
"phing/phing": "^2.16.0",
|
||||
"phpstan/phpstan-phpunit": "^0.12",
|
||||
"phpstan/phpstan-strict-rules": "^0.12",
|
||||
"phpunit/phpunit": "^7.0",
|
||||
"slevomat/coding-standard": "^4.5.2",
|
||||
"squizlabs/php_codesniffer": "^3.3.2",
|
||||
"symfony/console": "^4.0",
|
||||
"symfony/framework-bundle": "^4.0",
|
||||
"symfony/http-foundation": "^4.0",
|
||||
"symfony/messenger": "^4.2",
|
||||
"symfony/serializer": "^4.0"
|
||||
},
|
||||
"type": "phpstan-extension",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "0.12-dev"
|
||||
},
|
||||
"phpstan": {
|
||||
"includes": [
|
||||
"extension.neon",
|
||||
"rules.neon"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PHPStan\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Lukáš Unger",
|
||||
"email": "looky.msc@gmail.com",
|
||||
"homepage": "https://lookyman.net"
|
||||
}
|
||||
],
|
||||
"description": "Symfony Framework extensions and rules for PHPStan",
|
||||
"time": "2020-04-15T20:26:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
"version": "8.0.2",
|
||||
@@ -11468,6 +11860,59 @@
|
||||
],
|
||||
"time": "2020-05-28T08:20:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "thecodingmachine/phpstan-safe-rule",
|
||||
"version": "v1.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thecodingmachine/phpstan-safe-rule.git",
|
||||
"reference": "ba333eb573167371309c8ae8fdab932991925e96"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thecodingmachine/phpstan-safe-rule/zipball/ba333eb573167371309c8ae8fdab932991925e96",
|
||||
"reference": "ba333eb573167371309c8ae8fdab932991925e96",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1",
|
||||
"phpstan/phpstan": "^0.10 | ^0.11 | ^0.12",
|
||||
"thecodingmachine/safe": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-coveralls/php-coveralls": "^2.1",
|
||||
"phpunit/phpunit": "^7.5.2",
|
||||
"squizlabs/php_codesniffer": "^3.4"
|
||||
},
|
||||
"type": "phpstan-extension",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0-dev"
|
||||
},
|
||||
"phpstan": {
|
||||
"includes": [
|
||||
"phpstan-safe-rule.neon"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"TheCodingMachine\\Safe\\PHPStan\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Négrier",
|
||||
"email": "d.negrier@thecodingmachine.com"
|
||||
}
|
||||
],
|
||||
"description": "A PHPStan rule to detect safety issues. Must be used in conjunction with thecodingmachine/safe",
|
||||
"time": "2020-01-02T14:59:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "theofidry/psysh-bundle",
|
||||
"version": "4.3.0",
|
||||
|
||||
13
phpstan.neon
Normal file
13
phpstan.neon
Normal file
@@ -0,0 +1,13 @@
|
||||
parameters:
|
||||
level: 8
|
||||
excludes_analyse:
|
||||
- src/CheeperSpaghetti/*
|
||||
- src/CheeperLayered/*
|
||||
- tests/bootstrap.php
|
||||
paths:
|
||||
- src
|
||||
symfony:
|
||||
container_xml_path: '%rootDir%/../../../var/cache/dev/App_KernelDevDebugContainer.xml'
|
||||
console_application_loader: tests/console-application.php
|
||||
typeAliases:
|
||||
PostEvents: Architecture\CQRS\Domain\PostWasCreated|Architecture\CQRS\Domain\PostWasPublished|Architecture\CQRS\Domain\PostWasCategorized|Architecture\CQRS\Domain\PostContentWasChanged|Architecture\CQRS\Domain\PostTitleWasChanged
|
||||
41
psalm.xml
41
psalm.xml
@@ -1,41 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<psalm
|
||||
totallyTyped="true"
|
||||
resolveFromConfigFile="true"
|
||||
strictBinaryOperands="true"
|
||||
allowPhpStormGenerics="true"
|
||||
findUnusedVariablesAndParams="true"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="https://getpsalm.org/schema/config"
|
||||
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
|
||||
>
|
||||
<projectFiles>
|
||||
<directory name="src"/>
|
||||
<ignoreFiles>
|
||||
<directory name="vendor"/>
|
||||
<directory name="src/CheeperSpaghetti/"/>
|
||||
<directory name="src/CheeperLayered/"/>
|
||||
<file name="src/App/Kernel.php"/>
|
||||
</ignoreFiles>
|
||||
</projectFiles>
|
||||
|
||||
<issueHandlers>
|
||||
<PropertyNotSetInConstructor>
|
||||
<errorLevel type="suppress">
|
||||
<!-- Due to a bug in PSALM. See https://github.com/vimeo/psalm/issues/2319 -->
|
||||
<file name="src/Architecture/CQRS/Infrastructure/Persistence/Doctrine/DoctrineFollowersRepository.php" />
|
||||
</errorLevel>
|
||||
</PropertyNotSetInConstructor>
|
||||
</issueHandlers>
|
||||
|
||||
<stubs>
|
||||
<file name=".psalm-stubs/lstrojny-functional-php.php"/>
|
||||
<file name=".psalm-stubs/doctrine-orm.php"/>
|
||||
<file name=".psalm-stubs/symfony-messenger.php"/>
|
||||
<file name=".psalm-stubs/api-platform.php"/>
|
||||
</stubs>
|
||||
|
||||
<plugins>
|
||||
<pluginClass class="Weirdan\DoctrinePsalmPlugin\Plugin"/>
|
||||
</plugins>
|
||||
</psalm>
|
||||
@@ -4,14 +4,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\API\DataPersister;
|
||||
|
||||
use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
|
||||
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
|
||||
use App\API\Resources\Author;
|
||||
use App\Messenger\CommandBus;
|
||||
use Cheeper\Application\Command\Author\SignUp;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
/** @template-implements ContextAwareDataPersisterInterface<Author> */
|
||||
final class AuthorDataPersister implements ContextAwareDataPersisterInterface
|
||||
final class AuthorDataPersister implements DataPersisterInterface
|
||||
{
|
||||
private CommandBus $commandBus;
|
||||
|
||||
@@ -21,13 +20,13 @@ final class AuthorDataPersister implements ContextAwareDataPersisterInterface
|
||||
}
|
||||
|
||||
/** @param mixed $data */
|
||||
public function supports($data, array $context = []): bool
|
||||
public function supports($data): bool
|
||||
{
|
||||
return $data instanceof Author && null === $data->id;
|
||||
}
|
||||
|
||||
/** @param Author $data */
|
||||
public function persist($data, array $context = []): Author
|
||||
/** @param mixed|Author $data */
|
||||
public function persist($data): Author
|
||||
{
|
||||
$authorId = Uuid::uuid4();
|
||||
|
||||
@@ -49,8 +48,8 @@ final class AuthorDataPersister implements ContextAwareDataPersisterInterface
|
||||
return $data;
|
||||
}
|
||||
|
||||
/** @param Author $data */
|
||||
public function remove($data, array $context = []): void
|
||||
/** @param mixed|Author $data */
|
||||
public function remove($data): void
|
||||
{
|
||||
// TODO: Implement remove() method.
|
||||
}
|
||||
|
||||
@@ -4,14 +4,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\API\DataPersister;
|
||||
|
||||
use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
|
||||
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
|
||||
use App\API\Resources\Follower;
|
||||
use App\Messenger\CommandBus;
|
||||
use Cheeper\Application\Command\Author\Follow;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
/** @template-implements ContextAwareDataPersisterInterface<Follower> */
|
||||
final class FollowerDataPersister implements ContextAwareDataPersisterInterface
|
||||
final class FollowerDataPersister implements DataPersisterInterface
|
||||
{
|
||||
private CommandBus $commandBus;
|
||||
|
||||
@@ -21,13 +19,13 @@ final class FollowerDataPersister implements ContextAwareDataPersisterInterface
|
||||
}
|
||||
|
||||
/** @param mixed $data */
|
||||
public function supports($data, array $context = []): bool
|
||||
public function supports($data): bool
|
||||
{
|
||||
return $data instanceof Follower;
|
||||
}
|
||||
|
||||
/** @param Follower $data */
|
||||
public function persist($data, array $context = []): Follower
|
||||
/** @param mixed|Follower $data */
|
||||
public function persist($data): Follower
|
||||
{
|
||||
$this->commandBus->execute(
|
||||
Follow::anAuthor(
|
||||
@@ -39,8 +37,8 @@ final class FollowerDataPersister implements ContextAwareDataPersisterInterface
|
||||
return $data;
|
||||
}
|
||||
|
||||
/** @param Follower $data */
|
||||
public function remove($data, array $context = []): void
|
||||
/** @param mixed|Follower $data */
|
||||
public function remove($data): void
|
||||
{
|
||||
// TODO: Implement remove() method.
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ final class UuidResolver implements ArgumentValueResolverInterface
|
||||
return UuidInterface::class === $argument->getType();
|
||||
}
|
||||
|
||||
/** @return iterable<UuidInterface> */
|
||||
public function resolve(Request $request, ArgumentMetadata $argument): iterable
|
||||
{
|
||||
yield Uuid::fromString(
|
||||
|
||||
@@ -38,10 +38,6 @@ final class SignupCommand extends Command
|
||||
$website = $input->getArgument('website');
|
||||
$birthdate = $input->getArgument('birthdate');
|
||||
|
||||
if (!is_string($username) || !is_string($email) || !is_string($name) || !is_string($biography) || !is_string($location) || !is_string($website) || !is_string($birthdate)) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
$command = new SignUp(
|
||||
Uuid::uuid4()->toString(),
|
||||
$username,
|
||||
|
||||
@@ -3,52 +3,26 @@
|
||||
namespace App;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
|
||||
use Symfony\Component\Config\Loader\LoaderInterface;
|
||||
use Symfony\Component\Config\Resource\FileResource;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
|
||||
use Symfony\Component\Routing\RouteCollectionBuilder;
|
||||
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
|
||||
|
||||
class Kernel extends BaseKernel
|
||||
{
|
||||
use MicroKernelTrait;
|
||||
|
||||
private const CONFIG_EXTS = '.{php,xml,yaml,yml}';
|
||||
|
||||
public function registerBundles(): iterable
|
||||
protected function configureContainer(ContainerConfigurator $container): void
|
||||
{
|
||||
$contents = require $this->getProjectDir().'/config/bundles.php';
|
||||
foreach ($contents as $class => $envs) {
|
||||
if ($envs[$this->environment] ?? $envs['all'] ?? false) {
|
||||
yield new $class();
|
||||
}
|
||||
}
|
||||
$container->import('../../config/{packages}/*.yaml');
|
||||
$container->import('../../config/{packages}/'.$this->environment.'/*.yaml');
|
||||
$container->import('../../config/{services}.yaml');
|
||||
$container->import('../../config/{services}_'.$this->environment.'.yaml');
|
||||
}
|
||||
|
||||
public function getProjectDir(): string
|
||||
protected function configureRoutes(RoutingConfigurator $routes): void
|
||||
{
|
||||
return \dirname(__DIR__, 2);
|
||||
}
|
||||
|
||||
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
|
||||
{
|
||||
$container->addResource(new FileResource($this->getProjectDir().'/config/bundles.php'));
|
||||
$container->setParameter('container.dumper.inline_class_loader', \PHP_VERSION_ID < 70400 || $this->debug);
|
||||
$container->setParameter('container.dumper.inline_factories', true);
|
||||
$confDir = $this->getProjectDir().'/config';
|
||||
|
||||
$loader->load($confDir.'/{packages}/*'.self::CONFIG_EXTS, 'glob');
|
||||
$loader->load($confDir.'/{packages}/'.$this->environment.'/*'.self::CONFIG_EXTS, 'glob');
|
||||
$loader->load($confDir.'/{services}'.self::CONFIG_EXTS, 'glob');
|
||||
$loader->load($confDir.'/{services}_'.$this->environment.self::CONFIG_EXTS, 'glob');
|
||||
}
|
||||
|
||||
protected function configureRoutes(RouteCollectionBuilder $routes): void
|
||||
{
|
||||
$confDir = $this->getProjectDir().'/config';
|
||||
|
||||
$routes->import($confDir.'/{routes}/'.$this->environment.'/*'.self::CONFIG_EXTS, '/', 'glob');
|
||||
$routes->import($confDir.'/{routes}/*'.self::CONFIG_EXTS, '/', 'glob');
|
||||
$routes->import($confDir.'/{routes}'.self::CONFIG_EXTS, '/', 'glob');
|
||||
$routes->import('../../config/{routes}/'.$this->environment.'/*.yaml');
|
||||
$routes->import('../../config/{routes}/*.yaml');
|
||||
$routes->import('../../config/{routes}.yaml');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,12 +17,8 @@ final class CommandBus
|
||||
$this->messageBus = $commandBus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param AsyncCommand|SyncCommand|object $command
|
||||
* @return T|Envelope The handler returned value
|
||||
*/
|
||||
public function execute($command)
|
||||
/** @param AsyncCommand|SyncCommand|object $command */
|
||||
public function execute($command): Envelope
|
||||
{
|
||||
if ($command instanceof SyncCommand) {
|
||||
return $this->handle($command);
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
namespace Architecture\CQRS\Domain;
|
||||
|
||||
/** @template T of DomainEvent */
|
||||
//snippet aggregate-root
|
||||
class AggregateRoot
|
||||
{
|
||||
/** @var DomainEvent[] */
|
||||
/** @var T[] */
|
||||
private array $recordedEvents = [];
|
||||
|
||||
/** @param T $event */
|
||||
protected function recordApplyAndPublishThat(DomainEvent $event): void
|
||||
{
|
||||
$this->recordThat($event);
|
||||
@@ -15,6 +17,7 @@ class AggregateRoot
|
||||
$this->publishThat($event);
|
||||
}
|
||||
|
||||
/** @param T $event */
|
||||
protected function recordThat(DomainEvent $event): void
|
||||
{
|
||||
$this->recordedEvents[] = $event;
|
||||
@@ -26,6 +29,7 @@ class AggregateRoot
|
||||
|
||||
$modifier = 'apply' . $className;
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->$modifier($event);
|
||||
}
|
||||
|
||||
@@ -34,7 +38,7 @@ class AggregateRoot
|
||||
DomainEventPublisher::instance()->publish($event);
|
||||
}
|
||||
|
||||
/** @return DomainEvent[] */
|
||||
/** @return T[] */
|
||||
public function recordedEvents(): array
|
||||
{
|
||||
return $this->recordedEvents;
|
||||
|
||||
@@ -15,7 +15,7 @@ class CategoryId
|
||||
|
||||
public static function create(): CategoryId
|
||||
{
|
||||
return new static(Uuid::uuid4()->toString());
|
||||
return new self(Uuid::uuid4()->toString());
|
||||
}
|
||||
|
||||
public function id(): string
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Architecture\CQRS\Domain;
|
||||
|
||||
/** @extends AggregateRoot<PostEvents> */
|
||||
//snippet post
|
||||
class Post extends AggregateRoot
|
||||
{
|
||||
@@ -10,6 +11,7 @@ class Post extends AggregateRoot
|
||||
private ?string $title = null;
|
||||
private ?string $content = null;
|
||||
private bool $published = false;
|
||||
/** @var CategoryId[] */
|
||||
private array $categories = [];
|
||||
|
||||
protected function __construct(PostId $id)
|
||||
@@ -32,6 +34,7 @@ class Post extends AggregateRoot
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/** @return CategoryId[] */
|
||||
public function categories(): array
|
||||
{
|
||||
return array_values($this->categories);
|
||||
@@ -47,7 +50,7 @@ class Post extends AggregateRoot
|
||||
{
|
||||
$postId = PostId::create();
|
||||
|
||||
$post = new static($postId);
|
||||
$post = new self($postId);
|
||||
|
||||
$post->recordApplyAndPublishThat(
|
||||
new PostWasCreated($postId, $title, $content)
|
||||
|
||||
@@ -15,7 +15,7 @@ class PostId
|
||||
|
||||
public static function create(): PostId
|
||||
{
|
||||
return new static(Uuid::uuid4()->toString());
|
||||
return new self(Uuid::uuid4()->toString());
|
||||
}
|
||||
|
||||
public function id(): string
|
||||
|
||||
@@ -2,10 +2,14 @@
|
||||
|
||||
namespace Architecture\CQRS\Domain;
|
||||
|
||||
/**
|
||||
* @template T of DomainEvent
|
||||
*/
|
||||
//snippet projection
|
||||
interface Projection
|
||||
{
|
||||
public function listensTo(): string;
|
||||
/** @param T $event */
|
||||
public function project(DomainEvent $event): void;
|
||||
}
|
||||
//end-snippet
|
||||
|
||||
@@ -15,10 +15,13 @@ interface PostRepository
|
||||
{
|
||||
public function save(Post $post): void;
|
||||
public function byId(PostId $id): Post;
|
||||
/** @return Post[] */
|
||||
public function all(): array;
|
||||
public function byCategory(CategoryId $categoryId): Post;
|
||||
/** @return Post[] */
|
||||
public function byTag(TagId $tagId): array;
|
||||
public function withComments(PostId $id): Post;
|
||||
/** @return Post[] */
|
||||
public function groupedByMonth(): array;
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -13,8 +13,10 @@ use Doctrine\ORM\EntityManager;
|
||||
class DoctrinePostRepository implements PostRepository
|
||||
{
|
||||
private EntityManager $em;
|
||||
/** @var Projector<PostEvents> */
|
||||
private Projector $projector;
|
||||
|
||||
/** @param Projector<PostEvents> $projector */
|
||||
public function __construct(EntityManager $em, Projector $projector)
|
||||
{
|
||||
$this->em = $em;
|
||||
@@ -23,7 +25,7 @@ class DoctrinePostRepository implements PostRepository
|
||||
|
||||
public function save(Post $post): void
|
||||
{
|
||||
$this->em->transactional(function (EntityManager $em) use ($post) {
|
||||
$this->em->transactional(static function(EntityManager $em) use ($post): void {
|
||||
$em->persist($post);
|
||||
|
||||
foreach ($post->recordedEvents() as $event) {
|
||||
|
||||
@@ -7,6 +7,7 @@ use Architecture\CQRS\Domain\PostContentWasChanged;
|
||||
use Architecture\CQRS\Domain\Projection;
|
||||
use Elasticsearch\Client;
|
||||
|
||||
/** @implements Projection<PostContentWasChanged> */
|
||||
class PostContentWasChangedProjection implements Projection
|
||||
{
|
||||
private Client $client;
|
||||
@@ -21,11 +22,11 @@ class PostContentWasChangedProjection implements Projection
|
||||
return PostContentWasChanged::class;
|
||||
}
|
||||
|
||||
/** @param PostContentWasChanged $event */
|
||||
public function project(DomainEvent $event): void
|
||||
{
|
||||
/** @var PostContentWasChanged $event */
|
||||
$id = $event->postId()->id();
|
||||
|
||||
|
||||
$this->client->update([
|
||||
'index' => 'posts',
|
||||
'type' => 'post',
|
||||
|
||||
@@ -7,6 +7,7 @@ use Architecture\CQRS\Domain\PostTitleWasChanged;
|
||||
use Architecture\CQRS\Domain\Projection;
|
||||
use Elasticsearch\Client;
|
||||
|
||||
/** @implements Projection<PostTitleWasChanged> */
|
||||
class PostTitleWasChangedProjection implements Projection
|
||||
{
|
||||
private Client $client;
|
||||
@@ -21,9 +22,9 @@ class PostTitleWasChangedProjection implements Projection
|
||||
return PostTitleWasChanged::class;
|
||||
}
|
||||
|
||||
/** @param PostTitleWasChanged $event */
|
||||
public function project(DomainEvent $event): void
|
||||
{
|
||||
/** @var PostTitleWasChanged $event */
|
||||
$id = $event->postId()->id();
|
||||
|
||||
$this->client->update([
|
||||
|
||||
@@ -7,6 +7,7 @@ use Architecture\CQRS\Domain\PostWasCategorized;
|
||||
use Architecture\CQRS\Domain\Projection;
|
||||
use Elasticsearch\Client;
|
||||
|
||||
/** @implements Projection<PostWasCategorized> */
|
||||
class PostWasCategorizedProjection implements Projection
|
||||
{
|
||||
private Client $client;
|
||||
@@ -21,9 +22,9 @@ class PostWasCategorizedProjection implements Projection
|
||||
return PostWasCategorized::class;
|
||||
}
|
||||
|
||||
/** @param PostWasCategorized $event */
|
||||
public function project(DomainEvent $event): void
|
||||
{
|
||||
/** @var PostWasCategorized $event */
|
||||
$id = $event->postId()->id();
|
||||
|
||||
$this->client->update([
|
||||
|
||||
@@ -7,6 +7,7 @@ use Architecture\CQRS\Domain\PostWasCreated;
|
||||
use Architecture\CQRS\Domain\Projection;
|
||||
use Elasticsearch\Client;
|
||||
|
||||
/** @implements Projection<PostWasCreated> */
|
||||
//snippet elasticsearch-projection
|
||||
class PostWasCreatedProjection implements Projection
|
||||
{
|
||||
@@ -22,9 +23,9 @@ class PostWasCreatedProjection implements Projection
|
||||
return PostWasCreated::class;
|
||||
}
|
||||
|
||||
/** @param PostWasCreated $event */
|
||||
public function project(DomainEvent $event): void
|
||||
{
|
||||
/** @var PostWasCreated $event */
|
||||
$id = $event->postId()->id();
|
||||
|
||||
$this->client->index([
|
||||
|
||||
@@ -7,6 +7,7 @@ use Architecture\CQRS\Domain\PostWasPublished;
|
||||
use Architecture\CQRS\Domain\Projection;
|
||||
use Elasticsearch\Client;
|
||||
|
||||
/** @implements Projection<PostWasPublished> */
|
||||
class PostWasPublishedProjection implements Projection
|
||||
{
|
||||
private Client $client;
|
||||
@@ -21,9 +22,9 @@ class PostWasPublishedProjection implements Projection
|
||||
return PostWasPublished::class;
|
||||
}
|
||||
|
||||
/** @param PostWasPublished $event */
|
||||
public function project(DomainEvent $event): void
|
||||
{
|
||||
/** @var PostWasPublished $event */
|
||||
$id = $event->postId()->id();
|
||||
|
||||
$this->client->update([
|
||||
|
||||
@@ -5,13 +5,14 @@ namespace Architecture\CQRS\Infrastructure\Projection;
|
||||
use Architecture\CQRS\Domain\DomainEvent;
|
||||
use Architecture\CQRS\Domain\Projection;
|
||||
|
||||
/** @template T of DomainEvent */
|
||||
//snippet projector
|
||||
class Projector
|
||||
{
|
||||
/** @var Projection[] */
|
||||
/** @var Projection<T>[] */
|
||||
private array $projections = [];
|
||||
|
||||
/** @param Projection[] $projections */
|
||||
/** @param Projection<T>[] $projections */
|
||||
public function register(array $projections): void
|
||||
{
|
||||
foreach ($projections as $projection) {
|
||||
@@ -19,7 +20,7 @@ class Projector
|
||||
}
|
||||
}
|
||||
|
||||
/** @param DomainEvent[] $events */
|
||||
/** @param T[] $events */
|
||||
public function project(array $events): void
|
||||
{
|
||||
foreach ($events as $event) {
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Architecture\CQRS\Presentation;
|
||||
//snippet post-controller
|
||||
class PostController
|
||||
{
|
||||
/** @return array{posts: array} */
|
||||
public function listAction(): array
|
||||
{
|
||||
$client = \Elasticsearch\ClientBuilder::create()->build();
|
||||
|
||||
@@ -5,15 +5,23 @@ namespace Architecture\ES\Domain;
|
||||
use Architecture\CQRS\Domain\AggregateRoot;
|
||||
use Architecture\CQRS\Domain\DomainEvent;
|
||||
|
||||
/**
|
||||
* @template T of DomainEvent
|
||||
* @extends AggregateRoot<T>
|
||||
*/
|
||||
//snippet event-sourced-aggregate-root
|
||||
abstract class EventSourcedAggregateRoot extends AggregateRoot
|
||||
{
|
||||
/**
|
||||
* @param EventStream<T> $events
|
||||
* @return EventSourcedAggregateRoot<T>
|
||||
*/
|
||||
abstract public static function reconstitute(EventStream $events):
|
||||
EventSourcedAggregateRoot;
|
||||
|
||||
/** @param EventStream<T> $history */
|
||||
public function replay(EventStream $history): void
|
||||
{
|
||||
/** @var DomainEvent */
|
||||
foreach ($history as $event) {
|
||||
$this->applyThat($event);
|
||||
}
|
||||
|
||||
@@ -4,15 +4,19 @@ namespace Architecture\ES\Domain;
|
||||
|
||||
use Architecture\CQRS\Domain\DomainEvent;
|
||||
|
||||
/**
|
||||
* @template T of DomainEvent
|
||||
* @implements \Iterator<T>
|
||||
*/
|
||||
class EventStream implements \Iterator
|
||||
{
|
||||
private string $aggregateId;
|
||||
/** @var DomainEvent[] */
|
||||
/** @var T[] */
|
||||
private array $events;
|
||||
|
||||
/**
|
||||
* @param string $aggregateId
|
||||
* @param DomainEvent[] $events
|
||||
* @param T[] $events
|
||||
*/
|
||||
public function __construct(string $aggregateId, array $events)
|
||||
{
|
||||
@@ -25,11 +29,12 @@ class EventStream implements \Iterator
|
||||
return $this->aggregateId;
|
||||
}
|
||||
|
||||
public function rewind()
|
||||
public function rewind(): void
|
||||
{
|
||||
reset($this->events);
|
||||
}
|
||||
|
||||
/** @return T */
|
||||
public function current()
|
||||
{
|
||||
return current($this->events);
|
||||
@@ -40,12 +45,12 @@ class EventStream implements \Iterator
|
||||
return key($this->events);
|
||||
}
|
||||
|
||||
public function next()
|
||||
public function next(): void
|
||||
{
|
||||
next($this->events);
|
||||
}
|
||||
|
||||
public function valid()
|
||||
public function valid(): bool
|
||||
{
|
||||
return key($this->events) !== null;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Architecture\ES\Domain;
|
||||
|
||||
use Architecture\CQRS\Domain\CategoryId;
|
||||
use Architecture\CQRS\Domain\DomainEvent;
|
||||
use Architecture\CQRS\Domain\PostContentWasChanged;
|
||||
use Architecture\CQRS\Domain\PostId;
|
||||
use Architecture\CQRS\Domain\PostTitleWasChanged;
|
||||
@@ -10,6 +11,7 @@ use Architecture\CQRS\Domain\PostWasCategorized;
|
||||
use Architecture\CQRS\Domain\PostWasCreated;
|
||||
use Architecture\CQRS\Domain\PostWasPublished;
|
||||
|
||||
/** @extends EventSourcedAggregateRoot<PostEvents> */
|
||||
//snippet post
|
||||
class Post extends EventSourcedAggregateRoot
|
||||
{
|
||||
@@ -18,6 +20,7 @@ class Post extends EventSourcedAggregateRoot
|
||||
private ?string $title = null;
|
||||
private ?string $content = null;
|
||||
private bool $published = false;
|
||||
/** @var CategoryId[] */
|
||||
private array $categories = [];
|
||||
|
||||
protected function __construct(PostId $id)
|
||||
@@ -29,7 +32,7 @@ class Post extends EventSourcedAggregateRoot
|
||||
{
|
||||
$postId = PostId::create();
|
||||
|
||||
$post = new static($postId);
|
||||
$post = new self($postId);
|
||||
|
||||
$post->recordApplyAndPublishThat(
|
||||
new PostWasCreated($postId, $title, $content)
|
||||
@@ -81,6 +84,7 @@ class Post extends EventSourcedAggregateRoot
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/** @return CategoryId[] */
|
||||
public function categories(): array
|
||||
{
|
||||
return array_values($this->categories);
|
||||
@@ -119,10 +123,11 @@ class Post extends EventSourcedAggregateRoot
|
||||
}
|
||||
//end-ignore
|
||||
|
||||
/** @return self */
|
||||
public static function reconstitute(EventStream $history):
|
||||
EventSourcedAggregateRoot
|
||||
{
|
||||
$post = new static(new PostId($history->getAggregateId()));
|
||||
$post = new self(new PostId($history->getAggregateId()));
|
||||
|
||||
$post->replay($history);
|
||||
|
||||
|
||||
@@ -7,55 +7,65 @@ use Architecture\CQRS\Domain\DomainEvent;
|
||||
use Architecture\ES\Domain\EventStream;
|
||||
use Predis\Client;
|
||||
use Zumba\JsonSerializer\JsonSerializer;
|
||||
use Safe\DateTimeImmutable;
|
||||
|
||||
/**
|
||||
* @template T of DomainEvent
|
||||
*/
|
||||
//snippet event-store
|
||||
class EventStore
|
||||
{
|
||||
/** @var Client<?string> */
|
||||
private Client $redis;
|
||||
private JsonSerializer $serializer;
|
||||
|
||||
/** @param Client<?string> $redis */
|
||||
public function __construct(Client $redis, JsonSerializer $serializer)
|
||||
{
|
||||
$this->redis = $redis;
|
||||
$this->serializer = $serializer;
|
||||
}
|
||||
|
||||
/** @param EventStream<T> $eventstream */
|
||||
public function append(EventStream $eventstream): void
|
||||
{
|
||||
/** @var DomainEvent */
|
||||
/** @var DomainEvent $event */
|
||||
foreach ($eventstream as $event) {
|
||||
$data = $this->serializer->serialize($event);
|
||||
|
||||
$date = (new \DateTimeImmutable())->format('YmdHis');
|
||||
$date = (new DateTimeImmutable())->format('YmdHis');
|
||||
|
||||
$this->redis->rpush(
|
||||
'events:' . $eventstream->getAggregateId(),
|
||||
$this->serializer->serialize([
|
||||
'type' => get_class($event),
|
||||
'created_on' => $date,
|
||||
'data' => $data
|
||||
])
|
||||
[
|
||||
$this->serializer->serialize([
|
||||
'type' => get_class($event),
|
||||
'created_on' => $date,
|
||||
'data' => $data
|
||||
])
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return EventStream<T> */
|
||||
public function getEventsFor(string $id): EventStream
|
||||
{
|
||||
return $this->fromVersion($id, 0);
|
||||
}
|
||||
|
||||
/** @return EventStream<T> */
|
||||
public function fromVersion(string $id, int $version): EventStream
|
||||
{
|
||||
$serializedEvents = (array) $this->redis->lrange(
|
||||
$serializedEvents = $this->redis->lrange(
|
||||
'events:' . $id,
|
||||
$version,
|
||||
-1
|
||||
);
|
||||
|
||||
/** @var DomainEvent[] */
|
||||
$events = [];
|
||||
|
||||
/** @var string */
|
||||
/** @var string $serializedEvent */
|
||||
foreach ($serializedEvents as $serializedEvent) {
|
||||
$event = (array) $this->serializer->unserialize($serializedEvent);
|
||||
|
||||
@@ -70,7 +80,7 @@ class EventStore
|
||||
|
||||
public function countEventsFor(string $id): int
|
||||
{
|
||||
return (int) $this->redis->llen('events:' . $id);
|
||||
return $this->redis->llen('events:' . $id);
|
||||
}
|
||||
}
|
||||
//end-snippet
|
||||
|
||||
@@ -4,17 +4,21 @@ namespace Architecture\ES\Infrastructure;
|
||||
|
||||
use Architecture\CQRS\Domain\AggregateRoot;
|
||||
|
||||
/** @template T of AggregateRoot */
|
||||
class Snapshot
|
||||
{
|
||||
/** @phpstan-var T */
|
||||
private AggregateRoot $aggregate;
|
||||
private int $version;
|
||||
|
||||
/** @phpstan-param T $aggregate */
|
||||
public function __construct(AggregateRoot $aggregate, int $version)
|
||||
{
|
||||
$this->aggregate = $aggregate;
|
||||
$this->version = $version;
|
||||
}
|
||||
|
||||
/** @return T */
|
||||
public function aggregate(): AggregateRoot
|
||||
{
|
||||
return $this->aggregate;
|
||||
|
||||
@@ -7,18 +7,24 @@ use Architecture\CQRS\Domain\AggregateRoot;
|
||||
use Predis\Client;
|
||||
use Zumba\JsonSerializer\JsonSerializer;
|
||||
|
||||
/**
|
||||
* @template T of AggregateRoot
|
||||
*/
|
||||
//snippet snapshot-repository
|
||||
class SnapshotRepository
|
||||
{
|
||||
/** @var Client<?string> */
|
||||
private Client $redis;
|
||||
private JsonSerializer $serializer;
|
||||
|
||||
/** @param Client<?string> $redis */
|
||||
public function __construct(Client $redis, JsonSerializer $serializer)
|
||||
{
|
||||
$this->redis = $redis;
|
||||
$this->serializer = $serializer;
|
||||
}
|
||||
|
||||
/** @phpstan-return Snapshot<T>|null */
|
||||
public function byId(string $id): ?Snapshot
|
||||
{
|
||||
$key = 'snapshots:' . $id;
|
||||
@@ -31,10 +37,9 @@ class SnapshotRepository
|
||||
}
|
||||
|
||||
$metadata = (array) $this->serializer->unserialize($data);
|
||||
|
||||
|
||||
$snapshot = (array) $metadata['snapshot'];
|
||||
|
||||
/** @var AggregateRoot */
|
||||
$aggregate = $this->serializer->unserialize(
|
||||
(string) $snapshot['data']
|
||||
);
|
||||
@@ -45,24 +50,25 @@ class SnapshotRepository
|
||||
);
|
||||
}
|
||||
|
||||
/** @phpstan-param Snapshot<T> $snapshot */
|
||||
public function save(string $id, Snapshot $snapshot): void
|
||||
{
|
||||
$key = 'snapshots:' . $id;
|
||||
$aggregate = $snapshot->aggregate();
|
||||
|
||||
$snapshot = [
|
||||
'version' => $snapshot->version(),
|
||||
'snapshot' => [
|
||||
'type' => get_class($aggregate),
|
||||
'data' => $this->serializer->serialize(
|
||||
$aggregate
|
||||
)
|
||||
]
|
||||
];
|
||||
|
||||
$this->redis->set(
|
||||
$key,
|
||||
$this->serializer->serialize($snapshot)
|
||||
$this->serializer->serialize(
|
||||
[
|
||||
'version' => $snapshot->version(),
|
||||
'snapshot' => [
|
||||
'type' => get_class($aggregate),
|
||||
'data' => $this->serializer->serialize(
|
||||
$aggregate
|
||||
)
|
||||
]
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,15 @@ use Architecture\ES\Infrastructure\EventStore;
|
||||
//snippet event-store-post-repository
|
||||
class EventStorePostRepository implements PostRepository
|
||||
{
|
||||
/** @var EventStore<PostEvents> */
|
||||
private EventStore $eventStore;
|
||||
/** @var Projector<PostEvents> */
|
||||
private Projector $projector;
|
||||
|
||||
/**
|
||||
* @param EventStore<PostEvents> $eventStore
|
||||
* @param Projector<PostEvents> $projector
|
||||
*/
|
||||
public function __construct(EventStore $eventStore, Projector $projector)
|
||||
{
|
||||
$this->eventStore = $eventStore;
|
||||
@@ -33,7 +39,6 @@ class EventStorePostRepository implements PostRepository
|
||||
|
||||
public function byId(PostId $id): Post
|
||||
{
|
||||
/** @var Post */
|
||||
return Post::reconstitute(
|
||||
$this->eventStore->getEventsFor($id->id())
|
||||
);
|
||||
|
||||
@@ -13,10 +13,18 @@ use Architecture\ES\Infrastructure\SnapshotRepository;
|
||||
|
||||
class EventStorePostRepository implements PostRepository
|
||||
{
|
||||
private SnapshotRepository $snapshotRepository;
|
||||
private EventStore $eventStore;
|
||||
/** @var Projector<PostEvents> */
|
||||
private Projector $projector;
|
||||
/** @var EventStore<PostEvents> */
|
||||
private EventStore $eventStore;
|
||||
/** @var SnapshotRepository<Post> */
|
||||
private SnapshotRepository $snapshotRepository;
|
||||
|
||||
/**
|
||||
* @param SnapshotRepository<Post> $snapshotRepository
|
||||
* @param EventStore<PostEvents> $eventStore
|
||||
* @param Projector<PostEvents> $projector
|
||||
*/
|
||||
public function __construct(
|
||||
SnapshotRepository $snapshotRepository,
|
||||
EventStore $eventStore,
|
||||
|
||||
@@ -35,8 +35,8 @@ final class SignUpHandler
|
||||
|
||||
$authorId = AuthorId::fromString($command->authorId());
|
||||
$email = new EmailAddress($command->email());
|
||||
$website = $command->website() ? new Website($command->website()) : null;
|
||||
$birthDate = $command->birthDate() ? new BirthDate($command->birthDate()) : null;
|
||||
$website = null !== $command->website() ? new Website($command->website()) : null;
|
||||
$birthDate = null !== $command->birthDate() ? new BirthDate($command->birthDate()) : null;
|
||||
|
||||
$this->authors->save(
|
||||
Author::signUp(
|
||||
|
||||
@@ -14,12 +14,13 @@ final class PostCheep
|
||||
private string $authorId;
|
||||
private string $message;
|
||||
|
||||
/** @param array{author_id: string, cheep_id: string, message: string} $array */
|
||||
public static function fromArray(array $array): self
|
||||
{
|
||||
return new static(
|
||||
(string) $array['author_id'],
|
||||
(string) $array['cheep_id'],
|
||||
(string) $array['message'],
|
||||
$array['author_id'],
|
||||
$array['cheep_id'],
|
||||
$array['message'],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace Cheeper\DomainModel\Author;
|
||||
|
||||
use Assert\Assertion;
|
||||
use function Functional\filter;
|
||||
|
||||
class Author
|
||||
@@ -65,7 +64,7 @@ class Author
|
||||
|
||||
private function setName(?string $name): void
|
||||
{
|
||||
if ($name !== null && empty($name)) {
|
||||
if (null !== $name && '' === $name) {
|
||||
throw new \InvalidArgumentException('Name cannot be empty');
|
||||
}
|
||||
|
||||
@@ -74,7 +73,7 @@ class Author
|
||||
|
||||
private function setBiography(?string $biography): void
|
||||
{
|
||||
if ($biography !== null && empty($biography)) {
|
||||
if ($biography !== null && '' === $biography) {
|
||||
throw new \InvalidArgumentException('Biography cannot be empty');
|
||||
}
|
||||
|
||||
@@ -83,7 +82,7 @@ class Author
|
||||
|
||||
private function setLocation(?string $location): void
|
||||
{
|
||||
if ($location !== null && empty($location)) {
|
||||
if ($location !== null && '' === $location) {
|
||||
throw new \InvalidArgumentException('Location cannot be empty');
|
||||
}
|
||||
|
||||
@@ -146,6 +145,7 @@ class Author
|
||||
$this->following[] = $followed;
|
||||
}
|
||||
|
||||
/** @return AuthorId[] */
|
||||
public function following(): array
|
||||
{
|
||||
return $this->following;
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Cheeper\DomainModel\Author;
|
||||
|
||||
use RuntimeException;
|
||||
use function Safe\sprintf;
|
||||
|
||||
final class AuthorAlreadyExists extends RuntimeException
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Cheeper\DomainModel\Author;
|
||||
|
||||
use RuntimeException;
|
||||
use function Safe\sprintf;
|
||||
|
||||
final class AuthorDoesNotExist extends RuntimeException
|
||||
{
|
||||
|
||||
@@ -4,13 +4,14 @@ declare(strict_types=1);
|
||||
|
||||
namespace Cheeper\DomainModel\Author;
|
||||
|
||||
use Assert\Assertion;
|
||||
use Cheeper\DomainModel\Common\ValueObject;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Safe\DateTimeImmutable;
|
||||
use Safe\Exceptions\DatetimeException;
|
||||
|
||||
final class BirthDate extends ValueObject
|
||||
{
|
||||
private DateTimeImmutable $date;
|
||||
private DateTimeInterface $date;
|
||||
|
||||
public function __construct(string $date)
|
||||
{
|
||||
@@ -24,12 +25,10 @@ final class BirthDate extends ValueObject
|
||||
|
||||
private function setDate(string $date): void
|
||||
{
|
||||
$date = DateTimeImmutable::createFromFormat('Y-m-d', $date);
|
||||
|
||||
if (!$date) {
|
||||
throw new \InvalidArgumentException("'$date' is not a valid datetime (Y-m-d formatted).");
|
||||
try {
|
||||
$this->date = DateTimeImmutable::createFromFormat('Y-m-d', $date);
|
||||
} catch (DatetimeException $exception) {
|
||||
throw new \InvalidArgumentException("'$date' is not a valid datetime (Y-m-d formatted).", 0, $exception);
|
||||
}
|
||||
|
||||
$this->date = $date;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Cheeper\DomainModel\Author;
|
||||
|
||||
use Cheeper\DomainModel\Common\ValueObject;
|
||||
use function Safe\sprintf;
|
||||
|
||||
final class EmailAddress extends ValueObject
|
||||
{
|
||||
|
||||
@@ -40,7 +40,7 @@ final class UserName extends ValueObject
|
||||
|
||||
private function assertNotEmpty(string $userName): void
|
||||
{
|
||||
if (empty($userName)) {
|
||||
if ('' === $userName) {
|
||||
throw new \InvalidArgumentException("Username cannot be empty");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Cheeper\DomainModel\Cheep;
|
||||
|
||||
use Cheeper\DomainModel\Author\AuthorId;
|
||||
use Safe\DateTimeImmutable;
|
||||
|
||||
class Cheep
|
||||
{
|
||||
@@ -15,12 +16,12 @@ class Cheep
|
||||
|
||||
public static function compose(AuthorId $authorId, CheepId $cheepId, CheepMessage $cheepMessage): self
|
||||
{
|
||||
return new static(
|
||||
return new self(
|
||||
$authorId,
|
||||
$cheepId,
|
||||
$cheepMessage,
|
||||
new CheepDate(
|
||||
(new \DateTimeImmutable('now'))->format('Y-m-d')
|
||||
(new DateTimeImmutable('now'))->format('Y-m-d')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace Cheeper\DomainModel\Cheep;
|
||||
|
||||
use Assert\Assertion;
|
||||
use Cheeper\DomainModel\Common\ValueObject;
|
||||
use DateTimeImmutable;
|
||||
use InvalidArgumentException;
|
||||
use Safe\DateTimeImmutable;
|
||||
use Safe\Exceptions\DatetimeException;
|
||||
|
||||
final class CheepDate extends ValueObject
|
||||
{
|
||||
@@ -24,12 +25,10 @@ final class CheepDate extends ValueObject
|
||||
|
||||
private function setDate(string $date): void
|
||||
{
|
||||
$date = DateTimeImmutable::createFromFormat('Y-m-d', $date);
|
||||
|
||||
if (!$date) {
|
||||
throw new \InvalidArgumentException("'$date' is not a valid datetime (Y-m-d formatted).");
|
||||
try {
|
||||
$this->date = DateTimeImmutable::createFromFormat('Y-m-d', $date);
|
||||
} catch (DatetimeException $exception) {
|
||||
throw new InvalidArgumentException("'$date' is not a valid datetime (Y-m-d formatted).");
|
||||
}
|
||||
|
||||
$this->date = $date;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ abstract class UuidBasedIdentity extends ValueObject
|
||||
protected string $id;
|
||||
protected string $idAsString;
|
||||
|
||||
private function __construct(string $id)
|
||||
final private function __construct(string $id)
|
||||
{
|
||||
$this->id = $this->idAsString = $id;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ final class DoctrineOrmAuthors implements Authors
|
||||
'authorId.id' => Uuid::fromString($authorId->id())
|
||||
]);
|
||||
|
||||
if (!$author) {
|
||||
if (null === $author) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ final class DoctrineOrmAuthors implements Authors
|
||||
->getRepository(Author::class)
|
||||
->findOneBy(['userName.userName' => $userName->userName()]);
|
||||
|
||||
if (!$author) {
|
||||
if (null === $author) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
23
src/CheeperCommandBus/Simplest/SignUpController.php
Normal file
23
src/CheeperCommandBus/Simplest/SignUpController.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CheeperCommandBus\Simplest;
|
||||
|
||||
use Cheeper\Application\Command\Cheep\PostCheep;
|
||||
use Cheeper\Application\Command\Cheep\PostCheepHandler;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class SignUpController extends AbstractController
|
||||
{
|
||||
private PostCheepHandler $postCheepHandler;
|
||||
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
// $command = PostCheep::fromArray(['test' => 'test']);
|
||||
|
||||
return new Response();
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,7 @@ final class SignUpController extends AbstractController
|
||||
//end-ignore
|
||||
}
|
||||
|
||||
/** @param ConstraintViolationListInterface<ConstraintViolationInterface> $errors */
|
||||
private function toJson(ConstraintViolationListInterface $errors): string
|
||||
{
|
||||
$json = [];
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace CheeperHexagonal;
|
||||
|
||||
use CheeperLayered\Authors;
|
||||
use CheeperLayered\Author;
|
||||
use function Safe\sprintf;
|
||||
|
||||
//snippet author-service
|
||||
class AuthorService
|
||||
@@ -22,10 +23,12 @@ class AuthorService
|
||||
?string $bio
|
||||
): Author
|
||||
{
|
||||
if (!$author = $this->authors->byId($id)) {
|
||||
$author = $this->authors->byId($id);
|
||||
|
||||
if (null === $author) {
|
||||
throw new \RuntimeException(sprintf('%s author not found', $username));
|
||||
}
|
||||
|
||||
|
||||
$author->setUsername($username);
|
||||
$author->setWebsite($website);
|
||||
$author->setBio($bio);
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace CheeperHexagonal;
|
||||
use CheeperLayered\Authors;
|
||||
use CheeperLayered\Cheeps;
|
||||
use CheeperLayered\Cheep;
|
||||
use function Safe\sprintf;
|
||||
|
||||
//snippet cheep-service
|
||||
class CheepService
|
||||
@@ -20,10 +21,12 @@ class CheepService
|
||||
|
||||
public function postCheep(string $username, string $message): Cheep
|
||||
{
|
||||
if (!$author = $this->authors->byUsername($username)) {
|
||||
$author = $this->authors->byUsername($username);
|
||||
|
||||
if (null === $author) {
|
||||
throw new \RuntimeException(sprintf('%s username not found', $username));
|
||||
}
|
||||
|
||||
|
||||
$cheep = $author->compose($message);
|
||||
|
||||
$this->cheeps->add($cheep);
|
||||
|
||||
@@ -10,7 +10,7 @@ class Post
|
||||
|
||||
public static function writeNewFrom(string $title, string $content): self
|
||||
{
|
||||
return new static($title, $content);
|
||||
return new self($title, $content);
|
||||
}
|
||||
|
||||
private function __construct(string $title, string $content)
|
||||
@@ -21,7 +21,7 @@ class Post
|
||||
|
||||
private function setTitle(string $title): void
|
||||
{
|
||||
if (empty($title)) {
|
||||
if ('' === $title) {
|
||||
throw new \RuntimeException('Title cannot be empty');
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ class Post
|
||||
|
||||
private function setContent(string $content): void
|
||||
{
|
||||
if (empty($content)) {
|
||||
if ('' === $content) {
|
||||
throw new \RuntimeException('Content cannot be empty');
|
||||
}
|
||||
|
||||
|
||||
@@ -59,4 +59,4 @@ SQL, mysqli_real_escape_string($link, $timeline_username)));
|
||||
</body>
|
||||
</html>
|
||||
<?php mysqli_close($link); ?>
|
||||
<!--end-snippet-->
|
||||
<!--end-snippet-->
|
||||
|
||||
24
symfony.lock
24
symfony.lock
@@ -260,6 +260,27 @@
|
||||
"phpspec/prophecy": {
|
||||
"version": "v1.10.2"
|
||||
},
|
||||
"phpstan/extension-installer": {
|
||||
"version": "1.0.4"
|
||||
},
|
||||
"phpstan/phpstan": {
|
||||
"version": "0.12.30"
|
||||
},
|
||||
"phpstan/phpstan-beberlei-assert": {
|
||||
"version": "0.12.2"
|
||||
},
|
||||
"phpstan/phpstan-deprecation-rules": {
|
||||
"version": "0.12.4"
|
||||
},
|
||||
"phpstan/phpstan-doctrine": {
|
||||
"version": "0.12.16"
|
||||
},
|
||||
"phpstan/phpstan-strict-rules": {
|
||||
"version": "0.12.2"
|
||||
},
|
||||
"phpstan/phpstan-symfony": {
|
||||
"version": "0.12.6"
|
||||
},
|
||||
"phpunit/php-code-coverage": {
|
||||
"version": "8.0.1"
|
||||
},
|
||||
@@ -713,6 +734,9 @@
|
||||
"symfony/yaml": {
|
||||
"version": "v5.0.4"
|
||||
},
|
||||
"thecodingmachine/phpstan-safe-rule": {
|
||||
"version": "v1.0.0"
|
||||
},
|
||||
"thecodingmachine/safe": {
|
||||
"version": "v1.1"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Architecture\ES\Domain;
|
||||
|
||||
use Cheeper\DomainModel\DomainEvent;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class EventStreamTest extends TestCase
|
||||
@@ -11,7 +12,13 @@ class EventStreamTest extends TestCase
|
||||
*/
|
||||
public function itShouldBuildAnEventStream(): void
|
||||
{
|
||||
$stream = new EventStream('irrelevant', [1, 2, 3]);
|
||||
$domainEvents = [
|
||||
new class implements DomainEvent{},
|
||||
new class implements DomainEvent{},
|
||||
new class implements DomainEvent{},
|
||||
];
|
||||
|
||||
$stream = new EventStream('irrelevant', $domainEvents);
|
||||
|
||||
$collected = [];
|
||||
foreach ($stream as $key => $event) {
|
||||
@@ -19,6 +26,6 @@ class EventStreamTest extends TestCase
|
||||
}
|
||||
|
||||
$this->assertEquals('irrelevant', $stream->getAggregateId());
|
||||
$this->assertEquals($collected, [1, 2, 3]);
|
||||
$this->assertEquals($collected, $domainEvents);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,27 +24,27 @@ final class PostCheepHandlerTest extends TestCase
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function throwsExceptionWhenAuthorIdIsNotString(): void
|
||||
public function throwsExceptionWhenAuthorIdIsNotUuid(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
$this->postNewCheep(1, Uuid::uuid4()->toString(), 'test');
|
||||
$this->postNewCheep("1", Uuid::uuid4()->toString(), 'test');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function throwsExceptionWhenCheepIdIsNotString(): void
|
||||
public function throwsExceptionWhenCheepIdIsNotUuid(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
$this->postNewCheep(Uuid::uuid4()->toString(), 1, 'test');
|
||||
$this->postNewCheep(Uuid::uuid4()->toString(), "1", 'test');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function throwsExceptionWhenCheepMessageIsNotString(): void
|
||||
public function throwsExceptionWhenCheepMessageIsEmpty(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
$this->postNewCheep(Uuid::uuid4()->toString(), Uuid::uuid4()->toString(), 0);
|
||||
$this->postNewCheep(Uuid::uuid4()->toString(), Uuid::uuid4()->toString(), "");
|
||||
}
|
||||
|
||||
/** @test */
|
||||
|
||||
@@ -3,17 +3,19 @@
|
||||
namespace CheeperLayered;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Twig\Environment;
|
||||
use Twig\Loader\FilesystemLoader;
|
||||
|
||||
class TemplateTest extends TestCase
|
||||
{
|
||||
private static $TEMPLATES_PATH = 'src/CheeperLayered/templates';
|
||||
private static string $TEMPLATES_PATH = __DIR__ . '/../../src/CheeperLayered/templates';
|
||||
|
||||
private $twig;
|
||||
private Environment $twig;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$loader = new \Twig\Loader\FilesystemLoader(self::$TEMPLATES_PATH);
|
||||
$this->twig = new \Twig\Environment($loader);
|
||||
$loader = new FilesystemLoader(self::$TEMPLATES_PATH);
|
||||
$this->twig = new Environment($loader);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
11
tests/console-application.php
Normal file
11
tests/console-application.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Kernel;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
|
||||
require dirname(__DIR__). '/config/bootstrap.php';
|
||||
|
||||
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
|
||||
return new Application($kernel);
|
||||
Reference in New Issue
Block a user