diff --git a/composer.json b/composer.json index 407fda2cb..87514ff3b 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "require-dev": { "ext-pdo_sqlite": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", "doctrine/orm": "^2.18.0 || ^3.0.0", - "infection/infection": "^0.31.9", + "infection/infection": "^0.32.0", "league/commonmark": "^2.6.1", "patchlevel/coding-standard": "^1.3.0", "patchlevel/event-sourcing-phpstan-extension": "dev-event-sourcing-4.0", diff --git a/composer.lock b/composer.lock index c37492e64..0b288a230 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "2be0026ad850f22ca2b2e6b4363fbc1d", + "content-hash": "0c3c161d87cdce3d1923690220fd6394", "packages": [ { "name": "brick/math", @@ -68,16 +68,16 @@ }, { "name": "doctrine/dbal", - "version": "4.4.0", + "version": "4.4.1", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "e8c5163fbec0f34e357431bd1e5fc4056cdf4fdc" + "reference": "3d544473fb93f5c25b483ea4f4ce99f8c4d9d44c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/e8c5163fbec0f34e357431bd1e5fc4056cdf4fdc", - "reference": "e8c5163fbec0f34e357431bd1e5fc4056cdf4fdc", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/3d544473fb93f5c25b483ea4f4ce99f8c4d9d44c", + "reference": "3d544473fb93f5c25b483ea4f4ce99f8c4d9d44c", "shasum": "" }, "require": { @@ -154,7 +154,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/4.4.0" + "source": "https://github.com/doctrine/dbal/tree/4.4.1" }, "funding": [ { @@ -170,7 +170,7 @@ "type": "tidelift" } ], - "time": "2025-11-29T12:17:09+00:00" + "time": "2025-12-04T10:11:03+00:00" }, { "name": "doctrine/deprecations", @@ -917,20 +917,20 @@ }, { "name": "ramsey/uuid", - "version": "4.9.1", + "version": "4.9.2", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440" + "reference": "8429c78ca35a09f27565311b98101e2826affde0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/81f941f6f729b1e3ceea61d9d014f8b6c6800440", - "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", + "reference": "8429c78ca35a09f27565311b98101e2826affde0", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", + "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -989,53 +989,45 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.9.1" + "source": "https://github.com/ramsey/uuid/tree/4.9.2" }, - "time": "2025-09-04T20:59:21+00:00" + "time": "2025-12-14T04:43:48+00:00" }, { "name": "symfony/console", - "version": "v7.4.0", + "version": "v8.0.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8" + "reference": "6145b304a5c1ea0bdbd0b04d297a5864f9a7d587" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8", - "reference": "0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8", + "url": "https://api.github.com/repos/symfony/console/zipball/6145b304a5c1ea0bdbd0b04d297a5864f9a7d587", + "reference": "6145b304a5c1ea0bdbd0b04d297a5864f9a7d587", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0", + "php": ">=8.4", + "symfony/polyfill-mbstring": "^1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^7.2|^8.0" - }, - "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/dotenv": "<6.4", - "symfony/event-dispatcher": "<6.4", - "symfony/lock": "<6.4", - "symfony/process": "<6.4" + "symfony/string": "^7.4|^8.0" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/lock": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -1069,7 +1061,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.0" + "source": "https://github.com/symfony/console/tree/v8.0.3" }, "funding": [ { @@ -1089,7 +1081,7 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2025-12-23T14:52:06+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1321,23 +1313,23 @@ }, { "name": "symfony/finder", - "version": "v7.4.0", + "version": "v8.0.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "340b9ed7320570f319028a2cbec46d40535e94bd" + "reference": "dd3a2953570a283a2ba4e17063bb98c734cf5b12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/340b9ed7320570f319028a2cbec46d40535e94bd", - "reference": "340b9ed7320570f319028a2cbec46d40535e94bd", + "url": "https://api.github.com/repos/symfony/finder/zipball/dd3a2953570a283a2ba4e17063bb98c734cf5b12", + "reference": "dd3a2953570a283a2ba4e17063bb98c734cf5b12", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.4" }, "require-dev": { - "symfony/filesystem": "^6.4|^7.0|^8.0" + "symfony/filesystem": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -1365,7 +1357,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.0" + "source": "https://github.com/symfony/finder/tree/v8.0.3" }, "funding": [ { @@ -1385,7 +1377,7 @@ "type": "tidelift" } ], - "time": "2025-11-05T05:42:40+00:00" + "time": "2025-12-23T14:52:06+00:00" }, { "name": "symfony/polyfill-ctype", @@ -1877,16 +1869,16 @@ }, { "name": "symfony/string", - "version": "v8.0.0", + "version": "v8.0.1", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f929eccf09531078c243df72398560e32fa4cf4f" + "reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f929eccf09531078c243df72398560e32fa4cf4f", - "reference": "f929eccf09531078c243df72398560e32fa4cf4f", + "url": "https://api.github.com/repos/symfony/string/zipball/ba65a969ac918ce0cc3edfac6cdde847eba231dc", + "reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc", "shasum": "" }, "require": { @@ -1943,7 +1935,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v8.0.0" + "source": "https://github.com/symfony/string/tree/v8.0.1" }, "funding": [ { @@ -1963,20 +1955,20 @@ "type": "tidelift" } ], - "time": "2025-09-11T14:37:55+00:00" + "time": "2025-12-01T09:13:36+00:00" }, { "name": "symfony/type-info", - "version": "v8.0.0", + "version": "v8.0.1", "source": { "type": "git", "url": "https://github.com/symfony/type-info.git", - "reference": "9de828eae6aeb33806f8f2fec161a8f8e79338d0" + "reference": "bb091cec1f70383538c7d000699781813f8d1a6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/9de828eae6aeb33806f8f2fec161a8f8e79338d0", - "reference": "9de828eae6aeb33806f8f2fec161a8f8e79338d0", + "url": "https://api.github.com/repos/symfony/type-info/zipball/bb091cec1f70383538c7d000699781813f8d1a6a", + "reference": "bb091cec1f70383538c7d000699781813f8d1a6a", "shasum": "" }, "require": { @@ -2025,7 +2017,7 @@ "type" ], "support": { - "source": "https://github.com/symfony/type-info/tree/v8.0.0" + "source": "https://github.com/symfony/type-info/tree/v8.0.1" }, "funding": [ { @@ -2045,7 +2037,7 @@ "type": "tidelift" } ], - "time": "2025-11-08T16:30:39+00:00" + "time": "2025-12-05T14:08:45+00:00" }, { "name": "symfony/var-exporter", @@ -2612,16 +2604,16 @@ }, { "name": "doctrine/collections", - "version": "2.4.0", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "9acfeea2e8666536edff3d77c531261c63680160" + "reference": "6108e0cd57d7ef125fb84696346a68860403a25d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/9acfeea2e8666536edff3d77c531261c63680160", - "reference": "9acfeea2e8666536edff3d77c531261c63680160", + "url": "https://api.github.com/repos/doctrine/collections/zipball/6108e0cd57d7ef125fb84696346a68860403a25d", + "reference": "6108e0cd57d7ef125fb84696346a68860403a25d", "shasum": "" }, "require": { @@ -2678,7 +2670,7 @@ ], "support": { "issues": "https://github.com/doctrine/collections/issues", - "source": "https://github.com/doctrine/collections/tree/2.4.0" + "source": "https://github.com/doctrine/collections/tree/2.5.0" }, "funding": [ { @@ -2694,7 +2686,7 @@ "type": "tidelift" } ], - "time": "2025-10-25T09:18:13+00:00" + "time": "2026-01-07T17:26:56+00:00" }, { "name": "doctrine/inflector", @@ -2788,30 +2780,29 @@ }, { "name": "doctrine/instantiator", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/23da848e1a2308728fe5fdddabf4be17ff9720c7", + "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^8.4" }, "require-dev": { - "doctrine/coding-standard": "^11", + "doctrine/coding-standard": "^14", "ext-pdo": "*", "ext-phar": "*", "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.9.4", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^5.4" + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5.58" }, "type": "library", "autoload": { @@ -2838,7 +2829,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + "source": "https://github.com/doctrine/instantiator/tree/2.1.0" }, "funding": [ { @@ -2854,7 +2845,7 @@ "type": "tidelift" } ], - "time": "2022-12-30T00:23:10+00:00" + "time": "2026-01-05T06:47:08+00:00" }, { "name": "doctrine/lexer", @@ -2935,16 +2926,16 @@ }, { "name": "doctrine/orm", - "version": "3.5.8", + "version": "3.6.0", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "78dd074266e8b47a83bcf60ab5fe06c91a639168" + "reference": "d4e9276e79602b1eb4c4029c6c999b0d93478e2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/78dd074266e8b47a83bcf60ab5fe06c91a639168", - "reference": "78dd074266e8b47a83bcf60ab5fe06c91a639168", + "url": "https://api.github.com/repos/doctrine/orm/zipball/d4e9276e79602b1eb4c4029c6c999b0d93478e2f", + "reference": "d4e9276e79602b1eb4c4029c6c999b0d93478e2f", "shasum": "" }, "require": { @@ -3017,9 +3008,9 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/3.5.8" + "source": "https://github.com/doctrine/orm/tree/3.6.0" }, - "time": "2025-11-29T23:11:02+00:00" + "time": "2025-12-19T20:36:14+00:00" }, { "name": "doctrine/persistence", @@ -3354,16 +3345,16 @@ }, { "name": "infection/infection", - "version": "0.31.9", + "version": "0.32.2", "source": { "type": "git", "url": "https://github.com/infection/infection.git", - "reference": "f9628fcd7f76eadf24726e57a81937c42458232b" + "reference": "df90353784ab0505f07502770f59f8fabef24d8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/infection/infection/zipball/f9628fcd7f76eadf24726e57a81937c42458232b", - "reference": "f9628fcd7f76eadf24726e57a81937c42458232b", + "url": "https://api.github.com/repos/infection/infection/zipball/df90353784ab0505f07502770f59f8fabef24d8c", + "reference": "df90353784ab0505f07502770f59f8fabef24d8c", "shasum": "" }, "require": { @@ -3380,20 +3371,22 @@ "infection/include-interceptor": "^0.2.5", "infection/mutator": "^0.4", "justinrainbow/json-schema": "^6.0", - "nikic/php-parser": "^5.3", + "nikic/php-parser": "^5.6.2", "ondram/ci-detector": "^4.1.0", "php": "^8.2", + "psr/log": "^2.0 || ^3.0", "sanmai/di-container": "^0.1.4", "sanmai/duoclock": "^0.1.0", "sanmai/later": "^0.1.7", "sanmai/pipeline": "^7.0", "sebastian/diff": "^4.0 || ^5.0 || ^6.0 || ^7.0", - "symfony/console": "^6.4 || ^7.0", - "symfony/filesystem": "^6.4 || ^7.0", - "symfony/finder": "^6.4 || ^7.0", - "symfony/process": "^6.4 || ^7.0", + "symfony/console": "^6.4 || ^7.0 || ^8.0", + "symfony/filesystem": "^6.4 || ^7.0 || ^8.0", + "symfony/finder": "^6.4 || ^7.0 || ^8.0", + "symfony/polyfill-php85": "^1.33", + "symfony/process": "^6.4 || ^7.0 || ^8.0", "thecodingmachine/safe": "^v3.0", - "webmozart/assert": "^1.11" + "webmozart/assert": "^1.11 || ^2.0" }, "conflict": { "antecedent/patchwork": "<2.1.25", @@ -3402,18 +3395,21 @@ "require-dev": { "ext-simplexml": "*", "fidry/makefile": "^1.0", + "fig/log-test": "^1.2", + "phpbench/phpbench": "^1.4", "phpstan/extension-installer": "^1.4", "phpstan/phpstan": "^2.1", "phpstan/phpstan-phpunit": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", "phpstan/phpstan-webmozart-assert": "^2.0", "phpunit/phpunit": "^11.5.27", - "rector/rector": "^2.0", - "shipmonk/dead-code-detector": "^0.12.0", + "rector/rector": "^2.2.4", + "shipmonk/dead-code-detector": "^0.14.0", "shipmonk/name-collision-detector": "^2.1", "sidz/phpstan-rules": "^0.5.1", - "symfony/yaml": "^6.4 || ^7.0", - "thecodingmachine/phpstan-safe-rule": "^1.4" + "symfony/yaml": "^6.4 || ^7.0 || ^8.0", + "thecodingmachine/phpstan-safe-rule": "^1.4", + "webmozarts/strict-phpunit": "^7.15" }, "bin": [ "bin/infection" @@ -3469,7 +3465,7 @@ ], "support": { "issues": "https://github.com/infection/infection/issues", - "source": "https://github.com/infection/infection/tree/0.31.9" + "source": "https://github.com/infection/infection/tree/0.32.2" }, "funding": [ { @@ -3481,7 +3477,7 @@ "type": "open_collective" } ], - "time": "2025-10-27T12:00:54+00:00" + "time": "2026-01-07T13:28:58+00:00" }, { "name": "infection/mutator", @@ -3538,21 +3534,21 @@ }, { "name": "justinrainbow/json-schema", - "version": "6.6.2", + "version": "6.6.4", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "3c25fe750c1599716ef26aa997f7c026cee8c4b7" + "reference": "2eeb75d21cf73211335888e7f5e6fd7440723ec7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/3c25fe750c1599716ef26aa997f7c026cee8c4b7", - "reference": "3c25fe750c1599716ef26aa997f7c026cee8c4b7", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/2eeb75d21cf73211335888e7f5e6fd7440723ec7", + "reference": "2eeb75d21cf73211335888e7f5e6fd7440723ec7", "shasum": "" }, "require": { "ext-json": "*", - "marc-mabe/php-enum": "^4.0", + "marc-mabe/php-enum": "^4.4", "php": "^7.2 || ^8.0" }, "require-dev": { @@ -3607,9 +3603,9 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/6.6.2" + "source": "https://github.com/jsonrainbow/json-schema/tree/6.6.4" }, - "time": "2025-11-28T15:24:03+00:00" + "time": "2025-12-19T15:01:32+00:00" }, { "name": "league/commonmark", @@ -4000,20 +3996,20 @@ }, { "name": "nette/utils", - "version": "v4.0.9", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "505a30ad386daa5211f08a318e47015b501cad30" + "reference": "c99059c0315591f1a0db7ad6002000288ab8dc72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/505a30ad386daa5211f08a318e47015b501cad30", - "reference": "505a30ad386daa5211f08a318e47015b501cad30", + "url": "https://api.github.com/repos/nette/utils/zipball/c99059c0315591f1a0db7ad6002000288ab8dc72", + "reference": "c99059c0315591f1a0db7ad6002000288ab8dc72", "shasum": "" }, "require": { - "php": "8.0 - 8.5" + "php": "8.2 - 8.5" }, "conflict": { "nette/finder": "<3", @@ -4036,7 +4032,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -4083,22 +4079,22 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.9" + "source": "https://github.com/nette/utils/tree/v4.1.1" }, - "time": "2025-10-31T00:45:47+00:00" + "time": "2025-12-22T12:14:32+00:00" }, { "name": "nikic/php-parser", - "version": "v5.6.2", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "3a454ca033b9e06b63282ce19562e892747449bb" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", - "reference": "3a454ca033b9e06b63282ce19562e892747449bb", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { @@ -4141,9 +4137,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "time": "2025-10-21T19:32:17+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { "name": "ondram/ci-detector", @@ -4455,16 +4451,16 @@ }, { "name": "phpat/phpat", - "version": "0.12.0", + "version": "0.12.1", "source": { "type": "git", "url": "https://github.com/carlosas/phpat.git", - "reference": "f1d0eccdba0a6862b7a639cbd020147c35b63763" + "reference": "67b9e179757fbaa8b87e1469a66f103725858ee0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/carlosas/phpat/zipball/f1d0eccdba0a6862b7a639cbd020147c35b63763", - "reference": "f1d0eccdba0a6862b7a639cbd020147c35b63763", + "url": "https://api.github.com/repos/carlosas/phpat/zipball/67b9e179757fbaa8b87e1469a66f103725858ee0", + "reference": "67b9e179757fbaa8b87e1469a66f103725858ee0", "shasum": "" }, "require": { @@ -4506,9 +4502,9 @@ "description": "PHP Architecture Tester", "support": { "issues": "https://github.com/carlosas/phpat/issues", - "source": "https://github.com/carlosas/phpat/tree/0.12.0" + "source": "https://github.com/carlosas/phpat/tree/0.12.1" }, - "time": "2025-09-11T19:00:27+00:00" + "time": "2025-12-25T17:53:07+00:00" }, { "name": "phpbench/container", @@ -4708,11 +4704,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.32", + "version": "2.1.33", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e126cad1e30a99b137b8ed75a85a676450ebb227", - "reference": "e126cad1e30a99b137b8ed75a85a676450ebb227", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9e800e6bee7d5bd02784d4c6069b48032d16224f", + "reference": "9e800e6bee7d5bd02784d4c6069b48032d16224f", "shasum": "" }, "require": { @@ -4757,20 +4753,20 @@ "type": "github" } ], - "time": "2025-11-11T15:18:17+00:00" + "time": "2025-12-05T10:24:31+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "2.0.8", + "version": "2.0.11", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "2fe9fbeceaf76dd1ebaa7bbbb25e2fb5e59db2fe" + "reference": "5e30669bef866eff70db8b58d72a5c185aa82414" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/2fe9fbeceaf76dd1ebaa7bbbb25e2fb5e59db2fe", - "reference": "2fe9fbeceaf76dd1ebaa7bbbb25e2fb5e59db2fe", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/5e30669bef866eff70db8b58d72a5c185aa82414", + "reference": "5e30669bef866eff70db8b58d72a5c185aa82414", "shasum": "" }, "require": { @@ -4808,41 +4804,41 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.8" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.11" }, - "time": "2025-11-11T07:55:22+00:00" + "time": "2025-12-19T09:05:35+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "11.0.11", + "version": "11.0.12", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4" + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", - "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2c1ed04922802c15e1de5d7447b4856de949cf56", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^5.4.0", + "nikic/php-parser": "^5.7.0", "php": ">=8.2", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-text-template": "^4.0.1", "sebastian/code-unit-reverse-lookup": "^4.0.1", "sebastian/complexity": "^4.0.1", - "sebastian/environment": "^7.2.0", + "sebastian/environment": "^7.2.1", "sebastian/lines-of-code": "^3.0.1", "sebastian/version": "^5.0.2", - "theseer/tokenizer": "^1.2.3" + "theseer/tokenizer": "^1.3.1" }, "require-dev": { - "phpunit/phpunit": "^11.5.2" + "phpunit/phpunit": "^11.5.46" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -4880,7 +4876,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.11" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.12" }, "funding": [ { @@ -4900,7 +4896,7 @@ "type": "tidelift" } ], - "time": "2025-08-27T14:37:49+00:00" + "time": "2025-12-24T07:01:01+00:00" }, { "name": "phpunit/php-file-iterator", @@ -5149,16 +5145,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.45", + "version": "11.5.46", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "faf5fff4fb9beb290affa53f812b05380819c51a" + "reference": "75dfe79a2aa30085b7132bb84377c24062193f33" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/faf5fff4fb9beb290affa53f812b05380819c51a", - "reference": "faf5fff4fb9beb290affa53f812b05380819c51a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/75dfe79a2aa30085b7132bb84377c24062193f33", + "reference": "75dfe79a2aa30085b7132bb84377c24062193f33", "shasum": "" }, "require": { @@ -5230,7 +5226,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.45" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.46" }, "funding": [ { @@ -5254,20 +5250,20 @@ "type": "tidelift" } ], - "time": "2025-12-01T07:38:43+00:00" + "time": "2025-12-06T08:01:15+00:00" }, { "name": "sanmai/di-container", - "version": "0.1.5", + "version": "0.1.8", "source": { "type": "git", "url": "https://github.com/sanmai/di-container.git", - "reference": "355534ad7970fc7dab4211ecaf2da5c546855ee8" + "reference": "f7ea6a00692608f785fc0eabb1c54f5349d973e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sanmai/di-container/zipball/355534ad7970fc7dab4211ecaf2da5c546855ee8", - "reference": "355534ad7970fc7dab4211ecaf2da5c546855ee8", + "url": "https://api.github.com/repos/sanmai/di-container/zipball/f7ea6a00692608f785fc0eabb1c54f5349d973e9", + "reference": "f7ea6a00692608f785fc0eabb1c54f5349d973e9", "shasum": "" }, "require": { @@ -5321,7 +5317,7 @@ ], "support": { "issues": "https://github.com/sanmai/di-container/issues", - "source": "https://github.com/sanmai/di-container/tree/0.1.5" + "source": "https://github.com/sanmai/di-container/tree/0.1.8" }, "funding": [ { @@ -5329,20 +5325,20 @@ "type": "github" } ], - "time": "2025-08-04T09:43:58+00:00" + "time": "2026-01-03T14:10:40+00:00" }, { "name": "sanmai/duoclock", - "version": "0.1.1", + "version": "0.1.3", "source": { "type": "git", "url": "https://github.com/sanmai/DuoClock.git", - "reference": "30aa40092396dc96b68c8e8d49162619574477e2" + "reference": "47461e3ff65b7308635047831a55615652e7be1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sanmai/DuoClock/zipball/30aa40092396dc96b68c8e8d49162619574477e2", - "reference": "30aa40092396dc96b68c8e8d49162619574477e2", + "url": "https://api.github.com/repos/sanmai/DuoClock/zipball/47461e3ff65b7308635047831a55615652e7be1a", + "reference": "47461e3ff65b7308635047831a55615652e7be1a", "shasum": "" }, "require": { @@ -5360,8 +5356,7 @@ "phpstan/extension-installer": "^1.4", "phpstan/phpstan": "^2", "phpunit/phpunit": "^11.5.25", - "sanmai/phpstan-rules": "^0.3.1", - "vimeo/psalm": "^6.12" + "sanmai/phpstan-rules": "^0.3.1" }, "type": "library", "extra": { @@ -5385,7 +5380,7 @@ "description": "PHP time mocking for tests - PSR-20 clock with mockable sleep(), time(), and TimeSpy for PHPUnit testing", "support": { "issues": "https://github.com/sanmai/DuoClock/issues", - "source": "https://github.com/sanmai/DuoClock/tree/0.1.1" + "source": "https://github.com/sanmai/DuoClock/tree/0.1.3" }, "funding": [ { @@ -5393,7 +5388,7 @@ "type": "github" } ], - "time": "2025-07-28T02:17:28+00:00" + "time": "2025-12-26T06:12:34+00:00" }, { "name": "sanmai/later", @@ -5461,16 +5456,16 @@ }, { "name": "sanmai/pipeline", - "version": "7.5", + "version": "7.6", "source": { "type": "git", "url": "https://github.com/sanmai/pipeline.git", - "reference": "c3b87db671ee0bc286860bd13bdb7cfc108b7d7e" + "reference": "f7aeb6e1c9572f366c6035c79d715a2a73eeb1c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sanmai/pipeline/zipball/c3b87db671ee0bc286860bd13bdb7cfc108b7d7e", - "reference": "c3b87db671ee0bc286860bd13bdb7cfc108b7d7e", + "url": "https://api.github.com/repos/sanmai/pipeline/zipball/f7aeb6e1c9572f366c6035c79d715a2a73eeb1c9", + "reference": "f7aeb6e1c9572f366c6035c79d715a2a73eeb1c9", "shasum": "" }, "require": { @@ -5517,7 +5512,7 @@ "description": "General-purpose collections pipeline", "support": { "issues": "https://github.com/sanmai/pipeline/issues", - "source": "https://github.com/sanmai/pipeline/tree/7.5" + "source": "https://github.com/sanmai/pipeline/tree/7.6" }, "funding": [ { @@ -5525,7 +5520,7 @@ "type": "github" } ], - "time": "2025-11-05T10:54:07+00:00" + "time": "2025-12-20T07:22:08+00:00" }, { "name": "sebastian/cli-parser", @@ -6852,25 +6847,25 @@ }, { "name": "symfony/filesystem", - "version": "v7.4.0", + "version": "v8.0.1", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "d551b38811096d0be9c4691d406991b47c0c630a" + "reference": "d937d400b980523dc9ee946bb69972b5e619058d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/d551b38811096d0be9c4691d406991b47c0c630a", - "reference": "d551b38811096d0be9c4691d406991b47c0c630a", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d937d400b980523dc9ee946bb69972b5e619058d", + "reference": "d937d400b980523dc9ee946bb69972b5e619058d", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "symfony/process": "^6.4|^7.0|^8.0" + "symfony/process": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -6898,7 +6893,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.4.0" + "source": "https://github.com/symfony/filesystem/tree/v8.0.1" }, "funding": [ { @@ -6918,20 +6913,20 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2025-12-01T09:13:36+00:00" }, { "name": "symfony/messenger", - "version": "v8.0.0", + "version": "v8.0.3", "source": { "type": "git", "url": "https://github.com/symfony/messenger.git", - "reference": "c37b86c313e26291c5defe9194bcf31cbe630fbc" + "reference": "b56b89aee16ceb623f76c8739ab62202ec198190" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/messenger/zipball/c37b86c313e26291c5defe9194bcf31cbe630fbc", - "reference": "c37b86c313e26291c5defe9194bcf31cbe630fbc", + "url": "https://api.github.com/repos/symfony/messenger/zipball/b56b89aee16ceb623f76c8739ab62202ec198190", + "reference": "b56b89aee16ceb623f76c8739ab62202ec198190", "shasum": "" }, "require": { @@ -6986,7 +6981,7 @@ "description": "Helps applications send and receive messages to/from other applications or via message queues", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/messenger/tree/v8.0.0" + "source": "https://github.com/symfony/messenger/tree/v8.0.3" }, "funding": [ { @@ -7006,7 +7001,7 @@ "type": "tidelift" } ], - "time": "2025-11-06T11:20:17+00:00" + "time": "2025-12-23T14:52:06+00:00" }, { "name": "symfony/options-resolver", @@ -7243,22 +7238,102 @@ ], "time": "2025-06-24T13:30:11+00:00" }, + { + "name": "symfony/polyfill-php85", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-23T16:12:55+00:00" + }, { "name": "symfony/process", - "version": "v7.4.0", + "version": "v8.0.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "7ca8dc2d0dcf4882658313aba8be5d9fd01026c8" + "reference": "0cbbd88ec836f8757641c651bb995335846abb78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/7ca8dc2d0dcf4882658313aba8be5d9fd01026c8", - "reference": "7ca8dc2d0dcf4882658313aba8be5d9fd01026c8", + "url": "https://api.github.com/repos/symfony/process/zipball/0cbbd88ec836f8757641c651bb995335846abb78", + "reference": "0cbbd88ec836f8757641c651bb995335846abb78", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.4" }, "type": "library", "autoload": { @@ -7286,7 +7361,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.4.0" + "source": "https://github.com/symfony/process/tree/v8.0.3" }, "funding": [ { @@ -7306,20 +7381,20 @@ "type": "tidelift" } ], - "time": "2025-10-16T11:21:06+00:00" + "time": "2025-12-19T10:01:18+00:00" }, { "name": "symfony/var-dumper", - "version": "v8.0.0", + "version": "v8.0.3", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "d2a2476c93b58ac5292145e9fac1ff76a21d1ce2" + "reference": "3bc368228532ad538cc216768caa8968be95a8d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/d2a2476c93b58ac5292145e9fac1ff76a21d1ce2", - "reference": "d2a2476c93b58ac5292145e9fac1ff76a21d1ce2", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/3bc368228532ad538cc216768caa8968be95a8d6", + "reference": "3bc368228532ad538cc216768caa8968be95a8d6", "shasum": "" }, "require": { @@ -7373,7 +7448,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v8.0.0" + "source": "https://github.com/symfony/var-dumper/tree/v8.0.3" }, "funding": [ { @@ -7393,7 +7468,7 @@ "type": "tidelift" } ], - "time": "2025-10-28T09:34:19+00:00" + "time": "2025-12-18T11:23:51+00:00" }, { "name": "thecodingmachine/safe", @@ -7586,23 +7661,23 @@ }, { "name": "webmozart/assert", - "version": "1.12.1", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "9be6926d8b485f55b9229203f962b51ed377ba68" + "reference": "bdbabc199a7ba9965484e4725d66170e5711323b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68", - "reference": "9be6926d8b485f55b9229203f962b51ed377ba68", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/bdbabc199a7ba9965484e4725d66170e5711323b", + "reference": "bdbabc199a7ba9965484e4725d66170e5711323b", "shasum": "" }, "require": { "ext-ctype": "*", "ext-date": "*", "ext-filter": "*", - "php": "^7.2 || ^8.0" + "php": "^8.2" }, "suggest": { "ext-intl": "", @@ -7612,7 +7687,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.10-dev" + "dev-feature/2-0": "2.0-dev" } }, "autoload": { @@ -7628,6 +7703,10 @@ { "name": "Bernhard Schussek", "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" } ], "description": "Assertions to validate method input/output with nice error messages.", @@ -7638,9 +7717,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.12.1" + "source": "https://github.com/webmozarts/assert/tree/2.1.1" }, - "time": "2025-10-29T15:56:20+00:00" + "time": "2026-01-08T11:28:40+00:00" }, { "name": "webmozart/glob", diff --git a/docs/getting-started.md b/docs/getting-started.md index 844b9e345..fd8e6ea65 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -163,12 +163,12 @@ use Patchlevel\EventSourcing\Attribute\Projector; use Patchlevel\EventSourcing\Attribute\Setup; use Patchlevel\EventSourcing\Attribute\Subscribe; use Patchlevel\EventSourcing\Attribute\Teardown; -use Patchlevel\EventSourcing\Subscription\Subscriber\SubscriberUtil; -#[Projector('hotel')] +#[Projector(self::TABLE)] final class HotelProjector { - use SubscriberUtil; + // use a const for easier access in the projector & to keep projector id and table name in sync + private const TABLE = 'hotel'; public function __construct( private readonly Connection $db, @@ -178,14 +178,14 @@ final class HotelProjector /** @return list */ public function getHotels(): array { - return $this->db->fetchAllAssociative("SELECT id, name, guests FROM {$this->table()};"); + return $this->db->fetchAllAssociative(sprintf('SELECT id, name, guests FROM %s;'), self::TABLE); } #[Subscribe(HotelCreated::class)] public function handleHotelCreated(HotelCreated $event): void { $this->db->insert( - $this->table(), + self::TABLE, [ 'id' => $event->hotelId->toString(), 'name' => $event->hotelName, @@ -198,7 +198,7 @@ final class HotelProjector public function handleGuestIsCheckedIn(GuestIsCheckedIn $event): void { $this->db->executeStatement( - "UPDATE {$this->table()} SET guests = guests + 1 WHERE id = ?;", + sprintf('UPDATE %s SET guests = guests + 1 WHERE id = ?;', self::TABLE), [$event->hotelId->toString()], ); } @@ -207,7 +207,7 @@ final class HotelProjector public function handleGuestIsCheckedOut(GuestIsCheckedOut $event): void { $this->db->executeStatement( - "UPDATE {$this->table()} SET guests = guests - 1 WHERE id = ?;", + sprintf('UPDATE %s SET guests = guests - 1 WHERE id = ?;', self::TABLE), [$event->hotelId->toString()], ); } @@ -215,18 +215,13 @@ final class HotelProjector #[Setup] public function create(): void { - $this->db->executeStatement("CREATE TABLE IF NOT EXISTS {$this->table()} (id VARCHAR PRIMARY KEY, name VARCHAR, guests INTEGER);"); + $this->db->executeStatement(sprintf('CREATE TABLE IF NOT EXISTS %s (id VARCHAR PRIMARY KEY, name VARCHAR, guests INTEGER);', self::TABLE)); } #[Teardown] public function drop(): void { - $this->db->executeStatement("DROP TABLE IF EXISTS {$this->table()};"); - } - - private function table(): string - { - return 'projection_' . $this->subscriberId(); + $this->db->executeStatement(sprintf('DROP TABLE IF EXISTS %s;', self::TABLE)); } } ``` diff --git a/docs/subscription.md b/docs/subscription.md index 1fc32ab20..c931c3329 100644 --- a/docs/subscription.md +++ b/docs/subscription.md @@ -234,8 +234,6 @@ use Patchlevel\EventSourcing\Subscription\Lookup; #[Projector('public_profile')] final class PublicProfileProjection { - use SubscriberUtil; - // ... constructor #[Subscribe(Published::class)] @@ -310,12 +308,11 @@ use Doctrine\DBAL\Connection; use Patchlevel\EventSourcing\Attribute\Projector; use Patchlevel\EventSourcing\Attribute\Setup; use Patchlevel\EventSourcing\Attribute\Teardown; -use Patchlevel\EventSourcing\Subscription\Subscriber\SubscriberUtil; -#[Projector('profile_1')] +#[Projector(self::TABLE)] final class ProfileProjector { - use SubscriberUtil; + private const TABLE = 'profile_v1'; private Connection $connection; @@ -323,19 +320,14 @@ final class ProfileProjector public function create(): void { $this->connection->executeStatement( - "CREATE TABLE IF NOT EXISTS {$this->table()} (id VARCHAR PRIMARY KEY, name VARCHAR NOT NULL);", + sprintf('CREATE TABLE IF NOT EXISTS %s (id VARCHAR PRIMARY KEY, name VARCHAR NOT NULL);', self::TABLE), ); } #[Teardown] public function drop(): void { - $this->connection->executeStatement("DROP TABLE IF EXISTS {$this->table()};"); - } - - private function table(): string - { - return 'projection_' . $this->subscriberId(); + $this->connection->executeStatement(sprintf('DROP TABLE IF EXISTS %s;', self::TABLE)); } } ``` @@ -345,12 +337,11 @@ MySQL and MariaDB don't support transactions for DDL statements. So you must use a different database connection in your projectors, otherwise you will get an error when the subscription tries to create the table. ::: - + :::warning If you change the subscriber id, you must also change the table/collection name. The subscription engine will create a new subscription with the new subscriber id. That means the setup method will be called again and the table/collection will conflict with the old existing projection. -You can use the `SubscriberUtil` to build the table/collection name. ::: :::note diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 843db2152..3dad89790 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -42,12 +42,6 @@ parameters: count: 1 path: src/Message/Serializer/DefaultHeadersSerializer.php - - - message: '#^Call to an undefined method Patchlevel\\EventSourcing\\Store\\Store\:\:archive\(\)\.$#' - identifier: method.notFound - count: 1 - path: src/Repository/DefaultRepository.php - - message: '#^Property Patchlevel\\EventSourcing\\Serializer\\Normalizer\\IdNormalizer\:\:\$identifierClass \(class\-string\\|null\) does not accept string\.$#' identifier: assign.propertyType @@ -168,6 +162,24 @@ parameters: count: 3 path: src/Subscription/ThrowableToErrorContextTransformer.php + - + message: '#^Cannot access offset ''name'' on array\\|false\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: tests/Benchmark/BasicImplementation/Projection/ProfileProjector.php + + - + message: '#^Method Patchlevel\\EventSourcing\\Tests\\Benchmark\\BasicImplementation\\Projection\\ProfileProjector\:\:getProfileName\(\) should return string but returns mixed\.$#' + identifier: return.type + count: 1 + path: tests/Benchmark/BasicImplementation/Projection/ProfileProjector.php + + - + message: '#^Parameter \#1 \$messageLoader of class Patchlevel\\EventSourcing\\Subscription\\Engine\\DefaultSubscriptionEngine constructor expects Patchlevel\\EventSourcing\\Subscription\\Engine\\MessageLoader, Patchlevel\\EventSourcing\\Store\\StreamDoctrineDbalStore given\.$#' + identifier: argument.type + count: 1 + path: tests/Benchmark/CommandToQueryBench.php + - message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertInstanceOf\(\) with ''Patchlevel\\\\EventSourcing\\\\Tests\\\\Integration\\\\BankAccountSplitStream\\\\BankAccount'' and Patchlevel\\EventSourcing\\Tests\\Integration\\BankAccountSplitStream\\BankAccount will always evaluate to true\.$#' identifier: staticMethod.alreadyNarrowedType @@ -216,6 +228,42 @@ parameters: count: 1 path: tests/Integration/BasicImplementation/BasicIntegrationTest.php + - + message: '#^Instantiated class Patchlevel\\EventSourcing\\Tests\\Integration\\BasicImplementation\\DoctrineDbalStore not found\.$#' + identifier: class.notFound + count: 1 + path: tests/Integration/BasicImplementation/BasicIntegrationTest.php + + - + message: '#^Parameter \#1 \$messageLoader of class Patchlevel\\EventSourcing\\Subscription\\Engine\\DefaultSubscriptionEngine constructor expects Patchlevel\\EventSourcing\\Subscription\\Engine\\MessageLoader, Patchlevel\\EventSourcing\\Tests\\Integration\\BasicImplementation\\DoctrineDbalStore given\.$#' + identifier: argument.type + count: 1 + path: tests/Integration/BasicImplementation/BasicIntegrationTest.php + + - + message: '#^Parameter \#2 \$schemaConfigurator of class Patchlevel\\EventSourcing\\Schema\\DoctrineSchemaDirector constructor expects Patchlevel\\EventSourcing\\Schema\\DoctrineSchemaConfigurator, Patchlevel\\EventSourcing\\Tests\\Integration\\BasicImplementation\\DoctrineDbalStore given\.$#' + identifier: argument.type + count: 1 + path: tests/Integration/BasicImplementation/BasicIntegrationTest.php + + - + message: '#^Parameter \#2 \$store of class Patchlevel\\EventSourcing\\Repository\\DefaultRepositoryManager constructor expects Patchlevel\\EventSourcing\\Store\\Store, Patchlevel\\EventSourcing\\Tests\\Integration\\BasicImplementation\\DoctrineDbalStore given\.$#' + identifier: argument.type + count: 1 + path: tests/Integration/BasicImplementation/BasicIntegrationTest.php + + - + message: '#^Cannot access offset ''name'' on array\\|false\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: tests/Integration/BasicImplementation/Projection/ProfileProjector.php + + - + message: '#^Method Patchlevel\\EventSourcing\\Tests\\Integration\\BasicImplementation\\Projection\\ProfileProjector\:\:getProfileName\(\) should return string but returns mixed\.$#' + identifier: return.type + count: 1 + path: tests/Integration/BasicImplementation/Projection/ProfileProjector.php + - message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertInstanceOf\(\) with ''Patchlevel\\\\EventSourcing\\\\Tests\\\\Integration\\\\MicroAggregate\\\\Profile'' and Patchlevel\\EventSourcing\\Tests\\Integration\\MicroAggregate\\Profile will always evaluate to true\.$#' identifier: staticMethod.alreadyNarrowedType @@ -276,12 +324,6 @@ parameters: count: 1 path: tests/Integration/Subscription/SubscriptionTest.php - - - message: '#^Cannot use array destructuring on list\\|null\.$#' - identifier: offsetAccess.nonArray - count: 1 - path: tests/ReturnCallback.php - - message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertInstanceOf\(\) with ''Patchlevel\\\\EventSourcing\\\\Metadata\\\\AggregateRoot\\\\AggregateRootMetadata'' and Patchlevel\\EventSourcing\\Metadata\\AggregateRoot\\AggregateRootMetadata\ will always evaluate to true\.$#' identifier: staticMethod.alreadyNarrowedType diff --git a/src/Console/Command/StoreMigrateCommand.php b/src/Console/Command/StoreMigrateCommand.php new file mode 100644 index 000000000..b91daa3d8 --- /dev/null +++ b/src/Console/Command/StoreMigrateCommand.php @@ -0,0 +1,88 @@ + $translators */ + public function __construct( + private readonly Store $store, + private readonly Store $newStore, + private readonly iterable $translators = [], + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addOption( + 'buffer', + null, + InputOption::VALUE_REQUIRED, + 'How many messages should be buffered', + 1_000, + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $buffer = InputHelper::positiveIntOrZero($input->getOption('buffer')); + $style = new OutputStyle($input, $output); + + $style->info('Migration initialization...'); + + $count = $this->store->count(); + $messages = $this->store->load(); + + $style->progressStart($count); + + $bufferedMessages = []; + + $pipe = new Pipe( + $messages, + ...$this->translators, + ); + + foreach ($pipe as $message) { + $bufferedMessages[] = $message; + + if (count($bufferedMessages) < $buffer) { + continue; + } + + $this->newStore->save(...$bufferedMessages); + $bufferedMessages = []; + $style->progressAdvance($buffer); + } + + if (count($bufferedMessages) !== 0) { + $this->newStore->save(...$bufferedMessages); + $style->progressAdvance(count($bufferedMessages)); + } + + $style->progressFinish(); + $style->success('Migration finished'); + + return 0; + } +} diff --git a/src/Subscription/Engine/SubscriptionManager.php b/src/Subscription/Engine/SubscriptionManager.php index 96a36f9b9..1350a13da 100644 --- a/src/Subscription/Engine/SubscriptionManager.php +++ b/src/Subscription/Engine/SubscriptionManager.php @@ -77,28 +77,28 @@ public function find(SubscriptionCriteria $criteria): array public function add(Subscription ...$subscriptions): void { foreach ($subscriptions as $sub) { - $this->forAdd->attach($sub); + $this->forAdd->offsetSet($sub); } } public function update(Subscription ...$subscriptions): void { foreach ($subscriptions as $sub) { - $this->forUpdate->attach($sub); + $this->forUpdate->offsetSet($sub); } } public function remove(Subscription ...$subscriptions): void { foreach ($subscriptions as $sub) { - $this->forRemove->attach($sub); + $this->forRemove->offsetSet($sub); } } public function flush(): void { foreach ($this->forAdd as $subscription) { - if ($this->forRemove->contains($subscription)) { + if ($this->forRemove->offsetExists($subscription)) { continue; } @@ -106,11 +106,11 @@ public function flush(): void } foreach ($this->forUpdate as $subscription) { - if ($this->forAdd->contains($subscription)) { + if ($this->forAdd->offsetExists($subscription)) { continue; } - if ($this->forRemove->contains($subscription)) { + if ($this->forRemove->offsetExists($subscription)) { continue; } @@ -118,7 +118,7 @@ public function flush(): void } foreach ($this->forRemove as $subscription) { - if ($this->forAdd->contains($subscription)) { + if ($this->forAdd->offsetExists($subscription)) { continue; } diff --git a/src/Subscription/Subscriber/SubscriberHelper.php b/src/Subscription/Subscriber/SubscriberHelper.php index 3243ac8fb..733a4ffcc 100644 --- a/src/Subscription/Subscriber/SubscriberHelper.php +++ b/src/Subscription/Subscriber/SubscriberHelper.php @@ -8,6 +8,7 @@ use Patchlevel\EventSourcing\Metadata\Subscriber\SubscriberMetadata; use Patchlevel\EventSourcing\Metadata\Subscriber\SubscriberMetadataFactory; +/** @deprecated since 3.15.0 will be removed with 4.0.0 */ final class SubscriberHelper { public function __construct( diff --git a/src/Subscription/Subscriber/SubscriberUtil.php b/src/Subscription/Subscriber/SubscriberUtil.php index b73ad0a88..01592494c 100644 --- a/src/Subscription/Subscriber/SubscriberUtil.php +++ b/src/Subscription/Subscriber/SubscriberUtil.php @@ -7,6 +7,7 @@ use Patchlevel\EventSourcing\Metadata\Subscriber\AttributeSubscriberMetadataFactory; use Patchlevel\EventSourcing\Metadata\Subscriber\SubscriberMetadataFactory; +/** @deprecated since 3.15.0 will be removed with 4.0.0 */ trait SubscriberUtil { private static SubscriberMetadataFactory|null $metadataFactory = null; diff --git a/tests/Benchmark/BasicImplementation/Command/ChangeProfileName.php b/tests/Benchmark/BasicImplementation/Command/ChangeProfileName.php new file mode 100644 index 000000000..f8f9521a5 --- /dev/null +++ b/tests/Benchmark/BasicImplementation/Command/ChangeProfileName.php @@ -0,0 +1,18 @@ +recordThat(new ProfileCreated($command->id, $command->name, null)); + + return $self; + } + + #[Handle] + public function changeName( + ChangeProfileName $command, + ClockInterface $clock, + #[Inject('env')] + string $env, + ): void { + $this->recordThat(new NameChanged($this->id, $command->name)); + } + + #[Apply] + protected function applyProfileCreated(ProfileCreated $event): void + { + $this->id = $event->profileId; + $this->name = $event->name; + } + + #[Apply] + protected function applyNameChanged(NameChanged $event): void + { + $this->name = $event->name; + } + + public function name(): string + { + return $this->name; + } +} diff --git a/tests/Benchmark/BasicImplementation/Projection/ProfileProjector.php b/tests/Benchmark/BasicImplementation/Projection/ProfileProjector.php index c80401ec8..98f16b3c2 100644 --- a/tests/Benchmark/BasicImplementation/Projection/ProfileProjector.php +++ b/tests/Benchmark/BasicImplementation/Projection/ProfileProjector.php @@ -5,6 +5,7 @@ namespace Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Projection; use Doctrine\DBAL\Connection; +use Patchlevel\EventSourcing\Attribute\Answer; use Patchlevel\EventSourcing\Attribute\Projector; use Patchlevel\EventSourcing\Attribute\Setup; use Patchlevel\EventSourcing\Attribute\Subscribe; @@ -12,6 +13,7 @@ use Patchlevel\EventSourcing\Subscription\Subscriber\SubscriberUtil; use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Events\NameChanged; use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Events\ProfileCreated; +use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Query\QueryProfileName; #[Projector('profile')] final class ProfileProjector @@ -57,6 +59,15 @@ public function onNameChanged(NameChanged $nameChanged): void ); } + #[Answer] + public function getProfileName(QueryProfileName $queryProfileName): string + { + return $this->connection->fetchAssociative( + "SELECT name FROM {$this->table()} WHERE id = :id;", + ['id' => $queryProfileName->id->toString()], + )['name']; + } + public function table(): string { return 'projection_' . $this->subscriberId(); diff --git a/tests/Benchmark/BasicImplementation/Query/QueryProfileName.php b/tests/Benchmark/BasicImplementation/Query/QueryProfileName.php new file mode 100644 index 000000000..a26ecad93 --- /dev/null +++ b/tests/Benchmark/BasicImplementation/Query/QueryProfileName.php @@ -0,0 +1,14 @@ + ProfileWithCommands::class]); + + $manager = new DefaultRepositoryManager( + $aggregateRootRegistry, + $store, + null, + new DefaultSnapshotStore(['default' => new InMemorySnapshotAdapter()]), + ); + + $projectionConnection = DbalManager::createConnection(); + $profileProjection = new ProfileProjector($projectionConnection); + + $engine = new DefaultSubscriptionEngine( + new StoreMessageLoader($store), + new InMemorySubscriptionStore(), + new MetadataSubscriberAccessorRepository([ + $profileProjection, + new SendEmailProcessor(), + ]), + ); + + $manager = new RunSubscriptionEngineRepositoryManager($manager, $engine); + + $this->commandBus = SyncCommandBus::createForAggregateHandlers( + $aggregateRootRegistry, + $manager, + new ServiceLocator([ + ClockInterface::class => new SystemClock(), + 'env' => 'test', + ]), + ); + + $this->queryBus = new SyncQueryBus(new ServiceHandlerProvider([$profileProjection])); + + $schemaDirector = new DoctrineSchemaDirector($connection, $store); + + $schemaDirector->create(); + $engine->setup(skipBooting: true); + + $this->updateId = ProfileId::generate(); + $this->commandBus->dispatch(new CreateProfile($this->updateId, 'Peter')); + } + + #[Bench\Revs(10)] + public function benchCreate(): void + { + $id = ProfileId::generate(); + $this->commandBus->dispatch(new CreateProfile($id, 'James')); + $result = $this->queryBus->dispatch(new QueryProfileName($id)); + + assert($result === 'James'); + } + + #[Bench\Revs(10)] + public function benchUpdate(): void + { + $this->commandBus->dispatch(new ChangeProfileName($this->updateId, 'James Doe')); + $result = $this->queryBus->dispatch(new QueryProfileName($this->updateId)); + + assert($result === 'James Doe'); + } + + #[Bench\Revs(10)] + public function benchBoth(): void + { + $id = ProfileId::generate(); + $this->commandBus->dispatch(new CreateProfile($id, 'James')); + $result = $this->queryBus->dispatch(new QueryProfileName($id)); + assert($result === 'James'); + + $this->commandBus->dispatch(new ChangeProfileName($id, 'James Doe')); + $result = $this->queryBus->dispatch(new QueryProfileName($id)); + assert($result === 'James Doe'); + } +} diff --git a/tests/Integration/BasicImplementation/BasicIntegrationTest.php b/tests/Integration/BasicImplementation/BasicIntegrationTest.php index 6f3b43060..0cabaad6c 100644 --- a/tests/Integration/BasicImplementation/BasicIntegrationTest.php +++ b/tests/Integration/BasicImplementation/BasicIntegrationTest.php @@ -15,6 +15,8 @@ use Patchlevel\EventSourcing\Message\Serializer\DefaultHeadersSerializer; use Patchlevel\EventSourcing\Message\Translator\UntilEventTranslator; use Patchlevel\EventSourcing\Metadata\AggregateRoot\AggregateRootRegistry; +use Patchlevel\EventSourcing\QueryBus\ServiceHandlerProvider; +use Patchlevel\EventSourcing\QueryBus\SyncQueryBus; use Patchlevel\EventSourcing\Repository\DefaultRepositoryManager; use Patchlevel\EventSourcing\Schema\DoctrineSchemaDirector; use Patchlevel\EventSourcing\Serializer\DefaultEventSerializer; @@ -36,6 +38,7 @@ use Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\MessageDecorator\FooMessageDecorator; use Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\Processor\SendEmailProcessor; use Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\Projection\ProfileProjector; +use Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\Query\QueryProfileName; use PHPUnit\Framework\Attributes\CoversNothing; use PHPUnit\Framework\TestCase; use Psr\Clock\ClockInterface; @@ -323,4 +326,69 @@ public function testCommandBus(): void self::assertSame('John Doe', $profile->name()); self::assertSame(1, SendEmailMock::count()); } + + public function testQueryBus(): void + { + $store = new StreamDoctrineDbalStore( + $this->connection, + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events']), + DefaultHeadersSerializer::createFromPaths([ + __DIR__ . '/Header', + ]), + ); + + $aggregateRootRegistry = new AggregateRootRegistry(['profile_with_commands' => ProfileWithCommands::class]); + + $manager = new DefaultRepositoryManager( + new AggregateRootRegistry(['profile_with_commands' => ProfileWithCommands::class]), + $store, + null, + new DefaultSnapshotStore(['default' => new InMemorySnapshotAdapter()]), + new FooMessageDecorator(), + ); + + $profileProjection = new ProfileProjector($this->connection); + + $engine = new DefaultSubscriptionEngine( + new StoreMessageLoader($store), + new InMemorySubscriptionStore(), + new MetadataSubscriberAccessorRepository([ + $profileProjection, + new SendEmailProcessor(), + ]), + ); + + $manager = new RunSubscriptionEngineRepositoryManager( + $manager, + $engine, + ); + + $commandBus = SyncCommandBus::createForAggregateHandlers( + $aggregateRootRegistry, + $manager, + new ServiceLocator([ + ClockInterface::class => new SystemClock(), + 'env' => 'test', + ]), + ); + + $queryBus = new SyncQueryBus(new ServiceHandlerProvider([$profileProjection])); + + $schemaDirector = new DoctrineSchemaDirector( + $this->connection, + $store, + ); + + $schemaDirector->create(); + $engine->setup(skipBooting: true); + + $profileId = ProfileId::generate(); + + $commandBus->dispatch(new CreateProfile($profileId, 'John')); + $commandBus->dispatch(new ChangeProfileName($profileId, 'John Doe')); + + $result = $queryBus->dispatch(new QueryProfileName($profileId)); + + self::assertSame('John Doe', $result); + } } diff --git a/tests/Integration/BasicImplementation/Projection/ProfileProjector.php b/tests/Integration/BasicImplementation/Projection/ProfileProjector.php index b62674fd6..164d9d1a2 100644 --- a/tests/Integration/BasicImplementation/Projection/ProfileProjector.php +++ b/tests/Integration/BasicImplementation/Projection/ProfileProjector.php @@ -6,12 +6,14 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\Table; +use Patchlevel\EventSourcing\Attribute\Answer; use Patchlevel\EventSourcing\Attribute\Projector; use Patchlevel\EventSourcing\Attribute\Setup; use Patchlevel\EventSourcing\Attribute\Subscribe; use Patchlevel\EventSourcing\Attribute\Teardown; use Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\Events\NameChanged; use Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\Events\ProfileCreated; +use Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\Query\QueryProfileName; #[Projector('profile-1')] final class ProfileProjector @@ -61,4 +63,13 @@ public function handleNameChanged(NameChanged $nameChanged): void ], ); } + + #[Answer] + public function getProfileName(QueryProfileName $queryProfileName): string + { + return $this->connection->fetchAssociative( + 'SELECT name FROM projection_profile WHERE id = :id', + ['id' => $queryProfileName->id->toString()], + )['name']; + } } diff --git a/tests/Integration/BasicImplementation/Query/QueryProfileName.php b/tests/Integration/BasicImplementation/Query/QueryProfileName.php new file mode 100644 index 000000000..607db5012 --- /dev/null +++ b/tests/Integration/BasicImplementation/Query/QueryProfileName.php @@ -0,0 +1,14 @@ +run($input, $output); + + self::assertSame(0, $exitCode); + + $content = $output->fetch(); + + self::assertStringContainsString('Migration initialization...', $content); + self::assertStringContainsString('0', $content); + self::assertStringContainsString('Migration finished', $content); + } + + public function testOneMessage(): void + { + $fromStore = new InMemoryStore([ + new Message( + new ProfileCreated( + ProfileId::fromString('1'), + Email::fromString('info@patchlevel.de'), + ), + ), + ]); + $toStore = new InMemoryStore(); + + $command = new StoreMigrateCommand($fromStore, $toStore, []); + + $input = new ArrayInput([]); + $output = new BufferedOutput(); + + $exitCode = $command->run($input, $output); + + self::assertSame(0, $exitCode); + + $content = $output->fetch(); + + self::assertStringContainsString('Migration initialization...', $content); + self::assertStringContainsString('1', $content); + self::assertStringContainsString('Migration finished', $content); + + self::assertCount(1, iterator_to_array($toStore->load()->getIterator())); + } + + public function testTenMessages(): void + { + $fromStore = new InMemoryStore([ + new Message( + new ProfileCreated( + ProfileId::fromString('1'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('2'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('3'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('4'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('5'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('6'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('7'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('8'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('9'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('10'), + Email::fromString('info@patchlevel.de'), + ), + ), + ]); + $toStore = new InMemoryStore(); + + $command = new StoreMigrateCommand($fromStore, $toStore, []); + + $input = new ArrayInput([]); + $output = new BufferedOutput(); + + $exitCode = $command->run($input, $output); + + self::assertSame(0, $exitCode); + + $content = $output->fetch(); + + self::assertStringContainsString('Migration initialization...', $content); + self::assertStringContainsString('10', $content); + self::assertStringContainsString('Migration finished', $content); + + self::assertCount(10, iterator_to_array($toStore->load()->getIterator())); + } + + public function testTenMessagesWithBufferAt2(): void + { + $fromStore = new InMemoryStore([ + new Message( + new ProfileCreated( + ProfileId::fromString('1'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('2'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('3'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('4'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('5'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('6'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('7'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('8'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('9'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('10'), + Email::fromString('info@patchlevel.de'), + ), + ), + ]); + $toStore = new InMemoryStore(); + + $command = new StoreMigrateCommand($fromStore, $toStore, []); + + $input = new ArrayInput(['--buffer' => 10]); + $output = new BufferedOutput(); + + $exitCode = $command->run($input, $output); + + self::assertSame(0, $exitCode); + + $content = $output->fetch(); + + self::assertStringContainsString('Migration initialization...', $content); + self::assertStringContainsString('10', $content); + self::assertStringContainsString('Migration finished', $content); + + self::assertCount(10, iterator_to_array($toStore->load()->getIterator())); + } + + public function testTenMessagesWithDroppingTranslator(): void + { + $fromStore = new InMemoryStore([ + new Message( + new ProfileCreated( + ProfileId::fromString('1'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('2'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('3'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('4'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('5'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('6'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('7'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('8'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileCreated( + ProfileId::fromString('9'), + Email::fromString('info@patchlevel.de'), + ), + ), + new Message( + new ProfileVisited( + ProfileId::fromString('1'), + ), + ), + ]); + $toStore = new InMemoryStore(); + + $command = new StoreMigrateCommand( + $fromStore, + $toStore, + [new ExcludeEventTranslator([ProfileCreated::class])], + ); + + $input = new ArrayInput([]); + $output = new BufferedOutput(); + + $exitCode = $command->run($input, $output); + + self::assertSame(0, $exitCode); + + $content = $output->fetch(); + + self::assertStringContainsString('Migration initialization...', $content); + self::assertStringContainsString('10', $content); + self::assertStringContainsString('Migration finished', $content); + + self::assertCount(1, iterator_to_array($toStore->load()->getIterator())); + } +} diff --git a/tools/composer.lock b/tools/composer.lock index 61cff083d..bbb6f9d3e 100644 --- a/tools/composer.lock +++ b/tools/composer.lock @@ -151,16 +151,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.5.9", + "version": "1.5.10", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "1905981ee626e6f852448b7aaa978f8666c5bc54" + "reference": "961a5e4056dd2e4a2eedcac7576075947c28bf63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/1905981ee626e6f852448b7aaa978f8666c5bc54", - "reference": "1905981ee626e6f852448b7aaa978f8666c5bc54", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/961a5e4056dd2e4a2eedcac7576075947c28bf63", + "reference": "961a5e4056dd2e4a2eedcac7576075947c28bf63", "shasum": "" }, "require": { @@ -207,7 +207,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.5.9" + "source": "https://github.com/composer/ca-bundle/tree/1.5.10" }, "funding": [ { @@ -219,20 +219,20 @@ "type": "github" } ], - "time": "2025-11-06T11:46:17+00:00" + "time": "2025-12-08T15:06:51+00:00" }, { "name": "composer/class-map-generator", - "version": "1.7.0", + "version": "1.7.1", "source": { "type": "git", "url": "https://github.com/composer/class-map-generator.git", - "reference": "2373419b7709815ed323ebf18c3c72d03ff4a8a6" + "reference": "8f5fa3cc214230e71f54924bd0197a3bcc705eb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/class-map-generator/zipball/2373419b7709815ed323ebf18c3c72d03ff4a8a6", - "reference": "2373419b7709815ed323ebf18c3c72d03ff4a8a6", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/8f5fa3cc214230e71f54924bd0197a3bcc705eb1", + "reference": "8f5fa3cc214230e71f54924bd0197a3bcc705eb1", "shasum": "" }, "require": { @@ -276,7 +276,7 @@ ], "support": { "issues": "https://github.com/composer/class-map-generator/issues", - "source": "https://github.com/composer/class-map-generator/tree/1.7.0" + "source": "https://github.com/composer/class-map-generator/tree/1.7.1" }, "funding": [ { @@ -288,20 +288,20 @@ "type": "github" } ], - "time": "2025-11-19T10:41:15+00:00" + "time": "2025-12-29T13:15:25+00:00" }, { "name": "composer/composer", - "version": "2.9.2", + "version": "2.9.3", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "8d5358f147c63a3a681b002076deff8c90e0b19d" + "reference": "fb3bee27676fd852a8a11ebbb1de19b4dada5aba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/8d5358f147c63a3a681b002076deff8c90e0b19d", - "reference": "8d5358f147c63a3a681b002076deff8c90e0b19d", + "url": "https://api.github.com/repos/composer/composer/zipball/fb3bee27676fd852a8a11ebbb1de19b4dada5aba", + "reference": "fb3bee27676fd852a8a11ebbb1de19b4dada5aba", "shasum": "" }, "require": { @@ -389,7 +389,7 @@ "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/composer/issues", "security": "https://github.com/composer/composer/security/policy", - "source": "https://github.com/composer/composer/tree/2.9.2" + "source": "https://github.com/composer/composer/tree/2.9.3" }, "funding": [ { @@ -401,7 +401,7 @@ "type": "github" } ], - "time": "2025-11-19T20:57:25+00:00" + "time": "2025-12-30T12:40:17+00:00" }, { "name": "composer/metadata-minifier", @@ -776,16 +776,16 @@ }, { "name": "deptrac/deptrac", - "version": "4.2.1", + "version": "4.4.0", "source": { "type": "git", "url": "https://github.com/deptrac/deptrac.git", - "reference": "232eeb7e99fd287a5a3c4f79efc34a025fc81957" + "reference": "3058cb1b5908a25711ccc0f4c96b64e6fd704114" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/deptrac/deptrac/zipball/232eeb7e99fd287a5a3c4f79efc34a025fc81957", - "reference": "232eeb7e99fd287a5a3c4f79efc34a025fc81957", + "url": "https://api.github.com/repos/deptrac/deptrac/zipball/3058cb1b5908a25711ccc0f4c96b64e6fd704114", + "reference": "3058cb1b5908a25711ccc0f4c96b64e6fd704114", "shasum": "" }, "require": { @@ -794,8 +794,9 @@ "nikic/php-parser": "^5", "php": "^8.1", "phpdocumentor/graphviz": "^2.1", - "phpdocumentor/type-resolver": "^1.6", - "phpstan/phpdoc-parser": "^2.0", + "phpdocumentor/type-resolver": "^1.9.0", + "phpstan/phpdoc-parser": "^1.5.0|^2.1.0", + "phpstan/phpstan": "^2.0", "psr/container": "^2.0", "psr/event-dispatcher": "^1.0", "symfony/config": "^6.4|^7.0", @@ -854,9 +855,9 @@ ], "support": { "issues": "https://github.com/deptrac/deptrac/issues", - "source": "https://github.com/deptrac/deptrac/tree/4.2.1" + "source": "https://github.com/deptrac/deptrac/tree/4.4.0" }, - "time": "2025-09-29T13:03:57+00:00" + "time": "2025-12-08T08:14:18+00:00" }, { "name": "doctrine/deprecations", @@ -955,21 +956,21 @@ }, { "name": "justinrainbow/json-schema", - "version": "6.6.2", + "version": "6.6.4", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "3c25fe750c1599716ef26aa997f7c026cee8c4b7" + "reference": "2eeb75d21cf73211335888e7f5e6fd7440723ec7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/3c25fe750c1599716ef26aa997f7c026cee8c4b7", - "reference": "3c25fe750c1599716ef26aa997f7c026cee8c4b7", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/2eeb75d21cf73211335888e7f5e6fd7440723ec7", + "reference": "2eeb75d21cf73211335888e7f5e6fd7440723ec7", "shasum": "" }, "require": { "ext-json": "*", - "marc-mabe/php-enum": "^4.0", + "marc-mabe/php-enum": "^4.4", "php": "^7.2 || ^8.0" }, "require-dev": { @@ -1024,9 +1025,9 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/6.6.2" + "source": "https://github.com/jsonrainbow/json-schema/tree/6.6.4" }, - "time": "2025-11-28T15:24:03+00:00" + "time": "2025-12-19T15:01:32+00:00" }, { "name": "marc-mabe/php-enum", @@ -1103,16 +1104,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.6.2", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "3a454ca033b9e06b63282ce19562e892747449bb" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", - "reference": "3a454ca033b9e06b63282ce19562e892747449bb", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { @@ -1155,9 +1156,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "time": "2025-10-21T19:32:17+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { "name": "nikolaposa/version", @@ -1494,6 +1495,59 @@ }, "time": "2025-08-30T15:50:23+00:00" }, + { + "name": "phpstan/phpstan", + "version": "2.1.33", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9e800e6bee7d5bd02784d4c6069b48032d16224f", + "reference": "9e800e6bee7d5bd02784d4c6069b48032d16224f", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2025-12-05T10:24:31+00:00" + }, { "name": "psr/container", "version": "2.0.2", @@ -2110,16 +2164,16 @@ }, { "name": "symfony/config", - "version": "v7.4.0", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "f76c74e93bce2b9285f2dad7fbd06fa8182a7a41" + "reference": "800ce889e358a53a9678b3212b0c8cecd8c6aace" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/f76c74e93bce2b9285f2dad7fbd06fa8182a7a41", - "reference": "f76c74e93bce2b9285f2dad7fbd06fa8182a7a41", + "url": "https://api.github.com/repos/symfony/config/zipball/800ce889e358a53a9678b3212b0c8cecd8c6aace", + "reference": "800ce889e358a53a9678b3212b0c8cecd8c6aace", "shasum": "" }, "require": { @@ -2165,7 +2219,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v7.4.0" + "source": "https://github.com/symfony/config/tree/v7.4.3" }, "funding": [ { @@ -2185,20 +2239,20 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2025-12-23T14:24:27+00:00" }, { "name": "symfony/console", - "version": "v7.4.0", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8" + "reference": "732a9ca6cd9dfd940c639062d5edbde2f6727fb6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8", - "reference": "0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8", + "url": "https://api.github.com/repos/symfony/console/zipball/732a9ca6cd9dfd940c639062d5edbde2f6727fb6", + "reference": "732a9ca6cd9dfd940c639062d5edbde2f6727fb6", "shasum": "" }, "require": { @@ -2263,7 +2317,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.0" + "source": "https://github.com/symfony/console/tree/v7.4.3" }, "funding": [ { @@ -2283,20 +2337,20 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2025-12-23T14:50:43+00:00" }, { "name": "symfony/dependency-injection", - "version": "v7.4.0", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "3972ca7bbd649467b21a54870721b9e9f3652f9b" + "reference": "54122901b6d772e94f1e71a75e0533bc16563499" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/3972ca7bbd649467b21a54870721b9e9f3652f9b", - "reference": "3972ca7bbd649467b21a54870721b9e9f3652f9b", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/54122901b6d772e94f1e71a75e0533bc16563499", + "reference": "54122901b6d772e94f1e71a75e0533bc16563499", "shasum": "" }, "require": { @@ -2347,7 +2401,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v7.4.0" + "source": "https://github.com/symfony/dependency-injection/tree/v7.4.3" }, "funding": [ { @@ -2367,7 +2421,7 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2025-12-28T10:55:46+00:00" }, { "name": "symfony/deprecation-contracts", @@ -2669,16 +2723,16 @@ }, { "name": "symfony/finder", - "version": "v7.4.0", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "340b9ed7320570f319028a2cbec46d40535e94bd" + "reference": "fffe05569336549b20a1be64250b40516d6e8d06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/340b9ed7320570f319028a2cbec46d40535e94bd", - "reference": "340b9ed7320570f319028a2cbec46d40535e94bd", + "url": "https://api.github.com/repos/symfony/finder/zipball/fffe05569336549b20a1be64250b40516d6e8d06", + "reference": "fffe05569336549b20a1be64250b40516d6e8d06", "shasum": "" }, "require": { @@ -2713,7 +2767,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.0" + "source": "https://github.com/symfony/finder/tree/v7.4.3" }, "funding": [ { @@ -2733,7 +2787,7 @@ "type": "tidelift" } ], - "time": "2025-11-05T05:42:40+00:00" + "time": "2025-12-23T14:50:43+00:00" }, { "name": "symfony/polyfill-ctype", @@ -3396,16 +3450,16 @@ }, { "name": "symfony/process", - "version": "v8.0.0", + "version": "v8.0.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "a0a750500c4ce900d69ba4e9faf16f82c10ee149" + "reference": "0cbbd88ec836f8757641c651bb995335846abb78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/a0a750500c4ce900d69ba4e9faf16f82c10ee149", - "reference": "a0a750500c4ce900d69ba4e9faf16f82c10ee149", + "url": "https://api.github.com/repos/symfony/process/zipball/0cbbd88ec836f8757641c651bb995335846abb78", + "reference": "0cbbd88ec836f8757641c651bb995335846abb78", "shasum": "" }, "require": { @@ -3437,7 +3491,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v8.0.0" + "source": "https://github.com/symfony/process/tree/v8.0.3" }, "funding": [ { @@ -3457,7 +3511,7 @@ "type": "tidelift" } ], - "time": "2025-10-16T16:25:44+00:00" + "time": "2025-12-19T10:01:18+00:00" }, { "name": "symfony/service-contracts", @@ -3548,16 +3602,16 @@ }, { "name": "symfony/string", - "version": "v8.0.0", + "version": "v8.0.1", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f929eccf09531078c243df72398560e32fa4cf4f" + "reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f929eccf09531078c243df72398560e32fa4cf4f", - "reference": "f929eccf09531078c243df72398560e32fa4cf4f", + "url": "https://api.github.com/repos/symfony/string/zipball/ba65a969ac918ce0cc3edfac6cdde847eba231dc", + "reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc", "shasum": "" }, "require": { @@ -3614,7 +3668,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v8.0.0" + "source": "https://github.com/symfony/string/tree/v8.0.1" }, "funding": [ { @@ -3634,7 +3688,7 @@ "type": "tidelift" } ], - "time": "2025-09-11T14:37:55+00:00" + "time": "2025-12-01T09:13:36+00:00" }, { "name": "symfony/var-exporter", @@ -3718,16 +3772,16 @@ }, { "name": "symfony/yaml", - "version": "v7.4.0", + "version": "v7.4.1", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "6c84a4b55aee4cd02034d1c528e83f69ddf63810" + "reference": "24dd4de28d2e3988b311751ac49e684d783e2345" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/6c84a4b55aee4cd02034d1c528e83f69ddf63810", - "reference": "6c84a4b55aee4cd02034d1c528e83f69ddf63810", + "url": "https://api.github.com/repos/symfony/yaml/zipball/24dd4de28d2e3988b311751ac49e684d783e2345", + "reference": "24dd4de28d2e3988b311751ac49e684d783e2345", "shasum": "" }, "require": { @@ -3770,7 +3824,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.4.0" + "source": "https://github.com/symfony/yaml/tree/v7.4.1" }, "funding": [ { @@ -3790,7 +3844,7 @@ "type": "tidelift" } ], - "time": "2025-11-16T10:14:42+00:00" + "time": "2025-12-04T18:11:45+00:00" } ], "packages-dev": [],