From d199818b5d35ed4bfbf1f656928d0adeeaf5ddaa Mon Sep 17 00:00:00 2001 From: Ryan Cooper <198979606+coopryan@users.noreply.github.com> Date: Thu, 28 Aug 2025 13:09:23 +0200 Subject: [PATCH 1/3] Composer update deps --- composer.lock | 690 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 496 insertions(+), 194 deletions(-) diff --git a/composer.lock b/composer.lock index 7335e25..f5d6a9c 100644 --- a/composer.lock +++ b/composer.lock @@ -409,16 +409,16 @@ }, { "name": "fidry/cpu-core-counter", - "version": "1.2.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "8520451a140d3f46ac33042715115e290cf5785f" + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", - "reference": "8520451a140d3f46ac33042715115e290cf5785f", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", "shasum": "" }, "require": { @@ -428,10 +428,10 @@ "fidry/makefile": "^0.2.0", "fidry/php-cs-fixer-config": "^1.1.2", "phpstan/extension-installer": "^1.2.0", - "phpstan/phpstan": "^1.9.2", - "phpstan/phpstan-deprecation-rules": "^1.0.0", - "phpstan/phpstan-phpunit": "^1.2.2", - "phpstan/phpstan-strict-rules": "^1.4.4", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^8.5.31 || ^9.5.26", "webmozarts/strict-phpunit": "^7.5" }, @@ -458,7 +458,7 @@ ], "support": { "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" }, "funding": [ { @@ -466,7 +466,7 @@ "type": "github" } ], - "time": "2024-08-06T10:04:20+00:00" + "time": "2025-08-14T07:29:31+00:00" }, { "name": "infection/abstract-testframework-adapter", @@ -647,38 +647,40 @@ }, { "name": "infection/infection", - "version": "0.29.14", + "version": "0.31.2", "source": { "type": "git", "url": "https://github.com/infection/infection.git", - "reference": "feea2a48a8aeedd3a4d2105167b41a46f0e568a3" + "reference": "242785d48ac2dc00a1d3a77b2048b289dc82cbc9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/infection/infection/zipball/feea2a48a8aeedd3a4d2105167b41a46f0e568a3", - "reference": "feea2a48a8aeedd3a4d2105167b41a46f0e568a3", + "url": "https://api.github.com/repos/infection/infection/zipball/242785d48ac2dc00a1d3a77b2048b289dc82cbc9", + "reference": "242785d48ac2dc00a1d3a77b2048b289dc82cbc9", "shasum": "" }, "require": { - "colinodell/json5": "^2.2 || ^3.0", + "colinodell/json5": "^3.0", "composer-runtime-api": "^2.0", - "composer/xdebug-handler": "^2.0 || ^3.0", + "composer/xdebug-handler": "^3.0", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", - "fidry/cpu-core-counter": "^0.4.0 || ^0.5.0 || ^1.0", + "fidry/cpu-core-counter": "^1.0", "infection/abstract-testframework-adapter": "^0.5.0", "infection/extension-installer": "^0.1.0", "infection/include-interceptor": "^0.2.5", "infection/mutator": "^0.4", - "justinrainbow/json-schema": "^5.3 || ^6.0", + "justinrainbow/json-schema": "^6.0", "nikic/php-parser": "^5.3", "ondram/ci-detector": "^4.1.0", "php": "^8.2", - "sanmai/later": "^0.1.1", - "sanmai/pipeline": "^5.1 || ^6", - "sebastian/diff": "^3.0.2 || ^4.0 || ^5.0 || ^6.0 || ^7.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", @@ -688,8 +690,7 @@ }, "conflict": { "antecedent/patchwork": "<2.1.25", - "dg/bypass-finals": "<1.4.1", - "phpunit/php-code-coverage": ">9,<9.1.4 || >9.2.17,<9.2.21" + "dg/bypass-finals": "<1.4.1" }, "require-dev": { "ext-simplexml": "*", @@ -699,8 +700,10 @@ "phpstan/phpstan-phpunit": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", "phpstan/phpstan-webmozart-assert": "^2.0", - "phpunit/phpunit": "^11.5", + "phpunit/phpunit": "^11.5.27", "rector/rector": "^2.0", + "shipmonk/dead-code-detector": "^0.12.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" @@ -759,7 +762,7 @@ ], "support": { "issues": "https://github.com/infection/infection/issues", - "source": "https://github.com/infection/infection/tree/0.29.14" + "source": "https://github.com/infection/infection/tree/0.31.2" }, "funding": [ { @@ -771,20 +774,20 @@ "type": "open_collective" } ], - "time": "2025-03-02T18:49:12+00:00" + "time": "2025-08-21T06:43:51+00:00" }, { "name": "infection/mutator", - "version": "0.4.0", + "version": "0.4.1", "source": { "type": "git", "url": "https://github.com/infection/mutator.git", - "reference": "51d6d01a2357102030aee9d603063c4bad86b144" + "reference": "3c976d721b02b32f851ee4e15d553ef1e9186d1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/infection/mutator/zipball/51d6d01a2357102030aee9d603063c4bad86b144", - "reference": "51d6d01a2357102030aee9d603063c4bad86b144", + "url": "https://api.github.com/repos/infection/mutator/zipball/3c976d721b02b32f851ee4e15d553ef1e9186d1d", + "reference": "3c976d721b02b32f851ee4e15d553ef1e9186d1d", "shasum": "" }, "require": { @@ -812,7 +815,7 @@ "description": "Mutator interface to implement custom mutators (mutation operators) for Infection", "support": { "issues": "https://github.com/infection/mutator/issues", - "source": "https://github.com/infection/mutator/tree/0.4.0" + "source": "https://github.com/infection/mutator/tree/0.4.1" }, "funding": [ { @@ -824,20 +827,20 @@ "type": "open_collective" } ], - "time": "2024-05-14T22:39:59+00:00" + "time": "2025-04-29T08:19:52+00:00" }, { "name": "justinrainbow/json-schema", - "version": "6.3.1", + "version": "6.4.2", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "c9f00dec766a67bf82c277b71d71d254357db92c" + "reference": "ce1fd2d47799bb60668643bc6220f6278a4c1d02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/c9f00dec766a67bf82c277b71d71d254357db92c", - "reference": "c9f00dec766a67bf82c277b71d71d254357db92c", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/ce1fd2d47799bb60668643bc6220f6278a4c1d02", + "reference": "ce1fd2d47799bb60668643bc6220f6278a4c1d02", "shasum": "" }, "require": { @@ -897,9 +900,9 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/6.3.1" + "source": "https://github.com/jsonrainbow/json-schema/tree/6.4.2" }, - "time": "2025-03-18T19:03:56+00:00" + "time": "2025-06-03T18:27:04+00:00" }, { "name": "marc-mabe/php-enum", @@ -976,16 +979,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.0", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -1024,7 +1027,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -1032,20 +1035,20 @@ "type": "tidelift" } ], - "time": "2025-02-12T12:17:51+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nikic/php-parser", - "version": "v5.4.0", + "version": "v5.6.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494" + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", "shasum": "" }, "require": { @@ -1064,7 +1067,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -1088,9 +1091,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" }, - "time": "2024-12-30T11:07:19+00:00" + "time": "2025-08-13T20:13:15+00:00" }, { "name": "ondram/ci-detector", @@ -1290,16 +1293,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.11", + "version": "2.1.22", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "8ca5f79a8f63c49b2359065832a654e1ec70ac30" + "reference": "41600c8379eb5aee63e9413fe9e97273e25d57e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8ca5f79a8f63c49b2359065832a654e1ec70ac30", - "reference": "8ca5f79a8f63c49b2359065832a654e1ec70ac30", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/41600c8379eb5aee63e9413fe9e97273e25d57e4", + "reference": "41600c8379eb5aee63e9413fe9e97273e25d57e4", "shasum": "" }, "require": { @@ -1344,25 +1347,25 @@ "type": "github" } ], - "time": "2025-03-24T13:45:00+00:00" + "time": "2025-08-04T19:17:37+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "2.0.6", + "version": "2.0.7", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "6b92469f8a7995e626da3aa487099617b8dfa260" + "reference": "9a9b161baee88a5f5c58d816943cff354ff233dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/6b92469f8a7995e626da3aa487099617b8dfa260", - "reference": "6b92469f8a7995e626da3aa487099617b8dfa260", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/9a9b161baee88a5f5c58d816943cff354ff233dc", + "reference": "9a9b161baee88a5f5c58d816943cff354ff233dc", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.0.4" + "phpstan/phpstan": "^2.1.18" }, "conflict": { "phpunit/phpunit": "<7.0" @@ -1395,22 +1398,22 @@ "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.6" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.7" }, - "time": "2025-03-26T12:47:06+00:00" + "time": "2025-07-13T11:31:46+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "12.1.0", + "version": "12.3.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "d331a5ced3d9a2b917baa9841b2211e72f9e780d" + "reference": "733025d94635a001f67db71a2ed1bab4e7e4a9dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d331a5ced3d9a2b917baa9841b2211e72f9e780d", - "reference": "d331a5ced3d9a2b917baa9841b2211e72f9e780d", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/733025d94635a001f67db71a2ed1bab4e7e4a9dc", + "reference": "733025d94635a001f67db71a2ed1bab4e7e4a9dc", "shasum": "" }, "require": { @@ -1428,7 +1431,7 @@ "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^12.1" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -1437,7 +1440,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "12.1.x-dev" + "dev-main": "12.3.x-dev" } }, "autoload": { @@ -1466,15 +1469,27 @@ "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/12.1.0" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.3.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" } ], - "time": "2025-03-17T13:56:07+00:00" + "time": "2025-08-27T14:43:48+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1723,16 +1738,16 @@ }, { "name": "phpunit/phpunit", - "version": "12.0.10", + "version": "12.3.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "6075843014de23bcd6992842d69ca99d25d6a433" + "reference": "b8fa997c49682979ad6bfaa0d7fb25f54954965e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6075843014de23bcd6992842d69ca99d25d6a433", - "reference": "6075843014de23bcd6992842d69ca99d25d6a433", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b8fa997c49682979ad6bfaa0d7fb25f54954965e", + "reference": "b8fa997c49682979ad6bfaa0d7fb25f54954965e", "shasum": "" }, "require": { @@ -1742,23 +1757,23 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.0", + "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.3", - "phpunit/php-code-coverage": "^12.1.0", + "phpunit/php-code-coverage": "^12.3.3", "phpunit/php-file-iterator": "^6.0.0", "phpunit/php-invoker": "^6.0.0", "phpunit/php-text-template": "^5.0.0", "phpunit/php-timer": "^8.0.0", "sebastian/cli-parser": "^4.0.0", - "sebastian/comparator": "^7.0.1", + "sebastian/comparator": "^7.1.3", "sebastian/diff": "^7.0.0", - "sebastian/environment": "^8.0.0", + "sebastian/environment": "^8.0.3", "sebastian/exporter": "^7.0.0", "sebastian/global-state": "^8.0.0", "sebastian/object-enumerator": "^7.0.0", - "sebastian/type": "^6.0.2", + "sebastian/type": "^6.0.3", "sebastian/version": "^6.0.0", "staabm/side-effects-detector": "^1.0.5" }, @@ -1768,7 +1783,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "12.0-dev" + "dev-main": "12.3-dev" } }, "autoload": { @@ -1800,7 +1815,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/12.0.10" + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.3.7" }, "funding": [ { @@ -1811,12 +1826,68 @@ "url": "https://github.com/sebastianbergmann", "type": "github" }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2025-03-23T16:03:59+00:00" + "time": "2025-08-28T05:15:46+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" }, { "name": "psr/container", @@ -1871,22 +1942,161 @@ }, "time": "2021-11-05T16:47:00+00:00" }, + { + "name": "sanmai/di-container", + "version": "0.1.5", + "source": { + "type": "git", + "url": "https://github.com/sanmai/di-container.git", + "reference": "355534ad7970fc7dab4211ecaf2da5c546855ee8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sanmai/di-container/zipball/355534ad7970fc7dab4211ecaf2da5c546855ee8", + "reference": "355534ad7970fc7dab4211ecaf2da5c546855ee8", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^1.1.2 || ^2.0", + "sanmai/pipeline": "^6.17 || ^7.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.8", + "friendsofphp/php-cs-fixer": "^3.17", + "infection/infection": ">=0.29", + "php-coveralls/php-coveralls": "^2.4.1", + "phpstan/extension-installer": "^1.4", + "phpunit/phpunit": "^11.5.25", + "sanmai/phpstan-rules": "^0.3.10" + }, + "type": "library", + "extra": { + "preferred-install": "dist" + }, + "autoload": { + "psr-4": { + "DIContainer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Alexey Kopytko", + "email": "alexey@kopytko.com", + "homepage": "https://github.com/sanmai" + }, + { + "name": "Maks Rafalko", + "homepage": "https://twitter.com/maks_rafalko" + }, + { + "name": "Théo FIDRY", + "homepage": "https://twitter.com/tfidry" + } + ], + "description": "dependency injection container with automatic constructor dependency resolution", + "keywords": [ + "Autowiring", + "constructor di", + "di container", + "psr 11" + ], + "support": { + "issues": "https://github.com/sanmai/di-container/issues", + "source": "https://github.com/sanmai/di-container/tree/0.1.5" + }, + "funding": [ + { + "url": "https://github.com/sanmai", + "type": "github" + } + ], + "time": "2025-08-04T09:43:58+00:00" + }, + { + "name": "sanmai/duoclock", + "version": "0.1.1", + "source": { + "type": "git", + "url": "https://github.com/sanmai/DuoClock.git", + "reference": "30aa40092396dc96b68c8e8d49162619574477e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sanmai/DuoClock/zipball/30aa40092396dc96b68c8e8d49162619574477e2", + "reference": "30aa40092396dc96b68c8e8d49162619574477e2", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/clock": "^1.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.8", + "friendsofphp/php-cs-fixer": "^3.17", + "infection/infection": ">=0.29", + "php-coveralls/php-coveralls": "^2.4.1", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^11.5.25", + "sanmai/phpstan-rules": "^0.3.1", + "vimeo/psalm": "^6.12" + }, + "type": "library", + "extra": { + "preferred-install": "dist" + }, + "autoload": { + "psr-4": { + "DuoClock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Alexey Kopytko", + "email": "alexey@kopytko.com" + } + ], + "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" + }, + "funding": [ + { + "url": "https://github.com/sanmai", + "type": "github" + } + ], + "time": "2025-07-28T02:17:28+00:00" + }, { "name": "sanmai/later", - "version": "0.1.4", + "version": "0.1.7", "source": { "type": "git", "url": "https://github.com/sanmai/later.git", - "reference": "e24c4304a4b1349c2a83151a692cec0c10579f60" + "reference": "72a82d783864bca90412d8a26c1878f8981fee97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sanmai/later/zipball/e24c4304a4b1349c2a83151a692cec0c10579f60", - "reference": "e24c4304a4b1349c2a83151a692cec0c10579f60", + "url": "https://api.github.com/repos/sanmai/later/zipball/72a82d783864bca90412d8a26c1878f8981fee97", + "reference": "72a82d783864bca90412d8a26c1878f8981fee97", "shasum": "" }, "require": { - "php": ">=7.4" + "php": ">=8.2" }, "require-dev": { "ergebnis/composer-normalize": "^2.8", @@ -1925,7 +2135,7 @@ "description": "Later: deferred wrapper object", "support": { "issues": "https://github.com/sanmai/later/issues", - "source": "https://github.com/sanmai/later/tree/0.1.4" + "source": "https://github.com/sanmai/later/tree/0.1.7" }, "funding": [ { @@ -1933,34 +2143,37 @@ "type": "github" } ], - "time": "2023-10-24T00:25:28+00:00" + "time": "2025-05-11T01:48:00+00:00" }, { "name": "sanmai/pipeline", - "version": "6.12", + "version": "7.1", "source": { "type": "git", "url": "https://github.com/sanmai/pipeline.git", - "reference": "ad7dbc3f773eeafb90d5459522fbd8f188532e25" + "reference": "d01565ef9f5cd7d1019c5f8bee09497067511f36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sanmai/pipeline/zipball/ad7dbc3f773eeafb90d5459522fbd8f188532e25", - "reference": "ad7dbc3f773eeafb90d5459522fbd8f188532e25", + "url": "https://api.github.com/repos/sanmai/pipeline/zipball/d01565ef9f5cd7d1019c5f8bee09497067511f36", + "reference": "d01565ef9f5cd7d1019c5f8bee09497067511f36", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0" + "php": ">=8.2" }, "require-dev": { "ergebnis/composer-normalize": "^2.8", + "esi/phpunit-coverage-check": ">2", "friendsofphp/php-cs-fixer": "^3.17", - "infection/infection": ">=0.10.5", + "infection/infection": ">=0.30.3", "league/pipeline": "^0.3 || ^1.0", - "phan/phan": ">=1.1", "php-coveralls/php-coveralls": "^2.4.1", - "phpstan/phpstan": ">=0.10", - "phpunit/phpunit": ">=9.4", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2", + "phpunit/phpunit": ">=9.4 <12", + "sanmai/phpstan-rules": "^0.3.0", + "sanmai/phpunit-double-colon-syntax": "^0.1.1", "vimeo/psalm": ">=2" }, "type": "library", @@ -1990,7 +2203,7 @@ "description": "General-purpose collections pipeline", "support": { "issues": "https://github.com/sanmai/pipeline/issues", - "source": "https://github.com/sanmai/pipeline/tree/6.12" + "source": "https://github.com/sanmai/pipeline/tree/7.1" }, "funding": [ { @@ -1998,7 +2211,7 @@ "type": "github" } ], - "time": "2024-10-17T02:22:57+00:00" + "time": "2025-07-27T07:02:29+00:00" }, { "name": "sebastian/cli-parser", @@ -2059,16 +2272,16 @@ }, { "name": "sebastian/comparator", - "version": "7.0.1", + "version": "7.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "b478f34614f934e0291598d0c08cbaba9644bee5" + "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/b478f34614f934e0291598d0c08cbaba9644bee5", - "reference": "b478f34614f934e0291598d0c08cbaba9644bee5", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/dc904b4bb3ab070865fa4068cd84f3da8b945148", + "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148", "shasum": "" }, "require": { @@ -2079,7 +2292,7 @@ "sebastian/exporter": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^12.2" }, "suggest": { "ext-bcmath": "For comparing BcMath\\Number objects" @@ -2087,7 +2300,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "7.0-dev" + "dev-main": "7.1-dev" } }, "autoload": { @@ -2127,15 +2340,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/7.0.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2025-03-07T07:00:32+00:00" + "time": "2025-08-20T11:27:00+00:00" }, { "name": "sebastian/complexity", @@ -2264,16 +2489,16 @@ }, { "name": "sebastian/environment", - "version": "8.0.0", + "version": "8.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "8afe311eca49171bf95405cc0078be9a3821f9f2" + "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8afe311eca49171bf95405cc0078be9a3821f9f2", - "reference": "8afe311eca49171bf95405cc0078be9a3821f9f2", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/24a711b5c916efc6d6e62aa65aa2ec98fef77f68", + "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68", "shasum": "" }, "require": { @@ -2316,15 +2541,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/8.0.0" + "source": "https://github.com/sebastianbergmann/environment/tree/8.0.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" } ], - "time": "2025-02-07T04:56:08+00:00" + "time": "2025-08-12T14:11:56+00:00" }, { "name": "sebastian/exporter", @@ -2406,16 +2643,16 @@ }, { "name": "sebastian/global-state", - "version": "8.0.0", + "version": "8.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "570a2aeb26d40f057af686d63c4e99b075fb6cbc" + "reference": "912dd568677a6e13c67c08321710ad6ac81e6dca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/570a2aeb26d40f057af686d63c4e99b075fb6cbc", - "reference": "570a2aeb26d40f057af686d63c4e99b075fb6cbc", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/912dd568677a6e13c67c08321710ad6ac81e6dca", + "reference": "912dd568677a6e13c67c08321710ad6ac81e6dca", "shasum": "" }, "require": { @@ -2456,15 +2693,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.0" + "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" } ], - "time": "2025-02-07T04:56:59+00:00" + "time": "2025-08-28T09:13:48+00:00" }, { "name": "sebastian/lines-of-code", @@ -2640,16 +2889,16 @@ }, { "name": "sebastian/recursion-context", - "version": "7.0.0", + "version": "7.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "c405ae3a63e01b32eb71577f8ec1604e39858a7c" + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/c405ae3a63e01b32eb71577f8ec1604e39858a7c", - "reference": "c405ae3a63e01b32eb71577f8ec1604e39858a7c", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", "shasum": "" }, "require": { @@ -2692,28 +2941,40 @@ "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.0.0" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.0.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2025-02-07T05:00:01+00:00" + "time": "2025-08-13T04:44:59+00:00" }, { "name": "sebastian/type", - "version": "6.0.2", + "version": "6.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "1d7cd6e514384c36d7a390347f57c385d4be6069" + "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/1d7cd6e514384c36d7a390347f57c385d4be6069", - "reference": "1d7cd6e514384c36d7a390347f57c385d4be6069", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/e549163b9760b8f71f191651d22acf32d56d6d4d", + "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d", "shasum": "" }, "require": { @@ -2749,15 +3010,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/type/issues", "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/6.0.2" + "source": "https://github.com/sebastianbergmann/type/tree/6.0.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" } ], - "time": "2025-03-18T13:37:31+00:00" + "time": "2025-08-09T06:57:12+00:00" }, { "name": "sebastian/version", @@ -2867,23 +3140,24 @@ }, { "name": "symfony/console", - "version": "v7.2.5", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "e51498ea18570c062e7df29d05a7003585b19b88" + "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/e51498ea18570c062e7df29d05a7003585b19b88", - "reference": "e51498ea18570c062e7df29d05a7003585b19b88", + "url": "https://api.github.com/repos/symfony/console/zipball/5f360ebc65c55265a74d23d7fe27f957870158a1", + "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1", "shasum": "" }, "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^6.4|^7.0" + "symfony/string": "^7.2" }, "conflict": { "symfony/dependency-injection": "<6.4", @@ -2940,7 +3214,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.2.5" + "source": "https://github.com/symfony/console/tree/v7.3.2" }, "funding": [ { @@ -2951,25 +3225,29 @@ "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-03-12T08:11:12+00:00" + "time": "2025-07-30T17:13:41+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { @@ -2982,7 +3260,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -3007,7 +3285,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -3023,20 +3301,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/filesystem", - "version": "v7.2.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" + "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", - "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/edcbb768a186b5c3f25d0643159a787d3e63b7fd", + "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd", "shasum": "" }, "require": { @@ -3073,7 +3351,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.2.0" + "source": "https://github.com/symfony/filesystem/tree/v7.3.2" }, "funding": [ { @@ -3084,25 +3362,29 @@ "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": "2024-10-25T15:15:23+00:00" + "time": "2025-07-07T08:17:47+00:00" }, { "name": "symfony/finder", - "version": "v7.2.2", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "87a71856f2f56e4100373e92529eed3171695cfb" + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb", - "reference": "87a71856f2f56e4100373e92529eed3171695cfb", + "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe", + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe", "shasum": "" }, "require": { @@ -3137,7 +3419,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.2.2" + "source": "https://github.com/symfony/finder/tree/v7.3.2" }, "funding": [ { @@ -3148,16 +3430,20 @@ "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": "2024-12-30T19:00:17+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -3216,7 +3502,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -3227,6 +3513,10 @@ "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" @@ -3236,16 +3526,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { @@ -3294,7 +3584,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -3305,16 +3595,20 @@ "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": "2024-09-09T11:45:10+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -3375,7 +3669,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -3386,6 +3680,10 @@ "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" @@ -3475,16 +3773,16 @@ }, { "name": "symfony/process", - "version": "v7.2.5", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "87b7c93e57df9d8e39a093d32587702380ff045d" + "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/87b7c93e57df9d8e39a093d32587702380ff045d", - "reference": "87b7c93e57df9d8e39a093d32587702380ff045d", + "url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", "shasum": "" }, "require": { @@ -3516,7 +3814,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.2.5" + "source": "https://github.com/symfony/process/tree/v7.3.0" }, "funding": [ { @@ -3532,20 +3830,20 @@ "type": "tidelift" } ], - "time": "2025-03-13T12:21:46+00:00" + "time": "2025-04-17T09:11:12+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", "shasum": "" }, "require": { @@ -3563,7 +3861,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -3599,7 +3897,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" }, "funding": [ { @@ -3615,20 +3913,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2025-04-25T09:37:31+00:00" }, { "name": "symfony/string", - "version": "v7.2.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82" + "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82", - "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82", + "url": "https://api.github.com/repos/symfony/string/zipball/42f505aff654e62ac7ac2ce21033818297ca89ca", + "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca", "shasum": "" }, "require": { @@ -3686,7 +3984,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.2.0" + "source": "https://github.com/symfony/string/tree/v7.3.2" }, "funding": [ { @@ -3697,12 +3995,16 @@ "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": "2024-11-13T13:31:26+00:00" + "time": "2025-07-10T08:47:49+00:00" }, { "name": "textalk/websocket", @@ -3755,16 +4057,16 @@ }, { "name": "thecodingmachine/safe", - "version": "v3.0.2", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/thecodingmachine/safe.git", - "reference": "22ffad3248982a784f9870a37aeb2e522bd19645" + "reference": "2cdd579eeaa2e78e51c7509b50cc9fb89a956236" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/22ffad3248982a784f9870a37aeb2e522bd19645", - "reference": "22ffad3248982a784f9870a37aeb2e522bd19645", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/2cdd579eeaa2e78e51c7509b50cc9fb89a956236", + "reference": "2cdd579eeaa2e78e51c7509b50cc9fb89a956236", "shasum": "" }, "require": { @@ -3874,7 +4176,7 @@ "description": "PHP core functions that throw exceptions instead of returning FALSE on error", "support": { "issues": "https://github.com/thecodingmachine/safe/issues", - "source": "https://github.com/thecodingmachine/safe/tree/v3.0.2" + "source": "https://github.com/thecodingmachine/safe/tree/v3.3.0" }, "funding": [ { @@ -3890,7 +4192,7 @@ "type": "github" } ], - "time": "2025-02-19T19:23:00+00:00" + "time": "2025-05-14T06:15:44+00:00" }, { "name": "theseer/tokenizer", From 6af8470b0c6df7dd3d3a4923fe58bbacc024e678 Mon Sep 17 00:00:00 2001 From: Ryan Cooper <198979606+coopryan@users.noreply.github.com> Date: Thu, 28 Aug 2025 13:10:40 +0200 Subject: [PATCH 2/3] qa: mutation testing with phsptan --- .github/workflows/test.yml | 6 ++-- cli/server.php | 3 +- composer.json | 8 +++--- infection.json5 | 18 +++++------- phpunit.xml | 1 - server/src/Core/BuyMenu.php | 2 +- server/src/Core/HitBox.php | 2 ++ server/src/Core/PathFinder.php | 10 +++---- server/src/Core/PlayerStat.php | 5 ++++ server/src/Core/Score.php | 4 +-- server/src/Equipment/Grenade.php | 2 ++ server/src/Equipment/HighExplosive.php | 2 +- server/src/Equipment/Incendiary.php | 1 + server/src/Equipment/Kevlar.php | 7 +++-- server/src/Event/GrillEvent.php | 4 +-- server/src/Event/PauseStartEvent.php | 2 +- server/src/Event/ThrowEvent.php | 1 + server/src/Net/ClueSocket.php | 2 +- server/src/Net/NetConnector.php | 2 +- server/src/Net/TestConnector.php | 2 +- test/og/Game/RoundTest.php | 39 ++++++++++++++++++++------ test/og/Inventory/InventoryTest.php | 17 +++++++++++ test/og/Shooting/BombTest.php | 3 ++ test/og/Shooting/PlayerKillTest.php | 4 ++- test/og/Shooting/ShootTest.php | 1 + test/og/Unit/CollisionTest.php | 9 ++++++ 26 files changed, 109 insertions(+), 48 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 02f0327..168c0d6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,7 +1,7 @@ name: "Tests" on: push: - branches: [master] + branches: ['*'] paths-ignore: - '**.md' - 'cli/' @@ -24,7 +24,7 @@ jobs: persist-credentials: false - name: "Install Linux dependencies" - timeout-minutes: 2 + timeout-minutes: 4 run: | sudo apt-get update && sudo apt-get install -y \ composer @@ -36,7 +36,7 @@ jobs: composer install - name: "Run Composer check-full" - timeout-minutes: 10 + timeout-minutes: 20 run: | grep '"timeout": 20,' infection.json5 sed -i 's/"timeout": 20,/"timeout": 40,/' infection.json5 diff --git a/cli/server.php b/cli/server.php index 7b336d3..c849cd8 100644 --- a/cli/server.php +++ b/cli/server.php @@ -14,6 +14,7 @@ $port = (int)($argv[2] ?? 8080); $debug = in_array('--debug', $argv); $bindAddress = "udp://0.0.0.0:$port"; +$map = new Maps\DefaultMap(); ///// $settings = new ServerSetting($playersMax); // must be first for correctly setting the global tickRate (Util::$TICK_RATE) @@ -22,7 +23,7 @@ $logger->info("Preparing game for launch, please wait..."); $game = ($debug ? GameFactory::createDebug() : GameFactory::createDefaultCompetitive()); -$game->loadMap(new Maps\DefaultMap()); +$game->loadMap($map); $logger->info("Starting server on '{$bindAddress}', waiting maximum of '{$settings->warmupWaitSec}' sec for '{$playersMax}' player" . ($playersMax > 1 ? 's' : '') . " to connect."); $net = new ClueSocket($bindAddress); diff --git a/composer.json b/composer.json index a5aee2b..8b8b056 100644 --- a/composer.json +++ b/composer.json @@ -2,8 +2,8 @@ "scripts": { "post-install-cmd": "@php -d memory_limit=200M cli/generateNavmesh.php", "stan": "@php vendor/bin/phpstan --memory-limit=300M analyze", - "unit": "@php vendor/bin/phpunit -d memory_limit=70M", - "infection": "@php -d memory_limit=180M vendor/bin/infection --show-mutations --only-covered --threads=max --min-covered-msi=100", + "unit": "@php vendor/bin/phpunit -d memory_limit=200M", + "infection": "@php -d memory_limit=300M vendor/bin/infection --show-mutations --threads=max --min-covered-msi=100", "dev": "php cli/server.php 1 8080 --debug & php cli/udp-ws-bridge.php", "dev2": "php cli/server.php 2 8080 --debug & php cli/udp-ws-bridge.php & php cli/udp-ws-bridge.php 8082", "dev2c": "php cli/server.php 2 8080 --debug & php cli/udp-ws-bridge.php & sleep 2 && php cli/client.php acode 8080", @@ -28,7 +28,7 @@ "check-full": [ "@check", "@coverage", - "@infection-cache" + "@infection-cache --skip-initial-tests" ] }, "require": { @@ -60,7 +60,7 @@ "platform": { "php": "8.3" }, - "process-timeout": 720, + "process-timeout": 0, "allow-plugins": { "infection/extension-installer": false } diff --git a/infection.json5 b/infection.json5 index 40eea29..eac77c4 100644 --- a/infection.json5 +++ b/infection.json5 @@ -7,6 +7,8 @@ }, "timeout": 20, "testFramework": "phpunit", + "staticAnalysisTool": "phpstan", + "staticAnalysisToolOptions": "--memory-limit=300M", "mutators": { "global-ignoreSourceCodeByRegex": [ "\\$this->log\\(.*\\);", @@ -14,26 +16,22 @@ "GameException::invalid\\(.*\\);", "GameException::notImplementedYet\\(.*\\);", "assert\\(.+?\\);", + "if\\s*\\(\\$count > 10 && \\$count % 2 === 0\\)\\s\\{", ], "@default": true, "@conditional_boundary": false, "@conditional_negotiation": false, - "CastInt": false, "Continue_": false, "DecrementInteger": false, "FalseValue": false, - "IfNegation": false, "Increment": false, "IncrementInteger": false, - "LogicalAnd": false, "LogicalAndAllSubExprNegation": false, "LogicalOr": false, - "LogicalOrAllSubExprNegation": false, "Minus": false, - "Modulus": false, "Multiplication": false, - "NullSafeMethodCall": false, "Plus": false, + "ReturnRemoval": false, "RoundingFamily": false, "TrueValue": false, "ArrayItem": { @@ -69,11 +67,6 @@ "if\\s*\\(\\$this->ball->getResolutionAngleVertical\\(\\) > 0 && \\(.+", ], }, - "LogicalAndNegation": { - "ignoreSourceCodeByRegex": [ - "if\\s*\\(\\$count > 10 && \\$count % 2 === 0\\)\\s\\{", - ], - }, "LogicalNot": { "ignoreSourceCodeByRegex": [ ".+\\$this->lastMoveX === -\\$moveX.+", @@ -85,6 +78,9 @@ ], }, "MethodCallRemoval": { + "ignore": [ + "cs\\Equipment\\Bomb::unEquip", + ], "ignoreSourceCodeByRegex": [ "\\$this->setActiveFloor\\(.+\\);", "\\$prevPos->setFrom\\(\\$candidate\\);", diff --git a/phpunit.xml b/phpunit.xml index 7a48895..5c9804c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -25,7 +25,6 @@ server/src/ - server/src/Equipment/ server/src/Map/ server/src/Weapon/ diff --git a/server/src/Core/BuyMenu.php b/server/src/Core/BuyMenu.php index 5ac4319..3371b67 100644 --- a/server/src/Core/BuyMenu.php +++ b/server/src/Core/BuyMenu.php @@ -29,7 +29,7 @@ public function reset(bool $forAttackerStore, array $alreadyHaveItems): void foreach ($alreadyHaveItems as $item) { if ($item->getType() === ItemType::TYPE_GRENADE) { - $this->grenadeCount++; + $this->grenadeCount += $item->getQuantity(); } if (!isset($this->itemBuyCount[$item->getId()])) { diff --git a/server/src/Core/HitBox.php b/server/src/Core/HitBox.php index 31ec0d5..69931ef 100644 --- a/server/src/Core/HitBox.php +++ b/server/src/Core/HitBox.php @@ -34,6 +34,7 @@ public function reset(): void $this->damage = 0; } + /** @infection-ignore-all */ public function getHitAntiForce(Point $point): int { if ($this->type === HitBoxType::HEAD) { @@ -84,6 +85,7 @@ public function registerHit(Bullet $bullet): void } } + /** @infection-ignore-all */ private function calculateArmorDamage(BaseWeapon $shootItem, ArmorType $armorType, HitBoxType $hitBoxType): int { if ($armorType->hasNoArmor() || $hitBoxType === HitBoxType::LEG) { diff --git a/server/src/Core/PathFinder.php b/server/src/Core/PathFinder.php index 8077a95..ae458ea 100644 --- a/server/src/Core/PathFinder.php +++ b/server/src/Core/PathFinder.php @@ -9,7 +9,7 @@ final class PathFinder { private Graph $graph; - /** @var array */ + /** @var array */ private array $visited = []; private readonly int $obstacleOvercomeHeight; /** @var array */ @@ -105,8 +105,8 @@ public function findTile(Point $pointOnFloor, int $radius): ?Point return $floorNavmeshPoint; } - $maxDistance = $this->navigationMesh->tileSize * 2; - $maxY = $this->obstacleOvercomeHeight * 2; + $maxDistance = $this->navigationMesh->tileSize + $this->navigationMesh->tileSize; + $maxY = $this->obstacleOvercomeHeight + $this->obstacleOvercomeHeight; $checkAbove = function (Point $start, int $maxY, int $radius): ?Point { $yCandidate = $start->clone(); $navMeshCenter = $yCandidate->clone(); @@ -188,7 +188,7 @@ public function buildNavigationMesh(Point $start, int $objectHeight, int $maxNod continue; } - $this->visited[$currentKey] = true; + $this->visited[$currentKey] = null; $currentNode = $this->graph->getNodeById($currentKey); if ($currentNode === null) { $currentNode = new Node($currentKey, $current); @@ -210,7 +210,7 @@ public function buildNavigationMesh(Point $start, int $objectHeight, int $maxNod $this->graph->addEdge(new DirectedEdge($currentNode, $newNode, 1)); $queue->enqueue($newNeighbour); } - if (++$nodeCount === $maxNodeCount) { + if (++$nodeCount === $maxNodeCount) { // @codeCoverageIgnore GameException::notImplementedYet('MaxNodeCount hit - new map, tileSize or bad test (no boundary box, bad starting point)?'); // @codeCoverageIgnore } } diff --git a/server/src/Core/PlayerStat.php b/server/src/Core/PlayerStat.php index be45ced..2f819b4 100644 --- a/server/src/Core/PlayerStat.php +++ b/server/src/Core/PlayerStat.php @@ -41,6 +41,11 @@ public function getKills(): int return $this->kills; } + public function getHeadshotKills(): int + { + return $this->killsHeadshot; + } + public function getDeaths(): int { return $this->deaths; diff --git a/server/src/Core/Score.php b/server/src/Core/Score.php index 8a63113..eac530c 100644 --- a/server/src/Core/Score.php +++ b/server/src/Core/Score.php @@ -73,7 +73,7 @@ public function roundEnd(RoundEndEvent $event): void } if ($this->secondHalfScore !== []) { - $this->secondHalfScore[(int)$attackersWins]++; + $this->secondHalfScore[$attackersWins ? 1 : 0]++; } $this->lastRoundAttackerWins = $attackersWins; $this->roundsHistory[$this->roundNumber] = [ @@ -145,7 +145,7 @@ public function toArray(): array $scoreboard = [[], []]; foreach ($this->playerStats as $playerId => $playerStat) { $key = sprintf('%d-%d-%d', $playerStat->getKills(), $playerStat->getDamage(), $playerId); - $scoreboard[(int)$playerStat->isAttacker()][$key] = $playerStat->toArray(); + $scoreboard[$playerStat->isAttacker() ? 1 : 0][$key] = $playerStat->toArray(); } $teamDefenders = $scoreboard[0]; $teamAttackers = $scoreboard[1]; diff --git a/server/src/Equipment/Grenade.php b/server/src/Equipment/Grenade.php index 4137963..a78a86c 100644 --- a/server/src/Equipment/Grenade.php +++ b/server/src/Equipment/Grenade.php @@ -35,6 +35,7 @@ public function attackSecondary(Attackable $event): ?AttackResult return $event->fire(); } + /** @codeCoverageIgnore **/ public function getDamageValue(HitBoxType $hitBox, ArmorType $armor): int { GameException::invalid(); @@ -55,6 +56,7 @@ public function getSpeedMultiplier(): float return ($this->primaryAttack ? 1.0 : 0.5); } + /** @codeCoverageIgnore **/ public function createBullet(): Bullet { GameException::invalid(get_class($this)); diff --git a/server/src/Equipment/HighExplosive.php b/server/src/Equipment/HighExplosive.php index a303a46..ca33174 100644 --- a/server/src/Equipment/HighExplosive.php +++ b/server/src/Equipment/HighExplosive.php @@ -27,7 +27,7 @@ public function calculateDamage(int $distanceSquared, bool $harArmor): int { $distanceSquared = max(1, $distanceSquared); if ($distanceSquared >= self::MAX_BLAST_RADIUS_SQUARED) { - return 0; + return 0; // @codeCoverageIgnore } $damage = self::DAMAGE * (1 - ($distanceSquared / self::MAX_BLAST_RADIUS_SQUARED)); diff --git a/server/src/Equipment/Incendiary.php b/server/src/Equipment/Incendiary.php index 9690f3f..fab2f45 100644 --- a/server/src/Equipment/Incendiary.php +++ b/server/src/Equipment/Incendiary.php @@ -31,6 +31,7 @@ public function getMaxAreaMetersSquared(): int return 200_000; } + /** @codeCoverageIgnore **/ public function calculateDamage(bool $hasKevlar): int { return $hasKevlar ? 7 : 3; diff --git a/server/src/Equipment/Kevlar.php b/server/src/Equipment/Kevlar.php index 4f63c83..2cf6f30 100644 --- a/server/src/Equipment/Kevlar.php +++ b/server/src/Equipment/Kevlar.php @@ -29,6 +29,7 @@ public function repairArmor(): void $this->armor = 100; } + /** @codeCoverageIgnore **/ public function lowerArmor(int $armorDamage): void { assert($armorDamage >= 0); @@ -86,12 +87,12 @@ public function canPurchaseMultipleTime(Item $newSlotItem): bool public function getPrice(?Item $alreadyHaveSlotItem = null): int { /** @var ?self $alreadyHaveSlotItem */ - if ($alreadyHaveSlotItem && $this->type === ArmorType::BODY_AND_HEAD && $alreadyHaveSlotItem->type === ArmorType::BODY && $alreadyHaveSlotItem->armor === 100) { - return 350; - } if ($alreadyHaveSlotItem && $alreadyHaveSlotItem->type === ArmorType::BODY_AND_HEAD) { return 650; } + if ($alreadyHaveSlotItem && $this->type === ArmorType::BODY_AND_HEAD && $alreadyHaveSlotItem->type === ArmorType::BODY && $alreadyHaveSlotItem->armor === 100) { + return 350; + } return $this->bodyPlusHelmet ? 1000 : 650; } diff --git a/server/src/Event/GrillEvent.php b/server/src/Event/GrillEvent.php index 99124a0..92787cf 100644 --- a/server/src/Event/GrillEvent.php +++ b/server/src/Event/GrillEvent.php @@ -4,7 +4,6 @@ use cs\Core\Column; use cs\Core\Point; -use cs\Core\Setting; use cs\Enum\SoundType; final class GrillEvent extends VolumetricEvent @@ -35,7 +34,6 @@ protected function shrinkPart(Column $column): void protected function expandPart(Point $center): Column { - assert($this->partHeight < Setting::playerHeadHeightCrouch()); $flame = new Column($center, $this->partRadius, $this->partHeight); if ($this->world->flameCanIgnite($flame)) { $soundEvent = new SoundEvent($flame->center, SoundType::FLAME_SPAWN); @@ -57,7 +55,7 @@ public function extinguish(Column $flame): void public function canHitPlayer(int $playerId, int $tickId): bool { - return (($this->playerTickHits[$playerId] ?? 0) + $this->damageCoolDownTickCount <= $tickId); + return (($this->playerTickHits[$playerId] ?? -$this->damageCoolDownTickCount) + $this->damageCoolDownTickCount <= $tickId); } public function playerHit(int $playerId, int $tickId): void diff --git a/server/src/Event/PauseStartEvent.php b/server/src/Event/PauseStartEvent.php index 6be9197..30f29b0 100644 --- a/server/src/Event/PauseStartEvent.php +++ b/server/src/Event/PauseStartEvent.php @@ -10,7 +10,7 @@ final class PauseStartEvent extends TimeoutEvent { /** @param Closure(static,int):void $callback */ - public function __construct(private Game $game, private PauseReason $reason, Closure $callback, int $timeoutMs) + public function __construct(private Game $game, public readonly PauseReason $reason, Closure $callback, int $timeoutMs) { parent::__construct($callback, $timeoutMs); } diff --git a/server/src/Event/ThrowEvent.php b/server/src/Event/ThrowEvent.php index 929fd56..ab80174 100644 --- a/server/src/Event/ThrowEvent.php +++ b/server/src/Event/ThrowEvent.php @@ -143,6 +143,7 @@ public function process(int $tick): void continue; } + /** @infection-ignore-all */ if ( $this->ball->getResolutionAngleVertical() > 0 && ($this->item instanceof Flammable || $this->item instanceof Smoke) diff --git a/server/src/Net/ClueSocket.php b/server/src/Net/ClueSocket.php index b931763..947defe 100644 --- a/server/src/Net/ClueSocket.php +++ b/server/src/Net/ClueSocket.php @@ -36,7 +36,7 @@ public function sendTo(Client $client, string &$msg): void } } - public function receive(?string &$peerAddress, ?int &$peerPort, int $readMaxBytes = 100): ?string + public function receive(?string &$peerAddress, ?int &$peerPort, int $readMaxBytes): ?string { $ret = @socket_recvfrom($this->resource, $buffer, $readMaxBytes, 0, $peerAddress, $peerPort); // @phpstan-ignore-line if ($ret === false) { diff --git a/server/src/Net/NetConnector.php b/server/src/Net/NetConnector.php index 3c03013..82a870a 100644 --- a/server/src/Net/NetConnector.php +++ b/server/src/Net/NetConnector.php @@ -8,7 +8,7 @@ interface NetConnector /** * @throws NetException */ - public function receive(?string &$peerAddress, ?int &$peerPort, int $readMaxBytes = 100): ?string; + public function receive(?string &$peerAddress, ?int &$peerPort, int $readMaxBytes): ?string; /** * @throws NetException diff --git a/server/src/Net/TestConnector.php b/server/src/Net/TestConnector.php index a7eb4d7..824936e 100644 --- a/server/src/Net/TestConnector.php +++ b/server/src/Net/TestConnector.php @@ -20,7 +20,7 @@ public function __construct(private array $requests) { } - public function receive(?string &$peerAddress, ?int &$peerPort, int $readMaxBytes = 100): ?string + public function receive(?string &$peerAddress, ?int &$peerPort, int $readMaxBytes): ?string { if (!$this->expectReceive) { return null; diff --git a/test/og/Game/RoundTest.php b/test/og/Game/RoundTest.php index 70ec630..0702d66 100644 --- a/test/og/Game/RoundTest.php +++ b/test/og/Game/RoundTest.php @@ -12,8 +12,10 @@ use cs\Enum\Color; use cs\Enum\GameOverReason; use cs\Enum\InventorySlot; +use cs\Enum\PauseReason; use cs\Enum\SoundType; use cs\Equipment\Bomb; +use cs\Equipment\DefuseKit; use cs\Event\GameOverEvent; use cs\Event\KillEvent; use cs\Event\PauseEndEvent; @@ -425,7 +427,7 @@ function () use ($enemy) { public function testMultipleRoundsScoreAndEvents(): void { - $maxRounds = 4; + $maxRounds = 5; $gameProperty = $this->createNoPauseGameProperty($maxRounds); $gameProperty->bomb_plant_time_ms = 0; $gameProperty->bomb_defuse_time_ms = 0; @@ -443,13 +445,16 @@ public function testMultipleRoundsScoreAndEvents(): void $eventCounts = []; $eventObjects = []; - $game->onEvents(function (array $events) use (&$eventCounts, &$eventObjects): void { + $game->onEvents(function (array $events) use ($game, &$eventCounts, &$eventObjects): void { foreach ($events as $event) { if (!isset($eventCounts[$event::class])) { $eventCounts[$event::class] = 0; } $eventCounts[$event::class]++; $eventObjects[$event::class] = $event; + if ($event instanceof PauseStartEvent && $event->reason === PauseReason::HALF_TIME) { + $this->assertTrue($game->isPaused()); + } } }); @@ -475,11 +480,18 @@ function () use ($p2) { fn() => $this->assertTrue($p1->buyItem(BuyMenuItem::GRENADE_FLASH)), fn() => $p1->buyItem(BuyMenuItem::DEFUSE_KIT), fn() => $this->assertTrue($p1->getInventory()->has(InventorySlot::SLOT_KIT->value)), + function () use ($p1) { + $kit = $p1->getInventory()->getItemSlot(InventorySlot::SLOT_KIT); + $this->assertInstanceOf(DefuseKit::class, $kit); + $this->assertFalse($kit->isUserDroppable()); + $this->assertFalse($kit->canPurchaseMultipleTime($kit)); + }, fn() => $p2->buyItem(BuyMenuItem::DEFUSE_KIT), fn() => $this->assertFalse($p2->getInventory()->has(InventorySlot::SLOT_KIT->value)), fn() => $this->assertTrue($p2->buyItem(BuyMenuItem::GRENADE_FLASH)), fn() => $this->assertTrue($p2->buyItem(BuyMenuItem::GRENADE_FLASH)), fn() => $this->assertTrue($p2->buyItem(BuyMenuItem::GRENADE_DECOY)), + fn() => $this->assertTrue($p2->buyItem(BuyMenuItem::GRENADE_HE)), fn() => $p1->suicide(), fn() => $this->assertSame(4, $game->getRoundNumber()), function () use ($p1, $p2) { @@ -489,9 +501,14 @@ function () use ($p1, $p2) { $this->assertTrue($p2->getInventory()->has(InventorySlot::SLOT_BOMB->value)); $this->assertTrue($p2->getInventory()->has(InventorySlot::SLOT_GRENADE_FLASH->value)); $this->assertTrue($p2->getInventory()->has(InventorySlot::SLOT_GRENADE_DECOY->value)); + $this->assertTrue($p2->getInventory()->has(InventorySlot::SLOT_GRENADE_HE->value)); + $this->assertFalse($p2->buyItem(BuyMenuItem::GRENADE_MOLOTOV)); $this->assertFalse($p1->getInventory()->has(InventorySlot::SLOT_GRENADE_FLASH->value)); }, $this->waitNTicks(800), + fn() => $this->assertTrue($game->getScore()->isTie()), + fn() => $p1->suicide(), + $this->endGame(), ]); $this->assertNotEmpty($eventCounts); @@ -502,11 +519,11 @@ function () use ($p1, $p2) { $this->assertSame($maxRounds, $eventCounts[RoundEndEvent::class]); $this->assertSame(2, $eventCounts[PlantEvent::class]); $this->assertSame($maxRounds - 2, $eventCounts[RoundEndCoolDownEvent::class]); - $this->assertTrue($game->getScore()->isTie()); + $this->assertFalse($game->getScore()->isTie()); $expectedScoreBoard = [ - 'score' => [2, 2], - 'lossBonus' => [1400, 1400], + 'score' => [2, 3], + 'lossBonus' => [1400, 1900], 'history' => [ 1 => [ 'attackersWins' => false, @@ -532,16 +549,22 @@ function () use ($p1, $p2) { 'scoreAttackers' => 2, 'scoreDefenders' => 2, ], + 5 => [ + 'attackersWins' => true, + 'reason' => 0, + 'scoreAttackers' => 3, + 'scoreDefenders' => 2, + ], ], 'firstHalfScore' => [1, 1], - 'secondHalfScore' => [1, 1], + 'secondHalfScore' => [1, 2], 'halfTimeRoundNumber' => 2, 'scoreboard' => [ [ [ 'id' => 1, - 'kills' => -1, - 'deaths' => 1, + 'kills' => -2, + 'deaths' => 2, 'damage' => 0, ], ], diff --git a/test/og/Inventory/InventoryTest.php b/test/og/Inventory/InventoryTest.php index ec60e2a..c3e85a3 100644 --- a/test/og/Inventory/InventoryTest.php +++ b/test/og/Inventory/InventoryTest.php @@ -18,6 +18,7 @@ use cs\Equipment\Decoy; use cs\Equipment\Flashbang; use cs\Equipment\HighExplosive; +use cs\Equipment\Kevlar; use cs\Event\SoundEvent; use cs\Weapon\Knife; use cs\Weapon\PistolGlock; @@ -227,6 +228,18 @@ public function testPlayerBuyAndDropAndUseForPickup(): void fn(Player $p) => $p->getSight()->lookHorizontal(45), fn(Player $p) => $p->use(), fn(Player $p) => $this->assertTrue($p->getInventory()->has(InventorySlot::SLOT_SECONDARY->value)), + // non primary nor secondary item already had in inventory pickup + fn(Player $p) => $p->getSight()->lookVertical(-90), + fn(Player $p) => $this->assertTrue($p->buyItem(BuyMenuItem::GRENADE_FLASH)), + fn(Player $p) => $this->assertTrue($p->buyItem(BuyMenuItem::GRENADE_FLASH)), + fn(Player $p) => $this->assertSame(2, $p->getEquippedItem()->getQuantity()), + fn(Player $p) => $this->assertNotNull($p->dropEquippedItem()), + fn(Player $p) => $this->assertSame(1, $p->getEquippedItem()->getQuantity()), + fn(Player $p) => $this->assertTrue($p->getInventory()->has(InventorySlot::SLOT_GRENADE_FLASH->value)), + $this->waitNTicks(200), + fn(Player $p) => $this->assertSame(1, $p->getEquippedItem()->getQuantity()), + fn(Player $p) => $p->use(), + fn(Player $p) => $this->assertSame(2, $p->getEquippedItem()->getQuantity()), $this->endGame(), ]); } @@ -692,6 +705,10 @@ function (Player $p): void { $this->assertSame(1651, $p->getMoney()); $this->assertSame(ArmorType::BODY, $p->getArmorType()); $this->assertSame(100, $p->getArmorValue()); + $kevlar = $p->getInventory()->getItemSlot(InventorySlot::SLOT_KEVLAR); + $this->assertInstanceOf(Kevlar::class, $kevlar); + $this->assertFalse($kevlar->isUserDroppable()); + $this->assertFalse($kevlar->canPurchaseMultipleTime($kevlar)); }, fn(Player $p) => $p->lowerArmor(10), function (Player $p): void { diff --git a/test/og/Shooting/BombTest.php b/test/og/Shooting/BombTest.php index d803b7b..c7a284d 100644 --- a/test/og/Shooting/BombTest.php +++ b/test/og/Shooting/BombTest.php @@ -178,6 +178,7 @@ public function testBombPlantReset(): void $gameProperty->bomb_plant_time_ms = self::TEST_TICK_RATE * 2; $gameProperty->bomb_explode_time_ms = 100; $game = $this->createTestGame(null, $gameProperty); + $game->addPlayer(new Player(2, Color::GREEN, false)); $start = new Point(321, 0, 300); $this->playPlayer($game, [ @@ -203,6 +204,8 @@ function (Player $p) use ($game) { function (Player $p) use ($game) { $this->assertFalse($game->isBombActive()); $p->attack(); + $this->assertTrue($p->isPlantingOrDefusing()); + $this->assertFalse($game->getPlayer(2)->isPlantingOrDefusing()); $this->assertFalse($game->isBombActive()); }, fn(Player $p) => $p->attack(), diff --git a/test/og/Shooting/PlayerKillTest.php b/test/og/Shooting/PlayerKillTest.php index 7d73622..bd98957 100644 --- a/test/og/Shooting/PlayerKillTest.php +++ b/test/og/Shooting/PlayerKillTest.php @@ -437,6 +437,7 @@ public function testArmorShooting(): void $p2->getSight()->look(-90, -90); $p2->setPosition(new Point(300, 0, 500)); $p2->buyItem(BuyMenuItem::KEVLAR_BODY); + $this->assertSame(150, $p2->getMoney()); $game->addPlayer(new Player(3, Color::ORANGE, false)); $game->getPlayer(3)->setPosition(new Point(350, 0, 500)); @@ -458,7 +459,7 @@ function (Player $p) use ($p2) { $this->assertSame(HitBoxType::BACK, $bodyShot->getType()); $this->assertLessThan(100, $p2->getHealth()); - $this->assertLessThan(100, $p2->getArmorValue()); + $this->assertSame(90, $p2->getArmorValue()); $this->assertTrue($p->isAlive()); $this->assertTrue($p2->isAlive()); }, @@ -610,6 +611,7 @@ function (Player $p) { $this->assertSame(3, $killEventsCount); $this->assertSame(3, $game->getScore()->getScoreDefenders()); + $this->assertSame(3, $game->getScore()->getPlayerStat(2)->getHeadshotKills()); $this->assertSame(3 + 1, $game->getRoundNumber()); $this->assertSame(6500, $p1->getMoney()); $this->assertSame(11450, $player2->getMoney()); diff --git a/test/og/Shooting/ShootTest.php b/test/og/Shooting/ShootTest.php index fc425cd..03eaab3 100644 --- a/test/og/Shooting/ShootTest.php +++ b/test/og/Shooting/ShootTest.php @@ -315,6 +315,7 @@ public function testDamageLowOnRangeMaxDamage(): void $this->assertSame(99, $game->getPlayer(2)->getHealth()); $this->assertSame(1, $bulletHitHeadShotsCount); + $this->assertSame(0, $game->getScore()->getPlayerStat(1)->getHeadshotKills()); } } diff --git a/test/og/Unit/CollisionTest.php b/test/og/Unit/CollisionTest.php index 87200cd..e16cda0 100644 --- a/test/og/Unit/CollisionTest.php +++ b/test/og/Unit/CollisionTest.php @@ -47,9 +47,14 @@ public function testPointWithCircle(): void $this->assertTrue(Collision::pointWithCircle(10, 10, 10, 10, 1)); $this->assertTrue(Collision::pointWithCircle(11, 10, 10, 10, 1)); $this->assertTrue(Collision::pointWithCircle(10, 11, 10, 10, 1)); + $this->assertTrue(Collision::pointWithCircle(15, 10, 10, 10, 5)); + $this->assertTrue(Collision::pointWithCircle(10, 15, 10, 10, 6)); $this->assertFalse(Collision::pointWithCircle(10, 13, 10, 10, 2)); $this->assertFalse(Collision::pointWithCircle(13, 10, 10, 10, 2)); + $this->assertFalse(Collision::pointWithCircle(15, 15, 10, 10, 4)); + $this->assertFalse(Collision::pointWithCircle(15, 10, 10, 10, 4)); + $this->assertFalse(Collision::pointWithCircle(10, 15, 10, 10, 4)); } public function testCircleWithPlaneFalse(): void @@ -360,6 +365,10 @@ public function testBoxWithBox(): void new Point(-5, 0, -5), new Point(5, 4, 5), new Point(1, -6, -5), new Point(3, -3, -3), )); + $this->assertFalse(Collision::boxWithBox( + new Point(0, 0, 0), new Point(5, 1, 1), + new Point(-10, 0, 0), new Point(-3, 1, 1), + )); } From c29ab06d5c6097fb7c71d16d5a7795e79e2c9b26 Mon Sep 17 00:00:00 2001 From: Ryan Cooper <198979606+coopryan@users.noreply.github.com> Date: Sat, 30 Aug 2025 14:52:46 +0200 Subject: [PATCH 3/3] refactor Package handling --- server/src/Core/Game.php | 2 +- server/src/Core/World.php | 46 +++++------------ server/src/Equipment/Bomb.php | 63 +++++++++++++++++++----- server/src/Traits/Player/AttackTrait.php | 2 +- 4 files changed, 63 insertions(+), 50 deletions(-) diff --git a/server/src/Core/Game.php b/server/src/Core/Game.php index 4604340..2d2c2c0 100644 --- a/server/src/Core/Game.php +++ b/server/src/Core/Game.php @@ -29,7 +29,7 @@ class Game { - private Bomb $bomb; + public readonly Bomb $bomb; private World $world; private Score $score; private GameState $state; diff --git a/server/src/Core/World.php b/server/src/Core/World.php index d1f95af..ba9c2f7 100644 --- a/server/src/Core/World.php +++ b/server/src/Core/World.php @@ -46,14 +46,12 @@ final class World /** @var array */ private array $activeMolotovs = []; private Bomb $bomb; - private int $lastBombActionTick = -1; - private int $lastBombPlayerId = -1; - private int $bombActionTickBuffer = 1; private ?NavigationMesh $grenadeNavigationMesh = null; private ?PathFinder $grenadePathFinder = null; public function __construct(private Game $game) { + $this->bomb = $game->bomb; } public function roundReset(): void @@ -293,21 +291,11 @@ public function playerUse(Player $player): void if (!$player->isPlayingOnAttackerSide() && $this->game->isBombActive() && $this->canBeSeen($player, $this->bomb->getPosition(), self::BOMB_RADIUS, self::BOMB_DEFUSE_MAX_DISTANCE) ) { - $bomb = $this->bomb; - $tickId = $this->getTickId(); - $playerId = $player->getId(); - if ($playerId !== $this->lastBombPlayerId || $this->lastBombActionTick + $this->bombActionTickBuffer < $tickId) { - $player->stop(); - $bomb->startDefusing($tickId, $player->hasDefuseKit()); + $defused = $this->bomb->tryDefuse($player, $this->getTickId()); + if ($defused === null) { $soundEvent = new SoundEvent($player->getPositionClone()->addY(10), SoundType::BOMB_DEFUSING); - $this->makeSound($soundEvent->setPlayer($player)->setItem($bomb)); - } - $this->lastBombActionTick = $tickId; - $this->lastBombPlayerId = $playerId; - - if ($bomb->isDefused($tickId)) { - $this->lastBombActionTick = -1; - $this->lastBombPlayerId = -1; + $this->makeSound($soundEvent->setPlayer($player)->setItem($this->bomb)); + } elseif ($defused === true) { $this->game->bombDefused($player); } return; @@ -802,37 +790,25 @@ private function playerHit(Point $hitPoint, Player $playerHit, Player $playerCul } } - public function tryPlantBomb(Player $player, Bomb $bomb): void + public function tryPlantBomb(Player $player): void { if (!$this->canPlant($player)) { return; } - $tickId = $this->getTickId(); - $playerId = $player->getId(); - if ($playerId !== $this->lastBombPlayerId || $this->lastBombActionTick + $this->bombActionTickBuffer < $tickId) { - $player->stop(); - $bomb->startPlanting($tickId); + $planted = $this->bomb->tryPlant($player, $this->getTickId()); + if ($planted === null) { $soundEvent = new SoundEvent($player->getPositionClone()->addY(10), SoundType::BOMB_PLANTING); - $this->makeSound($soundEvent->setPlayer($player)->setItem($bomb)); - } - $this->lastBombActionTick = $this->getTickId(); - $this->lastBombPlayerId = $playerId; - - if ($bomb->isPlanted($tickId)) { + $this->makeSound($soundEvent->setPlayer($player)->setItem($this->bomb)); + } elseif ($planted === true) { $player->equip($player->getInventory()->removeBomb()); - $bomb->setPosition($player->getPositionClone()); $this->game->bombPlanted($player); - - $this->bomb = $bomb; - $this->lastBombActionTick = -1; - $this->lastBombPlayerId = -1; } } public function isPlantingOrDefusing(Player $player): bool { - return ($this->lastBombPlayerId === $player->getId() && $this->bombActionTickBuffer >= $this->getTickId() - $this->lastBombActionTick); + return $this->bomb->isPlantingOrDefusing($player->getId(), $this->getTickId()); } public function isWallOrFloorCollision(Point $start, Point $candidate, int $radius): bool diff --git a/server/src/Equipment/Bomb.php b/server/src/Equipment/Bomb.php index 4ca61e7..98b1c20 100644 --- a/server/src/Equipment/Bomb.php +++ b/server/src/Equipment/Bomb.php @@ -13,17 +13,22 @@ class Bomb extends BaseEquipment public const int equipReadyTimeMs = 80; private Point $position; - private int $plantTickStart = 0; + private int $plantTickStart; private int $plantTickCountMax; - private int $defuseTickStart = 0; + private int $defuseTickStart; private int $defuseTickCountMax; private int $tickToDefuseCount; + private int $lastBombActionTick = -1; + private int $lastBombPlayerId = -1; - public function __construct(int $plantTimeMs, int $defuseTimeMs, private int $maxBlastDistance = 1000) + public function __construct(int $plantTimeMs, int $defuseTimeMs, private int $maxBlastDistance = 1000, private int $bombActionTickBuffer = 1) { parent::__construct(); + $this->position = new Point(); $this->plantTickCountMax = Util::millisecondsToFrames($plantTimeMs); + $this->plantTickStart = -$this->plantTickCountMax; $this->defuseTickCountMax = Util::millisecondsToFrames($defuseTimeMs); + $this->defuseTickStart = -$this->defuseTickCountMax; } public function setMaxBlastDistance(int $maxBlastDistance): void @@ -56,30 +61,61 @@ public function unEquip(): void $this->reset(); } - public function startPlanting(int $tickStart): void + public function tryPlant(Player $player, int $tickId): ?bool { - $this->plantTickStart = $tickStart; + $planted = false; + $playerId = $player->getId(); + if ($playerId !== $this->lastBombPlayerId || $this->lastBombActionTick + $this->bombActionTickBuffer < $tickId) { + $player->stop(); + $this->plantTickStart = $tickId; + $planted = null; + } + $this->lastBombActionTick = $tickId; + $this->lastBombPlayerId = $playerId; + + if ($this->isPlanted($tickId)) { + $this->position->setFrom($player->getReferenceToPosition()); + $this->lastBombActionTick = -1; + $this->lastBombPlayerId = -1; + $planted = true; + } + return $planted; } - public function isPlanted(int $tickId): bool + public function tryDefuse(Player $player, int $tickId): ?bool { - return ($tickId - $this->plantTickStart >= $this->plantTickCountMax); + $defused = false; + $playerId = $player->getId(); + if ($playerId !== $this->lastBombPlayerId || $this->lastBombActionTick + $this->bombActionTickBuffer < $tickId) { + $player->stop(); + $this->defuseTickStart = $tickId; + $this->tickToDefuseCount = $player->hasDefuseKit() ? (int)ceil($this->defuseTickCountMax / 2) : $this->defuseTickCountMax;; + $defused = null; + } + $this->lastBombActionTick = $tickId; + $this->lastBombPlayerId = $playerId; + + if ($this->isDefused($tickId)) { + $this->lastBombActionTick = -1; + $this->lastBombPlayerId = -1; + $defused = true; + } + return $defused; } - public function startDefusing(int $tickId, bool $hasDefuseKit): void + private function isPlanted(int $tickId): bool { - $this->defuseTickStart = $tickId; - $this->tickToDefuseCount = $hasDefuseKit ? (int)ceil($this->defuseTickCountMax / 2) : $this->defuseTickCountMax;; + return ($tickId - $this->plantTickStart >= $this->plantTickCountMax); } - public function isDefused(int $tickId): bool + private function isDefused(int $tickId): bool { return ($tickId - $this->defuseTickStart >= $this->tickToDefuseCount); } - public function setPosition(Point $position): void + public function isPlantingOrDefusing(int $playerId, int $tickId): bool { - $this->position = $position; + return ($this->lastBombPlayerId === $playerId && $this->bombActionTickBuffer >= $tickId - $this->lastBombActionTick); } public function getPosition(): Point @@ -87,6 +123,7 @@ public function getPosition(): Point return $this->position; } + /** @codeCoverageIgnore **/ public function explodeDamageToPlayer(Player $player): void { $distanceSquared = Util::distanceSquared($player->getPositionClone(), $this->position); diff --git a/server/src/Traits/Player/AttackTrait.php b/server/src/Traits/Player/AttackTrait.php index fd4e8f4..a5a97d4 100644 --- a/server/src/Traits/Player/AttackTrait.php +++ b/server/src/Traits/Player/AttackTrait.php @@ -31,7 +31,7 @@ public function attack(): ?AttackResult if ($item instanceof Bomb) { if ($item->canAttack($this->world->getTickId())) { - $this->world->tryPlantBomb($this, $item); + $this->world->tryPlantBomb($this); } return null; }