From c00bfafca53bceb2e7e50c7961714a3ca9d917cd Mon Sep 17 00:00:00 2001 From: Nicolas PHILIPPE Date: Fri, 18 Oct 2024 17:08:00 +0200 Subject: [PATCH 1/2] refactor: fix proxy system --- .github/workflows/ci.yml | 11 +- bin/tools/phpstan/composer.lock | 36 +- bin/tools/psalm/composer.lock | 462 +++++++++--------- composer.json | 8 +- docs/index.rst | 8 +- phpstan.neon | 2 +- psalm.xml | 11 +- src/Factory.php | 42 +- src/FactoryCollection.php | 2 +- src/ObjectFactory.php | 18 +- src/Persistence/PersistentObjectFactory.php | 80 +-- .../PersistentProxyObjectFactory.php | 73 ++- src/Persistence/Proxy.php | 35 ++ src/Persistence/ProxyRepositoryDecorator.php | 44 ++ src/Persistence/RepositoryDecorator.php | 9 +- src/phpunit_helper.php | 2 +- stubs/PersistentObjectFactory.php | 70 --- stubs/PersistentProxyObjectFactory.php | 66 --- stubs/functions.php | 26 - stubs/phpstan/ObjectFactory.php | 69 +++ stubs/phpstan/PersistentObjectFactory.php | 118 +++++ .../phpstan/PersistentProxyObjectFactory.php | 134 +++++ stubs/phpstan/functions.php | 26 + stubs/psalm/ObjectFactory.php | 63 +++ stubs/psalm/PersistentObjectFactory.php | 114 +++++ stubs/psalm/PersistentProxyObjectFactory.php | 122 +++++ stubs/psalm/functions.php | 39 ++ .../Mongo/EmbeddableDocumentFactoryTest.php | 2 + .../FixProxyFactoryMethodsReturnType.php | 63 +++ utils/psalm/FoundryPlugin.php | 16 + 30 files changed, 1219 insertions(+), 552 deletions(-) delete mode 100644 stubs/PersistentObjectFactory.php delete mode 100644 stubs/PersistentProxyObjectFactory.php delete mode 100644 stubs/functions.php create mode 100644 stubs/phpstan/ObjectFactory.php create mode 100644 stubs/phpstan/PersistentObjectFactory.php create mode 100644 stubs/phpstan/PersistentProxyObjectFactory.php create mode 100644 stubs/phpstan/functions.php create mode 100644 stubs/psalm/ObjectFactory.php create mode 100644 stubs/psalm/PersistentObjectFactory.php create mode 100644 stubs/psalm/PersistentProxyObjectFactory.php create mode 100644 stubs/psalm/functions.php create mode 100644 utils/psalm/FixProxyFactoryMethodsReturnType.php create mode 100644 utils/psalm/FoundryPlugin.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5df09b069..0d5bced4d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -242,12 +242,11 @@ jobs: - name: Run static analysis run: bin/tools/phpstan/vendor/phpstan/phpstan/phpstan analyse - # there are some problem with psalm and bc layer, around proxy - # - name: Install Psalm - # run: composer bin psalm install - # - # - name: Run Psalm on factories generated with maker - # run: bin/tools/psalm/vendor/vimeo/psalm/psalm + - name: Install Psalm + run: composer bin psalm install + + - name: Run Psalm on factories generated with maker + run: bin/tools/psalm/vendor/vimeo/psalm/psalm fixcs: name: Run php-cs-fixer diff --git a/bin/tools/phpstan/composer.lock b/bin/tools/phpstan/composer.lock index a8b309c85..153ae99e8 100644 --- a/bin/tools/phpstan/composer.lock +++ b/bin/tools/phpstan/composer.lock @@ -117,16 +117,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.11.10", + "version": "1.12.7", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "640410b32995914bde3eed26fa89552f9c2c082f" + "reference": "dc2b9976bd8b0f84ec9b0e50cc35378551de7af0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/640410b32995914bde3eed26fa89552f9c2c082f", - "reference": "640410b32995914bde3eed26fa89552f9c2c082f", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc2b9976bd8b0f84ec9b0e50cc35378551de7af0", + "reference": "dc2b9976bd8b0f84ec9b0e50cc35378551de7af0", "shasum": "" }, "require": { @@ -171,20 +171,20 @@ "type": "github" } ], - "time": "2024-08-08T09:02:50+00:00" + "time": "2024-10-18T11:12:07+00:00" }, { "name": "phpstan/phpstan-doctrine", - "version": "1.5.0", + "version": "1.5.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-doctrine.git", - "reference": "caa046bd6152818e781260fb3a7a96d6b0fadeed" + "reference": "38db3bad8f1567d7bf64806738d724261f8a2b5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/caa046bd6152818e781260fb3a7a96d6b0fadeed", - "reference": "caa046bd6152818e781260fb3a7a96d6b0fadeed", + "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/38db3bad8f1567d7bf64806738d724261f8a2b5c", + "reference": "38db3bad8f1567d7bf64806738d724261f8a2b5c", "shasum": "" }, "require": { @@ -241,9 +241,9 @@ "description": "Doctrine extensions for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-doctrine/issues", - "source": "https://github.com/phpstan/phpstan-doctrine/tree/1.5.0" + "source": "https://github.com/phpstan/phpstan-doctrine/tree/1.5.3" }, - "time": "2024-08-05T13:47:07+00:00" + "time": "2024-09-01T13:17:34+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -299,22 +299,22 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "1.4.6", + "version": "1.4.10", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "e909a075d69e0d4db262ac3407350ae2c6b6ab5f" + "reference": "f7d5782044bedf93aeb3f38e09c91148ee90e5a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/e909a075d69e0d4db262ac3407350ae2c6b6ab5f", - "reference": "e909a075d69e0d4db262ac3407350ae2c6b6ab5f", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/f7d5782044bedf93aeb3f38e09c91148ee90e5a1", + "reference": "f7d5782044bedf93aeb3f38e09c91148ee90e5a1", "shasum": "" }, "require": { "ext-simplexml": "*", "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11.7" + "phpstan/phpstan": "^1.12" }, "conflict": { "symfony/framework-bundle": "<3.0" @@ -365,9 +365,9 @@ "description": "Symfony Framework extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-symfony/issues", - "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.6" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.10" }, - "time": "2024-07-16T11:48:54+00:00" + "time": "2024-09-26T18:14:50+00:00" } ], "packages-dev": [], diff --git a/bin/tools/psalm/composer.lock b/bin/tools/psalm/composer.lock index 13d03967a..a61d622fb 100644 --- a/bin/tools/psalm/composer.lock +++ b/bin/tools/psalm/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "amphp/amp", - "version": "v2.6.2", + "version": "v2.6.4", "source": { "type": "git", "url": "https://github.com/amphp/amp.git", - "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb" + "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", - "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", + "url": "https://api.github.com/repos/amphp/amp/zipball/ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", + "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", "shasum": "" }, "require": { @@ -29,8 +29,8 @@ "ext-json": "*", "jetbrains/phpstorm-stubs": "^2019.3", "phpunit/phpunit": "^7 | ^8 | ^9", - "psalm/phar": "^3.11@dev", - "react/promise": "^2" + "react/promise": "^2", + "vimeo/psalm": "^3.12" }, "type": "library", "extra": { @@ -85,7 +85,7 @@ "support": { "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v2.6.2" + "source": "https://github.com/amphp/amp/tree/v2.6.4" }, "funding": [ { @@ -93,20 +93,20 @@ "type": "github" } ], - "time": "2022-02-20T17:52:18+00:00" + "time": "2024-03-21T18:52:26+00:00" }, { "name": "amphp/byte-stream", - "version": "v1.8.1", + "version": "v1.8.2", "source": { "type": "git", "url": "https://github.com/amphp/byte-stream.git", - "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd" + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/byte-stream/zipball/acbd8002b3536485c997c4e019206b3f10ca15bd", - "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/4f0e968ba3798a423730f567b1b50d3441c16ddc", + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc", "shasum": "" }, "require": { @@ -122,11 +122,6 @@ "psalm/phar": "^3.11.4" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, "autoload": { "files": [ "lib/functions.php" @@ -150,7 +145,7 @@ } ], "description": "A stream abstraction to make working with non-blocking I/O simple.", - "homepage": "http://amphp.org/byte-stream", + "homepage": "https://amphp.org/byte-stream", "keywords": [ "amp", "amphp", @@ -160,9 +155,8 @@ "stream" ], "support": { - "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/byte-stream/issues", - "source": "https://github.com/amphp/byte-stream/tree/v1.8.1" + "source": "https://github.com/amphp/byte-stream/tree/v1.8.2" }, "funding": [ { @@ -170,34 +164,42 @@ "type": "github" } ], - "time": "2021-03-30T17:13:30+00:00" + "time": "2024-04-13T18:00:56+00:00" }, { "name": "composer/pcre", - "version": "3.1.1", + "version": "3.3.1", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9" + "reference": "63aaeac21d7e775ff9bc9d45021e1745c97521c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/00104306927c7a0919b4ced2aaa6782c1e61a3c9", - "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9", + "url": "https://api.github.com/repos/composer/pcre/zipball/63aaeac21d7e775ff9bc9d45021e1745c97521c4", + "reference": "63aaeac21d7e775ff9bc9d45021e1745c97521c4", "shasum": "" }, "require": { "php": "^7.4 || ^8.0" }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, "require-dev": { - "phpstan/phpstan": "^1.3", + "phpstan/phpstan": "^1.11.10", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^5" + "phpunit/phpunit": "^8 || ^9" }, "type": "library", "extra": { "branch-alias": { "dev-main": "3.x-dev" + }, + "phpstan": { + "includes": [ + "extension.neon" + ] } }, "autoload": { @@ -225,7 +227,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.1.1" + "source": "https://github.com/composer/pcre/tree/3.3.1" }, "funding": [ { @@ -241,28 +243,28 @@ "type": "tidelift" } ], - "time": "2023-10-11T07:11:09+00:00" + "time": "2024-08-27T18:44:43+00:00" }, { "name": "composer/semver", - "version": "3.4.0", + "version": "3.4.3", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", "extra": { @@ -306,7 +308,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.0" + "source": "https://github.com/composer/semver/tree/3.4.3" }, "funding": [ { @@ -322,20 +324,20 @@ "type": "tidelift" } ], - "time": "2023-08-31T09:50:34+00:00" + "time": "2024-09-19T14:15:21+00:00" }, { "name": "composer/xdebug-handler", - "version": "3.0.3", + "version": "3.0.5", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "ced299686f41dce890debac69273b47ffe98a40c" + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", - "reference": "ced299686f41dce890debac69273b47ffe98a40c", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", "shasum": "" }, "require": { @@ -346,7 +348,7 @@ "require-dev": { "phpstan/phpstan": "^1.0", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^6.0" + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" }, "type": "library", "autoload": { @@ -370,9 +372,9 @@ "performance" ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" }, "funding": [ { @@ -388,7 +390,7 @@ "type": "tidelift" } ], - "time": "2022-02-25T21:32:43+00:00" + "time": "2024-05-06T16:37:16+00:00" }, { "name": "dnoegel/php-xdg-base-dir", @@ -429,16 +431,16 @@ }, { "name": "doctrine/deprecations", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931" + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/4f2d4f2836e7ec4e7a8625e75c6aa916004db931", - "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", "shasum": "" }, "require": { @@ -470,9 +472,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.2" + "source": "https://github.com/doctrine/deprecations/tree/1.1.3" }, - "time": "2023-09-27T20:04:15+00:00" + "time": "2024-01-30T19:34:25+00:00" }, { "name": "felixfbecker/advanced-json-rpc", @@ -521,16 +523,16 @@ }, { "name": "felixfbecker/language-server-protocol", - "version": "v1.5.2", + "version": "v1.5.3", "source": { "type": "git", "url": "https://github.com/felixfbecker/php-language-server-protocol.git", - "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842" + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842", - "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/a9e113dbc7d849e35b8776da39edaf4313b7b6c9", + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9", "shasum": "" }, "require": { @@ -571,22 +573,22 @@ ], "support": { "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", - "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2" + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.3" }, - "time": "2022-03-02T22:36:06+00:00" + "time": "2024-04-30T00:40:11+00:00" }, { "name": "fidry/cpu-core-counter", - "version": "0.5.1", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "b58e5a3933e541dc286cc91fc4f3898bbc6f1623" + "reference": "8520451a140d3f46ac33042715115e290cf5785f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/b58e5a3933e541dc286cc91fc4f3898bbc6f1623", - "reference": "b58e5a3933e541dc286cc91fc4f3898bbc6f1623", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", + "reference": "8520451a140d3f46ac33042715115e290cf5785f", "shasum": "" }, "require": { @@ -594,13 +596,13 @@ }, "require-dev": { "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", - "phpunit/phpunit": "^9.5.26 || ^8.5.31", - "theofidry/php-cs-fixer-config": "^1.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", "webmozarts/strict-phpunit": "^7.5" }, "type": "library", @@ -626,7 +628,7 @@ ], "support": { "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/0.5.1" + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" }, "funding": [ { @@ -634,20 +636,20 @@ "type": "github" } ], - "time": "2022-12-24T12:35:10+00:00" + "time": "2024-08-06T10:04:20+00:00" }, { "name": "netresearch/jsonmapper", - "version": "v4.2.0", + "version": "v4.5.0", "source": { "type": "git", "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "f60565f8c0566a31acf06884cdaa591867ecc956" + "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/f60565f8c0566a31acf06884cdaa591867ecc956", - "reference": "f60565f8c0566a31acf06884cdaa591867ecc956", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8e76efb98ee8b6afc54687045e1b8dba55ac76e5", + "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5", "shasum": "" }, "require": { @@ -658,7 +660,7 @@ "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0", + "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0", "squizlabs/php_codesniffer": "~3.5" }, "type": "library", @@ -683,31 +685,31 @@ "support": { "email": "cweiske@cweiske.de", "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/cweiske/jsonmapper/tree/v4.2.0" + "source": "https://github.com/cweiske/jsonmapper/tree/v4.5.0" }, - "time": "2023-04-09T17:37:40+00:00" + "time": "2024-09-08T10:13:13+00:00" }, { "name": "nikic/php-parser", - "version": "v4.18.0", + "version": "v4.19.4", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" + "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/715f4d25e225bc47b293a8b997fe6ce99bf987d2", + "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.1" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/php-parse" @@ -739,9 +741,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.4" }, - "time": "2023-12-10T21:03:43+00:00" + "time": "2024-09-29T15:01:53+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -798,28 +800,35 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.3.0", + "version": "5.4.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", "shasum": "" }, "require": { + "doctrine/deprecations": "^1.1", "ext-filter": "*", - "php": "^7.2 || ^8.0", + "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7", "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "~1.3.2", - "psalm/phar": "^4.8" + "mockery/mockery": "~1.3.5", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^5.13" }, "type": "library", "extra": { @@ -843,33 +852,33 @@ }, { "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" + "email": "opensource@ijaap.nl" } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.1" }, - "time": "2021-10-19T17:43:47+00:00" + "time": "2024-05-21T05:55:05+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.7.3", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419" + "reference": "153ae662783729388a584b4361f2545e4d841e3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419", - "reference": "3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", + "reference": "153ae662783729388a584b4361f2545e4d841e3c", "shasum": "" }, "require": { "doctrine/deprecations": "^1.0", - "php": "^7.4 || ^8.0", + "php": "^7.3 || ^8.0", "phpdocumentor/reflection-common": "^2.0", "phpstan/phpdoc-parser": "^1.13" }, @@ -907,22 +916,22 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.3" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" }, - "time": "2023-08-12T11:01:26+00:00" + "time": "2024-02-23T11:10:43+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "1.24.4", + "version": "1.33.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496" + "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6bd0c26f3786cd9b7c359675cb789e35a8e07496", - "reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/82a311fd3690fb2bf7b64d5c98f912b3dd746140", + "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140", "shasum": "" }, "require": { @@ -954,9 +963,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.4" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.33.0" }, - "time": "2023-11-26T18:29:22+00:00" + "time": "2024-10-13T11:25:22+00:00" }, { "name": "psr/container", @@ -1013,16 +1022,16 @@ }, { "name": "psr/log", - "version": "3.0.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { @@ -1057,35 +1066,35 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "time": "2021-07-14T16:46:02+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { "name": "sebastian/diff", - "version": "5.0.3", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b" + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/912dc2fbe3e3c1e7873313cc801b100b6c68c87b", - "reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0", + "phpunit/phpunit": "^11.0", "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1118,7 +1127,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.0.3" + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" }, "funding": [ { @@ -1126,20 +1135,20 @@ "type": "github" } ], - "time": "2023-05-01T07:48:21+00:00" + "time": "2024-07-03T04:53:05+00:00" }, { "name": "spatie/array-to-xml", - "version": "3.2.2", + "version": "3.3.0", "source": { "type": "git", "url": "https://github.com/spatie/array-to-xml.git", - "reference": "96be97e664c87613121d073ea39af4c74e57a7f8" + "reference": "f56b220fe2db1ade4c88098d83413ebdfc3bf876" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/96be97e664c87613121d073ea39af4c74e57a7f8", - "reference": "96be97e664c87613121d073ea39af4c74e57a7f8", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/f56b220fe2db1ade4c88098d83413ebdfc3bf876", + "reference": "f56b220fe2db1ade4c88098d83413ebdfc3bf876", "shasum": "" }, "require": { @@ -1152,6 +1161,11 @@ "spatie/pest-plugin-snapshots": "^1.1" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, "autoload": { "psr-4": { "Spatie\\ArrayToXml\\": "src" @@ -1177,7 +1191,7 @@ "xml" ], "support": { - "source": "https://github.com/spatie/array-to-xml/tree/3.2.2" + "source": "https://github.com/spatie/array-to-xml/tree/3.3.0" }, "funding": [ { @@ -1189,51 +1203,50 @@ "type": "github" } ], - "time": "2023-11-14T14:08:51+00:00" + "time": "2024-05-01T10:20:27+00:00" }, { "name": "symfony/console", - "version": "v6.4.1", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "a550a7c99daeedef3f9d23fb82e3531525ff11fd" + "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/a550a7c99daeedef3f9d23fb82e3531525ff11fd", - "reference": "a550a7c99daeedef3f9d23fb82e3531525ff11fd", + "url": "https://api.github.com/repos/symfony/console/zipball/0fa539d12b3ccf068a722bbbffa07ca7079af9ee", + "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", + "php": ">=8.2", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^5.4|^6.0|^7.0" + "symfony/string": "^6.4|^7.0" }, "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", "symfony/http-foundation": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -1267,7 +1280,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.1" + "source": "https://github.com/symfony/console/tree/v7.1.5" }, "funding": [ { @@ -1283,20 +1296,20 @@ "type": "tidelift" } ], - "time": "2023-11-30T10:54:28+00:00" + "time": "2024-09-20T08:28:38+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.4.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", "shasum": "" }, "require": { @@ -1305,7 +1318,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -1334,7 +1347,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.4.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" }, "funding": [ { @@ -1350,27 +1363,30 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/filesystem", - "version": "v6.4.0", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "952a8cb588c3bc6ce76f6023000fb932f16a6e59" + "reference": "61fe0566189bf32e8cfee78335d8776f64a66f5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/952a8cb588c3bc6ce76f6023000fb932f16a6e59", - "reference": "952a8cb588c3bc6ce76f6023000fb932f16a6e59", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/61fe0566189bf32e8cfee78335d8776f64a66f5a", + "reference": "61fe0566189bf32e8cfee78335d8776f64a66f5a", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8" }, + "require-dev": { + "symfony/process": "^6.4|^7.0" + }, "type": "library", "autoload": { "psr-4": { @@ -1397,7 +1413,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.0" + "source": "https://github.com/symfony/filesystem/tree/v7.1.5" }, "funding": [ { @@ -1413,24 +1429,24 @@ "type": "tidelift" } ], - "time": "2023-07-26T17:27:13+00:00" + "time": "2024-09-17T09:16:35+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.28.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -1440,9 +1456,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1479,7 +1492,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -1495,33 +1508,30 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.28.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "875e90aeea2777b6f135677f618529449334a612" + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612", - "reference": "875e90aeea2777b6f135677f618529449334a612", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1560,7 +1570,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" }, "funding": [ { @@ -1576,33 +1586,30 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.28.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", - "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1644,7 +1651,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" }, "funding": [ { @@ -1660,24 +1667,24 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.28.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "42292d99c55abe617799667f454222c54c60e229" + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", - "reference": "42292d99c55abe617799667f454222c54c60e229", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -1687,9 +1694,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1727,7 +1731,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -1743,25 +1747,26 @@ "type": "tidelift" } ], - "time": "2023-07-28T09:04:16+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.4.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838" + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/b3313c2dbffaf71c8de2934e2ea56ed2291a3838", - "reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", "shasum": "" }, "require": { "php": ">=8.1", - "psr/container": "^2.0" + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "ext-psr": "<1.1|>=2" @@ -1769,7 +1774,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -1809,7 +1814,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" }, "funding": [ { @@ -1825,24 +1830,24 @@ "type": "tidelift" } ], - "time": "2023-07-30T20:28:31+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/string", - "version": "v6.4.0", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "b45fcf399ea9c3af543a92edf7172ba21174d809" + "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/b45fcf399ea9c3af543a92edf7172ba21174d809", - "reference": "b45fcf399ea9c3af543a92edf7172ba21174d809", + "url": "https://api.github.com/repos/symfony/string/zipball/d66f9c343fa894ec2037cc928381df90a7ad4306", + "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", @@ -1852,11 +1857,12 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^5.4|^6.0|^7.0", - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/intl": "^6.2|^7.0", + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^5.4|^6.0|^7.0" + "symfony/var-exporter": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -1895,7 +1901,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.0" + "source": "https://github.com/symfony/string/tree/v7.1.5" }, "funding": [ { @@ -1911,20 +1917,20 @@ "type": "tidelift" } ], - "time": "2023-11-28T20:41:49+00:00" + "time": "2024-09-20T08:28:38+00:00" }, { "name": "vimeo/psalm", - "version": "5.17.0", + "version": "5.26.1", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "c620f6e80d0abfca532b00bda366062aaedf6e5d" + "reference": "d747f6500b38ac4f7dfc5edbcae6e4b637d7add0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/c620f6e80d0abfca532b00bda366062aaedf6e5d", - "reference": "c620f6e80d0abfca532b00bda366062aaedf6e5d", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/d747f6500b38ac4f7dfc5edbcae6e4b637d7add0", + "reference": "d747f6500b38ac4f7dfc5edbcae6e4b637d7add0", "shasum": "" }, "require": { @@ -1943,11 +1949,11 @@ "ext-tokenizer": "*", "felixfbecker/advanced-json-rpc": "^3.1", "felixfbecker/language-server-protocol": "^1.5.2", - "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1", + "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "nikic/php-parser": "^4.16", + "nikic/php-parser": "^4.17", "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", - "sebastian/diff": "^4.0 || ^5.0", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0", "spatie/array-to-xml": "^2.17.0 || ^3.0", "symfony/console": "^4.1.6 || ^5.0 || ^6.0 || ^7.0", "symfony/filesystem": "^5.4 || ^6.0 || ^7.0" @@ -2021,7 +2027,7 @@ "issues": "https://github.com/vimeo/psalm/issues", "source": "https://github.com/vimeo/psalm" }, - "time": "2023-12-03T20:21:41+00:00" + "time": "2024-09-08T18:53:08+00:00" }, { "name": "webmozart/assert", diff --git a/composer.json b/composer.json index 0e619e8b1..aec2e99e4 100644 --- a/composer.json +++ b/composer.json @@ -44,7 +44,10 @@ "symfony/yaml": "^6.4|^7.0" }, "autoload": { - "psr-4": { "Zenstruck\\Foundry\\": "src/" }, + "psr-4": { + "Zenstruck\\Foundry\\": "src/", + "Zenstruck\\Foundry\\Psalm\\": "utils/psalm" + }, "files": ["src/functions.php", "src/Persistence/functions.php", "src/phpunit_helper.php"] }, "autoload-dev": { @@ -67,6 +70,9 @@ "target-directory": "bin/tools", "bin-links": true, "forward-command": false + }, + "psalm": { + "pluginClass": "Zenstruck\\Foundry\\Psalm\\FoundryPlugin" } }, "scripts": { diff --git a/docs/index.rst b/docs/index.rst index 579078db5..3e5a0da0a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -852,7 +852,7 @@ the LazyValue can be `memoized `_ so return [ // Call CategoryFactory::random() everytime this factory is instantiated 'category' => LazyValue::new(fn() => CategoryFactory::random()), - // The same User instance will be both added to the Project and set as the Task owner + // The same UserForPersistentFactory instance will be both added to the Project and set as the Task owner 'project' => ProjectFactory::new(['users' => [$owner]]), 'owner' => $owner, ]; @@ -888,7 +888,7 @@ common use-case: encoding a password with the ``UserPasswordHasherInterface`` se public static function class(): string { - return User::class; + return UserForPersistentFactory::class; } protected function defaults(): array @@ -902,7 +902,7 @@ common use-case: encoding a password with the ``UserPasswordHasherInterface`` se protected function initialize(): static { return $this - ->afterInstantiate(function(User $user) { + ->afterInstantiate(function(UserForPersistentFactory $user) { $user->setPassword($this->passwordHasher->hashPassword($user, $user->getPassword())); }) ; @@ -1748,7 +1748,7 @@ You can improve the speed by reducing the *work factor* of your encoder: # config/packages/test/security.yaml encoders: # use your user class name here - App\Entity\User: + App\Entity\UserForPersistentFactory: # This should be the same value as in config/packages/security.yaml algorithm: auto cost: 4 # Lowest possible value for bcrypt diff --git a/phpstan.neon b/phpstan.neon index 030b3b5e3..f3e6e4d76 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,7 +4,7 @@ parameters: paths: - src - tests - - stubs + - stubs/phpstan ignoreErrors: # suppress strange behavior of PHPStan where it considers proxy() return type as *NEVER* - message: '#Return type of call to function Zenstruck\\Foundry\\Persistence\\proxy contains unresolvable type#' diff --git a/psalm.xml b/psalm.xml index f6647a3ff..55f2d4718 100644 --- a/psalm.xml +++ b/psalm.xml @@ -9,12 +9,11 @@ findUnusedCode="false" > - - + + - - - - + + + diff --git a/src/Factory.php b/src/Factory.php index fdf337486..93869470b 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -25,7 +25,7 @@ */ abstract class Factory { - /** @var Attributes[] */ + /** @phpstan-var Attributes[] */ private array $attributes; // keep an empty constructor for BC @@ -33,9 +33,10 @@ public function __construct() { } - /** - * @param Attributes $attributes + * @return static + * @phpstan-return static + * @phpstan-param Attributes $attributes */ final public static function new(array|callable $attributes = []): static { @@ -53,19 +54,19 @@ final public static function new(array|callable $attributes = []): static } /** - * @param Attributes $attributes + * @phpstan-param Attributes $attributes * * @return T */ - final public static function createOne(array|callable $attributes = []): mixed + public static function createOne(array|callable $attributes = []): mixed { return static::new()->create($attributes); } /** - * @param Attributes $attributes + * @phpstan-param Attributes $attributes * - * @return T[] + * @return list */ final public static function createMany(int $number, array|callable $attributes = []): array { @@ -73,9 +74,9 @@ final public static function createMany(int $number, array|callable $attributes } /** - * @param Attributes $attributes + * @phpstan-param Attributes $attributes * - * @return T[] + * @return list */ final public static function createRange(int $min, int $max, array|callable $attributes = []): array { @@ -83,9 +84,9 @@ final public static function createRange(int $min, int $max, array|callable $att } /** - * @param Sequence $sequence + * @phpstan-param Sequence $sequence * - * @return T[] + * @return list */ final public static function createSequence(iterable|callable $sequence): array { @@ -93,7 +94,7 @@ final public static function createSequence(iterable|callable $sequence): array } /** - * @param Attributes $attributes + * @phpstan-param Attributes $attributes * * @return T */ @@ -116,7 +117,7 @@ final public function range(int $min, int $max): FactoryCollection } /** - * @param Sequence $sequence + * @phpstan-param Sequence $sequence * @return FactoryCollection */ final public function sequence(iterable|callable $sequence): FactoryCollection @@ -129,7 +130,10 @@ final public function sequence(iterable|callable $sequence): FactoryCollection } /** - * @param Attributes $attributes + * @phpstan-param Attributes $attributes + * + * @psalm-return static + * @phpstan-return static */ final public function with(array|callable $attributes = []): static { @@ -147,9 +151,9 @@ final protected static function faker(): Faker\Generator /** * @internal * - * @param Attributes $attributes + * @phpstan-param Attributes $attributes * - * @return Parameters + * @phpstan-return Parameters */ final protected function normalizeAttributes(array|callable $attributes = []): array { @@ -181,9 +185,9 @@ protected function initialize(): static /** * @internal * - * @param Parameters $parameters + * @phpstan-param Parameters $parameters * - * @return Parameters + * @phpstan-return Parameters */ protected function normalizeParameters(array $parameters): array { @@ -241,7 +245,7 @@ protected function normalizeObject(object $object): object } /** - * @return Attributes + * @phpstan-return Attributes */ abstract protected function defaults(): array|callable; } diff --git a/src/FactoryCollection.php b/src/FactoryCollection.php index d2a5304ab..0b11b252f 100644 --- a/src/FactoryCollection.php +++ b/src/FactoryCollection.php @@ -66,7 +66,7 @@ public static function sequence(Factory $factory, iterable $items): self /** * @param Attributes $attributes * - * @return T[] + * @return list */ public function create(array|callable $attributes = []): array { diff --git a/src/ObjectFactory.php b/src/ObjectFactory.php index 16b128874..f791ec89f 100644 --- a/src/ObjectFactory.php +++ b/src/ObjectFactory.php @@ -25,13 +25,13 @@ */ abstract class ObjectFactory extends Factory { - /** @var list):Parameters> */ + /** @phpstan-var list):Parameters> */ private array $beforeInstantiate = []; - /** @var list */ + /** @phpstan-var list */ private array $afterInstantiate = []; - /** @var InstantiatorCallable|null */ + /** @phpstan-var InstantiatorCallable|null */ private $instantiator; /** @@ -40,8 +40,6 @@ abstract class ObjectFactory extends Factory abstract public static function class(): string; /** - * @final - * * @return T */ public function create(callable|array $attributes = []): object @@ -58,6 +56,7 @@ public function create(callable|array $attributes = []): object $parameters = $this->normalizeParameters($parameters); $instantiator = $this->instantiator ?? Configuration::instance()->instantiator; + /** @var T $object */ $object = $instantiator($parameters, static::class()); foreach ($this->afterInstantiate as $hook) { @@ -68,7 +67,10 @@ public function create(callable|array $attributes = []): object } /** - * @param InstantiatorCallable $instantiator + * @phpstan-param InstantiatorCallable $instantiator + * + * @psalm-return static + * @phpstan-return static */ final public function instantiateWith(callable $instantiator): static { @@ -79,7 +81,7 @@ final public function instantiateWith(callable $instantiator): static } /** - * @param callable(Parameters,class-string):Parameters $callback + * @phpstan-param callable(Parameters,class-string):Parameters $callback */ final public function beforeInstantiate(callable $callback): static { @@ -92,7 +94,7 @@ final public function beforeInstantiate(callable $callback): static /** * @final * - * @param callable(T,Parameters):void $callback + * @phpstan-param callable(T,Parameters):void $callback */ public function afterInstantiate(callable $callback): static { diff --git a/src/Persistence/PersistentObjectFactory.php b/src/Persistence/PersistentObjectFactory.php index 63f723d5e..2165b614c 100644 --- a/src/Persistence/PersistentObjectFactory.php +++ b/src/Persistence/PersistentObjectFactory.php @@ -34,16 +34,14 @@ abstract class PersistentObjectFactory extends ObjectFactory { private bool $persist; - /** @var list */ + /** @phpstan-var list */ private array $afterPersist = []; /** @var list */ private array $tempAfterPersist = []; /** - * @final - * - * @param mixed|Parameters $criteriaOrId + * @phpstan-param mixed|Parameters $criteriaOrId * * @return T * @@ -55,9 +53,7 @@ public static function find(mixed $criteriaOrId): object } /** - * @final - * - * @param Parameters $criteria + * @phpstan-param Parameters $criteria * * @return T */ @@ -73,9 +69,7 @@ public static function findOrCreate(array $criteria): object } /** - * @final - * - * @param Parameters $criteria + * @phpstan-param Parameters $criteria * * @return T */ @@ -89,12 +83,10 @@ public static function randomOrCreate(array $criteria = []): object } /** - * @final - * * @param positive-int $count - * @param Parameters $criteria + * @phpstan-param Parameters $criteria * - * @return T[] + * @return list */ public static function randomSet(int $count, array $criteria = []): array { @@ -102,13 +94,11 @@ public static function randomSet(int $count, array $criteria = []): array } /** - * @final - * * @param int<0, max> $min * @param int<0, max> $max - * @param Parameters $criteria + * @phpstan-param Parameters $criteria * - * @return T[] + * @return list */ public static function randomRange(int $min, int $max, array $criteria = []): array { @@ -116,11 +106,9 @@ public static function randomRange(int $min, int $max, array $criteria = []): ar } /** - * @final - * - * @param Parameters $criteria + * @phpstan-param Parameters $criteria * - * @return T[] + * @return list */ public static function findBy(array $criteria): array { @@ -128,9 +116,7 @@ public static function findBy(array $criteria): array } /** - * @final - * - * @param Parameters $criteria + * @phpstan-param Parameters $criteria * * @return T */ @@ -140,33 +126,31 @@ public static function random(array $criteria = []): object } /** - * @final - * * @return T * * @throws \RuntimeException If no objects exist */ public static function first(string $sortBy = 'id'): object { - return static::repository()->firstOrFail($sortBy); + /** @var T $object */ + $object = static::repository()->firstOrFail($sortBy); + return $object; } /** - * @final - * * @return T * * @throws \RuntimeException If no objects exist */ public static function last(string $sortBy = 'id'): object { - return static::repository()->lastOrFail($sortBy); + /** @var T $object */ + $object = static::repository()->lastOrFail($sortBy); + return $object; } /** - * @final - * - * @return T[] + * @return list */ public static function all(): array { @@ -174,8 +158,6 @@ public static function all(): array } /** - * @final - * * @return RepositoryDecorator> */ public static function repository(): ObjectRepository @@ -191,7 +173,7 @@ final public static function assert(): RepositoryAssertions } /** - * @param Parameters $criteria + * @phpstan-param Parameters $criteria */ final public static function count(array $criteria = []): int { @@ -206,12 +188,12 @@ final public static function truncate(): void /** * @return T */ - final public function create(callable|array $attributes = []): object + public function create(callable|array $attributes = []): object { $object = parent::create($attributes); if (!$this->isPersisting()) { - return $this->proxy($object); + return $object; } $configuration = Configuration::instance(); @@ -238,7 +220,7 @@ final public function create(callable|array $attributes = []): object $configuration->persistence()->save($object); } - return $this->proxy($object); + return $object; } final public function andPersist(): static @@ -258,7 +240,7 @@ final public function withoutPersisting(): static } /** - * @param callable(T, Parameters):void $callback + * @phpstan-param callable(T, Parameters):void $callback */ final public function afterPersist(callable $callback): static { @@ -345,20 +327,4 @@ final protected function isPersisting(): bool return $this->persist ?? $config->isPersistenceAvailable() && $config->persistence()->isEnabled() && $config->persistence()->autoPersist(static::class()); } - - /** - * @param T $object - * - * @return T - */ - private function proxy(object $object): object - { - if (!$this instanceof PersistentProxyObjectFactory) { - return $object; - } - - $object = proxy($object); - - return $this->isPersisting() ? $object : $object->_disableAutoRefresh(); - } } diff --git a/src/Persistence/PersistentProxyObjectFactory.php b/src/Persistence/PersistentProxyObjectFactory.php index a8b4870cb..a2e78d0a0 100644 --- a/src/Persistence/PersistentProxyObjectFactory.php +++ b/src/Persistence/PersistentProxyObjectFactory.php @@ -13,33 +13,12 @@ use Doctrine\Persistence\ObjectRepository; use Zenstruck\Foundry\Configuration; -use Zenstruck\Foundry\Factory; -use Zenstruck\Foundry\Object\Instantiator; -use Zenstruck\Foundry\FactoryCollection; // keep me! /** * @author Kevin Bond * * @template T of object * @extends PersistentObjectFactory> - * - * @phpstan-type InstantiatorCallable = Instantiator|callable(Parameters,class-string):T - * @phpstan-import-type Parameters from Factory - * @phpstan-import-type Attributes from Factory - * - * @phpstan-method $this instantiateWith(InstantiatorCallable $instantiator) - * - * @phpstan-method FactoryCollection> sequence(iterable>|callable(): iterable> $sequence) - * @phpstan-method FactoryCollection> many(int $min, int|null $max = null) - * - * @phpstan-method static list> createSequence(iterable>|callable(): iterable> $sequence) - * @phpstan-method static list> createMany(int $number, array|callable $attributes = []) - * - * @method static Proxy createOne(Attributes $attributes = []) - * @phpstan-method static T&Proxy createOne(Attributes $attributes = []) - * - * @method Proxy create(Attributes $attributes = []) - * @phpstan-method T&Proxy create(Attributes $attributes = []) */ abstract class PersistentProxyObjectFactory extends PersistentObjectFactory { @@ -49,30 +28,48 @@ abstract class PersistentProxyObjectFactory extends PersistentObjectFactory abstract public static function class(): string; /** - * @return Proxy + * @return T|Proxy + * @phpstan-return T&Proxy + */ + public function create(callable|array $attributes = []): object + { + return proxy(parent::create($attributes)); // @phpstan-ignore function.unresolvableReturnType + } + + /** + * @return T|Proxy + * @phpstan-return T&Proxy + */ + public static function createOne(array|callable $attributes = []): mixed + { + return proxy(parent::createOne($attributes)); // @phpstan-ignore function.unresolvableReturnType + } + + /** + * @return T|Proxy * @phpstan-return T&Proxy */ final public static function find(mixed $criteriaOrId): object { - return proxy(parent::find($criteriaOrId)); // @phpstan-ignore-line + return proxy(parent::find($criteriaOrId)); // @phpstan-ignore function.unresolvableReturnType } /** - * @return Proxy + * @return T|Proxy * @phpstan-return T&Proxy */ final public static function findOrCreate(array $criteria): object { - return proxy(parent::findOrCreate($criteria)); // @phpstan-ignore-line + return proxy(parent::findOrCreate($criteria)); // @phpstan-ignore function.unresolvableReturnType } /** - * @return Proxy + * @return T|Proxy * @phpstan-return T&Proxy */ final public static function randomOrCreate(array $criteria = []): object { - return proxy(parent::randomOrCreate($criteria)); // @phpstan-ignore-line + return proxy(parent::randomOrCreate($criteria)); // @phpstan-ignore function.unresolvableReturnType } /** @@ -80,7 +77,7 @@ final public static function randomOrCreate(array $criteria = []): object */ final public static function randomSet(int $count, array $criteria = []): array { - return \array_map(proxy(...), parent::randomSet($count, $criteria)); // @phpstan-ignore-line + return \array_map(proxy(...), parent::randomSet($count, $criteria)); } /** @@ -88,7 +85,7 @@ final public static function randomSet(int $count, array $criteria = []): array */ final public static function randomRange(int $min, int $max, array $criteria = []): array { - return \array_map(proxy(...), parent::randomRange($min, $max, $criteria)); // @phpstan-ignore-line + return \array_map(proxy(...), parent::randomRange($min, $max, $criteria)); } /** @@ -96,34 +93,34 @@ final public static function randomRange(int $min, int $max, array $criteria = [ */ final public static function findBy(array $criteria): array { - return \array_map(proxy(...), parent::findBy($criteria)); // @phpstan-ignore-line + return \array_map(proxy(...), parent::findBy($criteria)); } /** - * @return Proxy + * @return T|Proxy * @phpstan-return T&Proxy */ final public static function random(array $criteria = []): object { - return proxy(parent::random($criteria)); // @phpstan-ignore-line + return proxy(parent::random($criteria)); // @phpstan-ignore function.unresolvableReturnType } /** - * @return Proxy + * @return T|Proxy * @phpstan-return T&Proxy */ final public static function first(string $sortBy = 'id'): object { - return proxy(parent::first($sortBy)); // @phpstan-ignore-line + return proxy(parent::first($sortBy)); // @phpstan-ignore function.unresolvableReturnType } /** - * @return Proxy + * @return T|Proxy * @phpstan-return T&Proxy */ final public static function last(string $sortBy = 'id'): object { - return proxy(parent::last($sortBy)); // @phpstan-ignore-line + return proxy(parent::last($sortBy)); // @phpstan-ignore function.unresolvableReturnType } /** @@ -131,7 +128,7 @@ final public static function last(string $sortBy = 'id'): object */ final public static function all(): array { - return \array_map(proxy(...), parent::all()); // @phpstan-ignore-line + return \array_map(proxy(...), parent::all()); } /** @@ -141,6 +138,6 @@ final public static function repository(): ObjectRepository { Configuration::instance()->assertPersistanceEnabled(); - return new ProxyRepositoryDecorator(static::class()); // @phpstan-ignore-line + return new ProxyRepositoryDecorator(static::class()); // @phpstan-ignore argument.type, return.type } } diff --git a/src/Persistence/Proxy.php b/src/Persistence/Proxy.php index cbddf6af7..6a02e4b0b 100644 --- a/src/Persistence/Proxy.php +++ b/src/Persistence/Proxy.php @@ -21,23 +21,50 @@ */ interface Proxy { + /** + * @psalm-return T&Proxy + * @phpstan-return static + */ public function _enableAutoRefresh(): static; + /** + * @psalm-return T&Proxy + * @phpstan-return static + */ public function _disableAutoRefresh(): static; /** * @param callable(static):void $callback + * @psalm-return T&Proxy + * @phpstan-return static */ public function _withoutAutoRefresh(callable $callback): static; + /** + * @psalm-return T&Proxy + * @phpstan-return static + */ public function _save(): static; + /** + * @return static + * @psalm-return T&Proxy + * @phpstan-return static + */ public function _refresh(): static; + /** + * @psalm-return T&Proxy + * @phpstan-return static + */ public function _delete(): static; public function _get(string $property): mixed; + /** + * @psalm-return T&Proxy + * @phpstan-return static + */ public function _set(string $property, mixed $value): static; /** @@ -45,8 +72,16 @@ public function _set(string $property, mixed $value): static; */ public function _real(): object; + /** + * @psalm-return T&Proxy + * @phpstan-return static + */ public function _assertPersisted(string $message = '{entity} is not persisted.'): static; + /** + * @psalm-return T&Proxy + * @phpstan-return static + */ public function _assertNotPersisted(string $message = '{entity} is persisted but it should not be.'): static; /** diff --git a/src/Persistence/ProxyRepositoryDecorator.php b/src/Persistence/ProxyRepositoryDecorator.php index 2005ec607..5e2d26787 100644 --- a/src/Persistence/ProxyRepositoryDecorator.php +++ b/src/Persistence/ProxyRepositoryDecorator.php @@ -22,56 +22,97 @@ */ final class ProxyRepositoryDecorator extends RepositoryDecorator // @phpstan-ignore-line { + /** + * @return T|Proxy|null + * @psalm-return (T&Proxy)|null + */ public function first(string $sortBy = 'id'): ?object { return $this->proxyNullableObject(parent::first($sortBy)); } + /** + * @return T|Proxy + * @psalm-return T&Proxy + */ public function firstOrFail(string $sortBy = 'id'): object { return proxy(parent::firstOrFail($sortBy)); } + /** + * @return T|Proxy|null + * @psalm-return (T&Proxy)|null + */ public function last(string $sortedField = 'id'): ?object { return $this->proxyNullableObject(parent::last($sortedField)); } + /** + * @return T|Proxy + * @psalm-return T&Proxy + */ public function lastOrFail(string $sortBy = 'id'): object { return proxy(parent::lastOrFail($sortBy)); } + /** + * @return T|Proxy|null + * @psalm-return (T&Proxy)|null + */ public function find($id): ?object { return $this->proxyNullableObject(parent::find($id)); } + /** + * @return T|Proxy + * @psalm-return T&Proxy + */ public function findOrFail(mixed $id): object { return proxy(parent::findOrFail($id)); } + /** + * @psalm-return array> + */ public function findAll(): array { return $this->proxyArray(parent::findAll()); } + /** + * @psalm-return array> + */ public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array { return $this->proxyArray(parent::findBy($criteria, $orderBy, $limit, $offset)); } + /** + * @return T|Proxy|null + * @psalm-return (T&Proxy)|null + */ public function findOneBy(array $criteria): ?object { return $this->proxyNullableObject(parent::findOneBy($criteria)); } + /** + * @return T|Proxy|null + * @psalm-return T&Proxy + */ public function random(array $criteria = []): object { return proxy(parent::random($criteria)); } + /** + * @psalm-return array> + */ public function randomSet(int $count, array $criteria = []): array { return $this->proxyArray( @@ -79,6 +120,9 @@ public function randomSet(int $count, array $criteria = []): array ); } + /** + * @psalm-return array> + */ public function randomRange(int $min, int $max, array $criteria = []): array { return $this->proxyArray( diff --git a/src/Persistence/RepositoryDecorator.php b/src/Persistence/RepositoryDecorator.php index dc622c690..922c308e6 100644 --- a/src/Persistence/RepositoryDecorator.php +++ b/src/Persistence/RepositoryDecorator.php @@ -92,10 +92,15 @@ public function lastOrFail(string $sortBy = 'id'): object public function find($id): ?object { if (\is_array($id) && (empty($id) || !array_is_list($id))) { - return $this->findOneBy($id); + /** @var T|null $object */ + $object = $this->findOneBy($id); + return $object; } - return $this->inner()->find(unproxy($id)); + /** @var T|null $object */ + $object = $this->inner()->find(unproxy($id)); + + return $object; } /** diff --git a/src/phpunit_helper.php b/src/phpunit_helper.php index e171ddece..1e5a7a2fe 100644 --- a/src/phpunit_helper.php +++ b/src/phpunit_helper.php @@ -31,7 +31,7 @@ function restorePhpUnitErrorHandler(): void } while (true) { - $previousHandler = \set_error_handler(static fn() => null); // @phpstan-ignore-line + $previousHandler = \set_error_handler(static fn() => null); // @phpstan-ignore argument.type \restore_error_handler(); $isPhpUnitErrorHandler = $previousHandler instanceof \PHPUnit\Runner\ErrorHandler; if (null === $previousHandler || $isPhpUnitErrorHandler) { diff --git a/stubs/PersistentObjectFactory.php b/stubs/PersistentObjectFactory.php deleted file mode 100644 index 5d55f7a61..000000000 --- a/stubs/PersistentObjectFactory.php +++ /dev/null @@ -1,70 +0,0 @@ - - */ -class UserRepository extends EntityRepository -{ - public function findByName(string $name): User - { - return new User(); - } -} - -/** - * The following method stubs are required for auto-completion in PhpStorm - * AND phpstan support. - * - * @extends PersistentObjectFactory - * - * @method User create(array|callable $attributes = []) - * @method static RepositoryDecorator|UserRepository repository() - * - * @phpstan-method static RepositoryDecorator repository() - */ -final class UserFactory extends PersistentObjectFactory -{ - public static function class(): string - { - return User::class; - } - - protected function defaults(): array|callable - { - return []; - } -} - -// test autocomplete with phpstorm -assertType('string', UserFactory::new()->create()->name); -assertType('string', UserFactory::createOne()->name); -assertType('string', UserFactory::new()->many(2)->create()[0]->name); -assertType('string', UserFactory::createMany(1)[0]->name); -assertType('string', UserFactory::first()->name); -assertType('string', UserFactory::last()->name); -assertType('string', UserFactory::find(1)->name); -assertType('string', UserFactory::all()[0]->name); -assertType('string', UserFactory::random()->name); -assertType('string', UserFactory::randomRange(1, 2)[0]->name); -assertType('string', UserFactory::randomSet(2)[0]->name); -assertType('string', UserFactory::findBy(['name' => 'foo'])[0]->name); -assertType('string', UserFactory::findOrCreate([])->name); -assertType('string', UserFactory::randomOrCreate([])->name); -assertType('string|null', UserFactory::repository()->find(1)?->name); -assertType('string', UserFactory::repository()->findAll()[0]->name); -assertType('string', UserFactory::repository()->findByName('foo')->name); -assertType('int', UserFactory::repository()->count()); -assertType('string', proxy(UserFactory::createOne())->name); -assertType('string', proxy(UserFactory::new()->create())->name); diff --git a/stubs/PersistentProxyObjectFactory.php b/stubs/PersistentProxyObjectFactory.php deleted file mode 100644 index c6a254f7b..000000000 --- a/stubs/PersistentProxyObjectFactory.php +++ /dev/null @@ -1,66 +0,0 @@ - - */ -class UserRepository1 extends EntityRepository -{ - public function findByName(string $name): User1 - { - return new User1(); - } -} - -/** - * The following method stubs are required for auto-completion in PhpStorm - * AND phpstan support. - * - * @extends PersistentProxyObjectFactory - * - * @method static RepositoryDecorator|UserRepository1 repository() - * - * @phpstan-method static RepositoryDecorator repository() - */ -final class User1Factory extends PersistentProxyObjectFactory -{ - public static function class(): string - { - return User1::class; - } - - protected function defaults(): array|callable - { - return []; - } -} - -// test autocomplete with phpstorm -assertType('string', User1Factory::new()->create()->_refresh()->name); -assertType('string', User1Factory::createOne()->_refresh()->name); -assertType('string', User1Factory::new()->many(2)->create()[0]->_refresh()->name); -assertType('string', User1Factory::createMany(1)[0]->_refresh()->name); -assertType('string', User1Factory::first()->_refresh()->name); -assertType('string', User1Factory::last()->_refresh()->name); -assertType('string', User1Factory::find(1)->_refresh()->name); -assertType('string', User1Factory::all()[0]->_refresh()->name); -assertType('string', User1Factory::random()->_refresh()->name); -assertType('string', User1Factory::randomRange(1, 2)[0]->_refresh()->name); -assertType('string', User1Factory::randomSet(2)[0]->_refresh()->name); -assertType('string', User1Factory::findBy(['name' => 'foo'])[0]->_refresh()->name); -assertType('string', User1Factory::findOrCreate([])->_refresh()->name); -assertType('string', User1Factory::randomOrCreate([])->_refresh()->name); -assertType('string|null', User1Factory::repository()->find(1)?->name); -assertType('string', User1Factory::repository()->findAll()[0]->name); -assertType('string', User1Factory::repository()->findByName('foo')->name); -assertType('int', User1Factory::repository()->count()); diff --git a/stubs/functions.php b/stubs/functions.php deleted file mode 100644 index e5fc30c3f..000000000 --- a/stubs/functions.php +++ /dev/null @@ -1,26 +0,0 @@ -create()->name); -assertType('string', object(User::class)->name); - -assertType('string', persistent_factory(User::class)->create()->name); -assertType('string', persist(User::class)->name); - -assertType('User|null', repository(User::class)->find(1)); - -assertType('User&Zenstruck\Foundry\Persistence\Proxy', proxy(object(User::class))); -assertType('string', proxy(object(User::class))->name); -assertType('string', proxy(object(User::class))->_refresh()->name); diff --git a/stubs/phpstan/ObjectFactory.php b/stubs/phpstan/ObjectFactory.php new file mode 100644 index 000000000..a90cb8e19 --- /dev/null +++ b/stubs/phpstan/ObjectFactory.php @@ -0,0 +1,69 @@ + + */ +final class UserObjectFactory extends ObjectFactory +{ + public static function class(): string + { + return UserForObjectFactory::class; + } + + protected function defaults(): array|callable + { + return []; + } +} + +// methods returning one object +assertType('UserForObjectFactory', UserObjectFactory::new()->create()); +assertType('UserForObjectFactory', UserObjectFactory::createOne()); +assertType( + 'UserForObjectFactory', + UserObjectFactory::new()->instantiateWith(Instantiator::withConstructor())->create() +); +assertType('UserForObjectFactory', UserObjectFactory::new()->with()->create()); + +// methods returning a list of objects +assertType("array", UserObjectFactory::createMany(1)); +assertType("array", UserObjectFactory::createRange(1, 2)); +assertType("array", UserObjectFactory::createSequence([])); + +// methods with FactoryCollection +$factoryCollection = FactoryCollection::class; +assertType("{$factoryCollection}", UserObjectFactory::new()->many(2)); +assertType("{$factoryCollection}", UserObjectFactory::new()->range(1, 2)); +assertType("{$factoryCollection}", UserObjectFactory::new()->sequence([])); +assertType("array", UserObjectFactory::new()->many(2)->create()); +assertType("array", UserObjectFactory::new()->range(1, 2)->create()); +assertType("array", UserObjectFactory::new()->sequence([])->create()); + +// test autocomplete with phpstorm +assertType('string', UserObjectFactory::new()->create()->name); +assertType('string', UserObjectFactory::new()->instantiateWith(Instantiator::withConstructor())->create()->name); +assertType('string', UserObjectFactory::new()->with()->create()->name); +assertType('string', UserObjectFactory::createOne()->name); + +assertType("string", UserObjectFactory::createMany(1)[0]->name); +assertType("string", UserObjectFactory::createRange(1, 2)[0]->name); +assertType("string", UserObjectFactory::createSequence([])[0]->name); + +assertType("string", UserObjectFactory::new()->many(2)->create()[0]->name); +assertType("string", UserObjectFactory::new()->range(1, 2)->create()[0]->name); +assertType("string", UserObjectFactory::new()->sequence([])->create()[0]->name); diff --git a/stubs/phpstan/PersistentObjectFactory.php b/stubs/phpstan/PersistentObjectFactory.php new file mode 100644 index 000000000..ff946514b --- /dev/null +++ b/stubs/phpstan/PersistentObjectFactory.php @@ -0,0 +1,118 @@ + + */ +final class UserFactory extends PersistentObjectFactory +{ + public static function class(): string + { + return UserForPersistentFactory::class; + } + + protected function defaults(): array|callable + { + return []; + } +} + +// methods returning one object +assertType('UserForPersistentFactory', UserFactory::new()->create()); +assertType('UserForPersistentFactory', UserFactory::createOne()); +assertType('UserForPersistentFactory', UserFactory::first()); +assertType('UserForPersistentFactory', UserFactory::last()); +assertType('UserForPersistentFactory', UserFactory::find(1)); +assertType('UserForPersistentFactory', UserFactory::random()); +assertType('UserForPersistentFactory', UserFactory::findOrCreate([])); +assertType('UserForPersistentFactory', UserFactory::randomOrCreate()); +assertType('UserForPersistentFactory', UserFactory::new()->instantiateWith(Instantiator::withConstructor())->create()); +assertType('UserForPersistentFactory', UserFactory::new()->with()->create()); + +// methods returning a list of objects +assertType("array", UserFactory::all()); +assertType("array", UserFactory::createMany(1)); +assertType("array", UserFactory::createRange(1, 2)); +assertType("array", UserFactory::createSequence([])); +assertType("array", UserFactory::randomRange(1, 2)); +assertType("array", UserFactory::randomSet(2)); +assertType("array", UserFactory::findBy(['name' => 'foo'])); + +// methods with FactoryCollection +$factoryCollection = FactoryCollection::class; +assertType("{$factoryCollection}", UserFactory::new()->many(2)); +assertType("{$factoryCollection}", UserFactory::new()->range(1, 2)); +assertType("{$factoryCollection}", UserFactory::new()->sequence([])); +assertType("array", UserFactory::new()->many(2)->create()); +assertType("array", UserFactory::new()->range(1, 2)->create()); +assertType("array", UserFactory::new()->sequence([])->create()); + +// methods using repository() +$repository = UserFactory::repository(); +assertType("Zenstruck\Foundry\Persistence\RepositoryDecorator>", $repository); +assertType("UserForPersistentFactory|null", $repository->first()); +assertType('UserForPersistentFactory', $repository->firstOrFail()); +assertType("UserForPersistentFactory|null", $repository->last()); +assertType('UserForPersistentFactory', $repository->lastOrFail()); +assertType("UserForPersistentFactory|null", $repository->find(1)); +assertType("UserForPersistentFactory", $repository->findOrFail(1)); +assertType("UserForPersistentFactory|null", $repository->findOneBy([])); +assertType('UserForPersistentFactory', $repository->random()); +assertType("array", $repository->findAll()); +assertType("array", $repository->findBy([])); +assertType("array", $repository->randomSet(2)); +assertType("array", $repository->randomRange(1, 2)); +assertType('int', $repository->count()); + +// test autocomplete with phpstorm +assertType('string', UserFactory::new()->create()->name); +assertType('string', UserFactory::new()->instantiateWith(Instantiator::withConstructor())->create()->name); +assertType('string', UserFactory::new()->with()->create()->name); +assertType('string', UserFactory::createOne()->name); +assertType('string', UserFactory::first()->name); +assertType('string', UserFactory::last()->name); +assertType('string', UserFactory::find(1)->name); +assertType('string', UserFactory::random()->name); +assertType('string', UserFactory::findOrCreate([])->name); +assertType('string', UserFactory::randomOrCreate()->name); + +assertType('string', proxy(UserFactory::createOne())->name); +assertType('string', proxy(UserFactory::new()->create())->name); + +assertType('string', UserFactory::all()[0]->name); +assertType("string", UserFactory::createMany(1)[0]->name); +assertType("string", UserFactory::createRange(1, 2)[0]->name); +assertType("string", UserFactory::createSequence([])[0]->name); +assertType("string", UserFactory::randomRange(1, 2)[0]->name); +assertType("string", UserFactory::randomSet(2)[0]->name); +assertType("string", UserFactory::findBy(['name' => 'foo'])[0]->name); + +assertType("string", UserFactory::new()->many(2)->create()[0]->name); +assertType("string", UserFactory::new()->range(1, 2)->create()[0]->name); +assertType("string", UserFactory::new()->sequence([])->create()[0]->name); + +assertType("string|null", $repository->first()?->name); +assertType('string', $repository->firstOrFail()->name); +assertType("string|null", $repository->last()?->name); +assertType('string', $repository->lastOrFail()->name); +assertType("string|null", $repository->find(1)?->name); +assertType("string", $repository->findOrFail(1)->name); +assertType("string|null", $repository->findOneBy([])?->name); +assertType('string', $repository->random()->name); +assertType("string", $repository->findAll()[0]->name); +assertType("string", $repository->findBy([])[0]->name); +assertType("string", $repository->randomSet(2)[0]->name); +assertType("string", $repository->randomRange(1, 2)[0]->name); diff --git a/stubs/phpstan/PersistentProxyObjectFactory.php b/stubs/phpstan/PersistentProxyObjectFactory.php new file mode 100644 index 000000000..c5768d3d5 --- /dev/null +++ b/stubs/phpstan/PersistentProxyObjectFactory.php @@ -0,0 +1,134 @@ + + */ +final class UserProxyFactory extends PersistentProxyObjectFactory +{ + public static function class(): string + { + return UserForProxyFactory::class; + } + + protected function defaults(): array|callable + { + return []; + } +} + +$proxyType = 'UserForProxyFactory&Zenstruck\Foundry\Persistence\Proxy'; + +// methods returning one object +assertType($proxyType, UserProxyFactory::new()->create()); +assertType($proxyType, UserProxyFactory::createOne()); +assertType($proxyType, UserProxyFactory::first()); +assertType($proxyType, UserProxyFactory::last()); +assertType($proxyType, UserProxyFactory::find(1)); +assertType($proxyType, UserProxyFactory::random()); +assertType($proxyType, UserProxyFactory::findOrCreate([])); +assertType($proxyType, UserProxyFactory::randomOrCreate()); +assertType($proxyType, UserProxyFactory::new()->instantiateWith(Instantiator::withConstructor())->with()->create()); + +// methods returning a list of objects +assertType("array", UserProxyFactory::all()); +assertType("array", UserProxyFactory::createMany(1)); +assertType("array", UserProxyFactory::createRange(1, 2)); +assertType("array", UserProxyFactory::createSequence([])); +assertType("array", UserProxyFactory::randomRange(1, 2)); +assertType("array", UserProxyFactory::randomSet(2)); +assertType("array", UserProxyFactory::findBy(['name' => 'foo'])); + +// methods with FactoryCollection +$factoryCollection = FactoryCollection::class; +assertType("{$factoryCollection}<{$proxyType}>", UserProxyFactory::new()->many(2)); +assertType("{$factoryCollection}<{$proxyType}>", UserProxyFactory::new()->range(1, 2)); +assertType("{$factoryCollection}<{$proxyType}>", UserProxyFactory::new()->sequence([])); +assertType("array", UserProxyFactory::new()->many(2)->create()); +assertType("array", UserProxyFactory::new()->range(1, 2)->create()); +assertType("array", UserProxyFactory::new()->sequence([])->create()); + +// methods using repository() +$repository = UserProxyFactory::repository(); +assertType("Zenstruck\Foundry\Persistence\ProxyRepositoryDecorator>", $repository); +assertType("({$proxyType})|null", $repository->first()); +assertType($proxyType, $repository->firstOrFail()); +assertType("({$proxyType})|null", $repository->last()); +assertType($proxyType, $repository->lastOrFail()); +assertType("({$proxyType})|null", $repository->find(1)); +assertType($proxyType, $repository->findOrFail(1)); +assertType("({$proxyType})|null", $repository->findOneBy([])); +assertType($proxyType, $repository->random()); +assertType("array<{$proxyType}>", $repository->findAll()); +assertType("array<{$proxyType}>", $repository->findBy([])); +assertType("array<{$proxyType}>", $repository->randomSet(2)); +assertType("array<{$proxyType}>", $repository->randomRange(1, 2)); +assertType('int', $repository->count()); + +// check proxy methods +assertType($proxyType, UserProxyFactory::new()->create()->_refresh()); +assertType($proxyType, UserProxyFactory::createOne()->_refresh()); +assertType('UserForProxyFactory', UserProxyFactory::createOne()->_real()); + +// test autocomplete with phpstorm +assertType('string', UserProxyFactory::new()->create()->name); +assertType('string', UserProxyFactory::new()->instantiateWith(Instantiator::withConstructor())->create()->name); +assertType('string', UserProxyFactory::new()->with()->create()->name); +assertType('string', UserProxyFactory::new()->create()->_refresh()->name); +assertType('string', UserProxyFactory::new()->create()->_real()->name); // ⚠️ no auto-complete ?! +assertType('string', UserProxyFactory::createOne()->name); +assertType('string', UserProxyFactory::createOne()->_refresh()->name); +assertType('string', UserProxyFactory::createOne()->_real()->name); +assertType('string', UserProxyFactory::first()->name); +assertType('string', UserProxyFactory::first()->_refresh()->name); +assertType('string', UserProxyFactory::last()->name); +assertType('string', UserProxyFactory::find(1)->name); +assertType('string', UserProxyFactory::find(1)->_refresh()->name); +assertType('string', UserProxyFactory::random()->name); +assertType('string', UserProxyFactory::random()->_refresh()->name); +assertType('string', UserProxyFactory::findOrCreate([])->name); +assertType('string', UserProxyFactory::findOrCreate([])->_refresh()->name); +assertType('string', UserProxyFactory::randomOrCreate()->name); +assertType('string', UserProxyFactory::randomOrCreate()->_refresh()->name); + +assertType('string', unproxy(UserProxyFactory::createOne())->name); +assertType('string', unproxy(UserProxyFactory::new()->create())->name); + +assertType('string', UserProxyFactory::all()[0]->name); +assertType("string", UserProxyFactory::createMany(1)[0]->name); +assertType("string", UserProxyFactory::createRange(1, 2)[0]->name); +assertType("string", UserProxyFactory::createSequence([])[0]->name); +assertType("string", UserProxyFactory::randomRange(1, 2)[0]->name); +assertType("string", UserProxyFactory::randomSet(2)[0]->name); +assertType("string", UserProxyFactory::findBy(['name' => 'foo'])[0]->name); + +assertType("string", UserProxyFactory::new()->many(2)->create()[0]->name); +assertType("string", UserProxyFactory::new()->range(1, 2)->create()[0]->name); +assertType("string", UserProxyFactory::new()->sequence([])->create()[0]->name); + +assertType("string|null", $repository->first()?->name); +assertType('string', $repository->firstOrFail()->name); +assertType('string', $repository->firstOrFail()->_refresh()->name); +assertType("string|null", $repository->last()?->name); +assertType('string', $repository->lastOrFail()->name); +assertType("string|null", $repository->find(1)?->name); +assertType("string", $repository->findOrFail(1)->name); +assertType("string|null", $repository->findOneBy([])?->name); +assertType('string', $repository->random()->name); +assertType("string", $repository->findAll()[0]->name); +assertType("string", $repository->findBy([])[0]->name); +assertType("string", $repository->randomSet(2)[0]->name); +assertType("string", $repository->randomRange(1, 2)[0]->name); diff --git a/stubs/phpstan/functions.php b/stubs/phpstan/functions.php new file mode 100644 index 000000000..a57973e41 --- /dev/null +++ b/stubs/phpstan/functions.php @@ -0,0 +1,26 @@ +create()->name); +assertType('string', object(UserForPersistentFactory::class)->name); + +assertType('string', persistent_factory(UserForPersistentFactory::class)->create()->name); +assertType('string', persist(UserForPersistentFactory::class)->name); + +assertType('UserForPersistentFactory|null', repository(UserForPersistentFactory::class)->find(1)); + +assertType('UserForPersistentFactory&Zenstruck\Foundry\Persistence\Proxy', proxy(object(UserForPersistentFactory::class))); +assertType('string', proxy(object(UserForPersistentFactory::class))->name); +assertType('string', proxy(object(UserForPersistentFactory::class))->_refresh()->name); diff --git a/stubs/psalm/ObjectFactory.php b/stubs/psalm/ObjectFactory.php new file mode 100644 index 000000000..f51369012 --- /dev/null +++ b/stubs/psalm/ObjectFactory.php @@ -0,0 +1,63 @@ + + */ +final class UserObjectFactory extends ObjectFactory +{ + public static function class(): string + { + return UserForObjectFactory::class; + } + + protected function defaults(): array|callable + { + return []; + } +} + +// methods returning one object +/** @psalm-check-type-exact $var = UserForObjectFactory */ +$var = UserObjectFactory::new()->create(); +/** @psalm-check-type-exact $var = UserForObjectFactory */ +$var = UserObjectFactory::createOne(); +/** @psalm-check-type-exact $var = UserForObjectFactory */ +$var = UserObjectFactory::new()->instantiateWith(Instantiator::withConstructor())->create(); +/** @psalm-check-type-exact $var = UserForObjectFactory */ +$var = UserObjectFactory::new()->with()->create(); + +// methods returning a list of objects +/** @psalm-check-type-exact $var = list */ +$var = UserObjectFactory::createMany(1); +/** @psalm-check-type-exact $var = list */ +$var = UserObjectFactory::createRange(1, 2); +/** @psalm-check-type-exact $var = list */ +$var = UserObjectFactory::createSequence([]); + +// methods with FactoryCollection +/** @psalm-check-type-exact $var = FactoryCollection */ +$var = UserObjectFactory::new()->many(2); +/** @psalm-check-type-exact $var = FactoryCollection */ +$var = UserObjectFactory::new()->range(1, 2); +/** @psalm-check-type-exact $var = FactoryCollection */ +$var = UserObjectFactory::new()->sequence([]); +/** @psalm-check-type-exact $var = list */ +$var = UserObjectFactory::new()->many(2)->create(); +/** @psalm-check-type-exact $var = list */ +$var = UserObjectFactory::new()->range(1, 2)->create(); +/** @psalm-check-type-exact $var = list */ +$var = UserObjectFactory::new()->sequence([])->create(); diff --git a/stubs/psalm/PersistentObjectFactory.php b/stubs/psalm/PersistentObjectFactory.php new file mode 100644 index 000000000..8528730ee --- /dev/null +++ b/stubs/psalm/PersistentObjectFactory.php @@ -0,0 +1,114 @@ + + */ +final class UserFactory extends PersistentObjectFactory +{ + public static function class(): string + { + return UserForPersistentFactory::class; + } + + protected function defaults(): array|callable + { + return []; + } +} + +// methods returning one object +/** @psalm-check-type-exact $var = UserForPersistentFactory */ +$var = UserFactory::new()->create(); +/** @psalm-check-type-exact $var = UserForPersistentFactory */ +$var = UserFactory::createOne(); +/** @psalm-check-type-exact $var = UserForPersistentFactory */ +$var = UserFactory::first(); +/** @psalm-check-type-exact $var = UserForPersistentFactory */ +$var = UserFactory::last(); +/** @psalm-check-type-exact $var = UserForPersistentFactory */ +$var = UserFactory::find(1); +/** @psalm-check-type-exact $var = UserForPersistentFactory */ +$var = UserFactory::random(); +/** @psalm-check-type-exact $var = UserForPersistentFactory */ +$var = UserFactory::findOrCreate([]); +/** @psalm-check-type-exact $var = UserForPersistentFactory */ +$var = UserFactory::randomOrCreate(); +/** @psalm-check-type-exact $var = UserForPersistentFactory */ +$var = UserFactory::new()->instantiateWith(Instantiator::withConstructor())->create(); +/** @psalm-check-type-exact $var = UserForPersistentFactory */ +$var = UserFactory::new()->with()->create(); + +// methods returning a list of objects +/** @psalm-check-type-exact $var = list */ +$var = UserFactory::all(); +/** @psalm-check-type-exact $var = list */ +$var = UserFactory::createMany(1); +/** @psalm-check-type-exact $var = list */ +$var = UserFactory::createRange(1, 2); +/** @psalm-check-type-exact $var = list */ +$var = UserFactory::createSequence([]); +/** @psalm-check-type-exact $var = list */ +$var = UserFactory::randomRange(1, 2); +/** @psalm-check-type-exact $var = list */ +$var = UserFactory::randomSet(2); +/** @psalm-check-type-exact $var = list */ +$var = UserFactory::findBy(['name' => 'foo']); + +// methods with FactoryCollection +/** @psalm-check-type-exact $var = FactoryCollection */ +$var = UserFactory::new()->many(2); +/** @psalm-check-type-exact $var = FactoryCollection */ +$var = UserFactory::new()->range(1, 2); +/** @psalm-check-type-exact $var = FactoryCollection */ +$var = UserFactory::new()->sequence([]); +/** @psalm-check-type-exact $var = list */ +$var = UserFactory::new()->many(2)->create(); +/** @psalm-check-type-exact $var = list */ +$var = UserFactory::new()->range(1, 2)->create(); +/** @psalm-check-type-exact $var = list */ +$var = UserFactory::new()->sequence([])->create(); + +// methods using repository() +$repository = UserFactory::repository(); +/** @psalm-check-type-exact $var = RepositoryDecorator> */ +$var = $repository; +/** @psalm-check-type-exact $var = UserForPersistentFactory|null */ +$var = $repository->first(); +/** @psalm-check-type-exact $var = UserForPersistentFactory */ +$var = $repository->firstOrFail(); +/** @psalm-check-type-exact $var = UserForPersistentFactory|null */ +$var = $repository->last(); +/** @psalm-check-type-exact $var = UserForPersistentFactory */ +$var = $repository->lastOrFail(); +/** @psalm-check-type-exact $var = UserForPersistentFactory|null */ +$var = $repository->find(1); +/** @psalm-check-type-exact $var = UserForPersistentFactory */ +$var = $repository->findOrFail(1); +/** @psalm-check-type-exact $var = UserForPersistentFactory|null */ +$var = $repository->findOneBy([]); +/** @psalm-check-type-exact $var = UserForPersistentFactory */ +$var = $repository->random(); +/** @psalm-check-type-exact $var = array */ +$var = $repository->findAll(); +/** @psalm-check-type-exact $var = array */ +$var = $repository->findBy([]); +/** @psalm-check-type-exact $var = array */ +$var = $repository->randomSet(2); +/** @psalm-check-type-exact $var = array */ +$var = $repository->randomRange(1, 2); +/** @psalm-check-type-exact $var = int */ +$var = $repository->count(); diff --git a/stubs/psalm/PersistentProxyObjectFactory.php b/stubs/psalm/PersistentProxyObjectFactory.php new file mode 100644 index 000000000..10a8f4e5f --- /dev/null +++ b/stubs/psalm/PersistentProxyObjectFactory.php @@ -0,0 +1,122 @@ + + */ +final class UserProxyFactory extends PersistentProxyObjectFactory +{ + public static function class(): string + { + return UserForProxyFactory::class; + } + + protected function defaults(): array|callable + { + return []; + } +} + +// methods returning one object +/** @psalm-check-type-exact $var = UserForProxyFactory&Proxy */ +$var = UserProxyFactory::new()->create(); +/** @psalm-check-type-exact $var = UserForProxyFactory&Proxy */ +$var = UserProxyFactory::createOne(); +/** @psalm-check-type-exact $var = UserForProxyFactory&Proxy */ +$var = UserProxyFactory::first(); +/** @psalm-check-type-exact $var = UserForProxyFactory&Proxy */ +$var = UserProxyFactory::last(); +/** @psalm-check-type-exact $var = UserForProxyFactory&Proxy */ +$var = UserProxyFactory::find(1); +/** @psalm-check-type-exact $var = UserForProxyFactory&Proxy */ +$var = UserProxyFactory::random(); +/** @psalm-check-type-exact $var = UserForProxyFactory&Proxy */ +$var = UserProxyFactory::findOrCreate([]); +/** @psalm-check-type-exact $var = UserForProxyFactory&Proxy */ +$var = UserProxyFactory::randomOrCreate(); +/** @psalm-check-type-exact $var = UserForProxyFactory&Proxy */ +$var = UserProxyFactory::new()->instantiateWith(Instantiator::withConstructor())->create(); +/** @psalm-check-type-exact $var = UserForProxyFactory&Proxy */ +$var = UserProxyFactory::new()->with()->create(); + +// methods returning a list of objects +/** @psalm-check-type-exact $var = list> */ +$var = UserProxyFactory::all(); +/** @psalm-check-type-exact $var = list> */ +$var = UserProxyFactory::createMany(1); +/** @psalm-check-type-exact $var = list> */ +$var = UserProxyFactory::createRange(1, 2); +/** @psalm-check-type-exact $var = list> */ +$var = UserProxyFactory::createSequence([]); +/** @psalm-check-type-exact $var = list> */ +$var = UserProxyFactory::randomRange(1, 2); +/** @psalm-check-type-exact $var = list> */ +$var = UserProxyFactory::randomSet(2); +/** @psalm-check-type-exact $var = list> */ +$var = UserProxyFactory::findBy(['name' => 'foo']); + +// methods with FactoryCollection +/** @psalm-check-type-exact $var = FactoryCollection> */ +$var = UserProxyFactory::new()->many(2); +/** @psalm-check-type-exact $var = FactoryCollection> */ +$var = UserProxyFactory::new()->range(1, 2); +/** @psalm-check-type-exact $var = FactoryCollection> */ +$var = UserProxyFactory::new()->sequence([]); +/** @psalm-check-type-exact $var = list> */ +$var = UserProxyFactory::new()->many(2)->create(); +/** @psalm-check-type-exact $var = list> */ +$var = UserProxyFactory::new()->range(1, 2)->create(); +/** @psalm-check-type-exact $var = list> */ +$var = UserProxyFactory::new()->sequence([])->create(); + +// methods using repository() +$repository = UserProxyFactory::repository(); +/** @psalm-check-type-exact $var = ProxyRepositoryDecorator> */ +$var = $repository; +/** @psalm-check-type-exact $var = (UserForProxyFactory&Proxy)|null */ +$var = $repository->first(); +/** @psalm-check-type-exact $var = UserForProxyFactory&Proxy */ +$var = $repository->firstOrFail(); +/** @psalm-check-type-exact $var = (UserForProxyFactory&Proxy)|null */ +$var = $repository->last(); +/** @psalm-check-type-exact $var = UserForProxyFactory&Proxy */ +$var = $repository->lastOrFail(); +/** @psalm-check-type-exact $var = (UserForProxyFactory&Proxy)|null */ +$var = $repository->find(1); +/** @psalm-check-type-exact $var = UserForProxyFactory&Proxy */ +$var = $repository->findOrFail(1); +/** @psalm-check-type-exact $var = (UserForProxyFactory&Proxy)|null */ +$var = $repository->findOneBy([]); +/** @psalm-check-type-exact $var = UserForProxyFactory&Proxy */ +$var = $repository->random(); +/** @psalm-check-type-exact $var = array> */ +$var = $repository->findAll(); +/** @psalm-check-type-exact $var = array> */ +$var = $repository->findBy([]); +/** @psalm-check-type-exact $var = array> */ +$var = $repository->randomSet(2); +/** @psalm-check-type-exact $var = array> */ +$var = $repository->randomRange(1, 2); +/** @psalm-check-type-exact $var = int */ +$var = $repository->count(); + +// check proxy methods +/** @psalm-check-type-exact $var = UserForProxyFactory&Proxy */ +$var = UserProxyFactory::new()->create()->_refresh(); +/** @psalm-check-type-exact $var = UserForProxyFactory&Proxy */ +$var = UserProxyFactory::createOne()->_refresh(); +/** @psalm-check-type-exact $var = UserForProxyFactory */ +$var = UserProxyFactory::createOne()->_real(); diff --git a/stubs/psalm/functions.php b/stubs/psalm/functions.php new file mode 100644 index 000000000..102e3e2ca --- /dev/null +++ b/stubs/psalm/functions.php @@ -0,0 +1,39 @@ +create(); + +/** @psalm-check-type-exact $user = User */ +$user = object(User::class); + +/** @psalm-check-type-exact $user = User */ +$user = persistent_factory(User::class)->create(); + +/** @psalm-check-type-exact $user = User */ +$user = persist(User::class); + +/** @psalm-check-type-exact $user = User|null */ +$user = repository(User::class)->find(1); + +/** @psalm-check-type-exact $user = User&Zenstruck\Foundry\Persistence\Proxy */ +$user = proxy(object(User::class)); + +/** @psalm-check-type-exact $name = string */ +$name = proxy(object(User::class))->name; + +/** @psalm-check-type-exact $user = User&Zenstruck\Foundry\Persistence\Proxy */ +$user = proxy(object(User::class))->_refresh(); diff --git a/tests/Integration/Mongo/EmbeddableDocumentFactoryTest.php b/tests/Integration/Mongo/EmbeddableDocumentFactoryTest.php index 90b396c31..450b422ca 100644 --- a/tests/Integration/Mongo/EmbeddableDocumentFactoryTest.php +++ b/tests/Integration/Mongo/EmbeddableDocumentFactoryTest.php @@ -13,6 +13,8 @@ use Zenstruck\Foundry\Persistence\PersistentObjectFactory; use Zenstruck\Foundry\Tests\Fixture\Document\WithEmbeddableDocument; +use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\Contact\CascadeContactFactory; +use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\Contact\ProxyCascadeContactFactory; use Zenstruck\Foundry\Tests\Fixture\Model\Embeddable; use Zenstruck\Foundry\Tests\Integration\Persistence\EmbeddableFactoryTestCase; use Zenstruck\Foundry\Tests\Integration\RequiresMongo; diff --git a/utils/psalm/FixProxyFactoryMethodsReturnType.php b/utils/psalm/FixProxyFactoryMethodsReturnType.php new file mode 100644 index 000000000..b9bb6be0f --- /dev/null +++ b/utils/psalm/FixProxyFactoryMethodsReturnType.php @@ -0,0 +1,63 @@ +getMethodId()); + + if ($event->getCodebase()->classExtends($class, PersistentProxyObjectFactory::class)) { + $templateType = $event->getCodebase()->classlikes->getStorageFor( + $class + )->template_extended_params[PersistentProxyObjectFactory::class]['T'] ?? null; + + if (!$templateType) { + return; + } + + $templateTypeAsString = $templateType->getId(); + $proxyTypeHint = "{$templateTypeAsString}&Zenstruck\\Foundry\\Persistence\\Proxy<{$templateTypeAsString}>"; + + $methodsReturningObject = ['create', 'createone', 'find', 'findorcreate', 'first', 'last', 'random', 'randomorcreate']; + if (\in_array($method, $methodsReturningObject, true)) { + $event->setReturnTypeCandidate(Type::parseString($proxyTypeHint)); + } + + $methodsReturningListOfObjects = ['all', 'createmany', 'createrange', 'createsequence', 'findby', 'randomrange', 'randomset']; + if (\in_array($method, $methodsReturningListOfObjects, true)) { + $event->setReturnTypeCandidate(Type::parseString("list<{$proxyTypeHint}>")); + } + + $methodsReturningFactoryCollection = ['many', 'range', 'sequence']; + if (\in_array($method, $methodsReturningFactoryCollection, true)) { + $factoryCollectionClass = FactoryCollection::class; + $event->setReturnTypeCandidate(Type::parseString("{$factoryCollectionClass}<{$proxyTypeHint}>")); + } + + if ($method === 'repository' + // if repository() method is overridden in userland, we should not change the return type + && str_starts_with($event->getReturnTypeCandidate()->getId(), ProxyRepositoryDecorator::class) + ) { + $repositoryDecoratorClass = ProxyRepositoryDecorator::class; + $doctrineRepositoryClass = ObjectRepository::class; + $event->setReturnTypeCandidate( + Type::parseString( + "{$repositoryDecoratorClass}<{$templateTypeAsString}, {$doctrineRepositoryClass}<$templateTypeAsString>>" + ) + ); + } + } + } +} diff --git a/utils/psalm/FoundryPlugin.php b/utils/psalm/FoundryPlugin.php new file mode 100644 index 000000000..da15803ea --- /dev/null +++ b/utils/psalm/FoundryPlugin.php @@ -0,0 +1,16 @@ +registerHooksFromClass(FixProxyFactoryMethodsReturnType::class); + } +} From 39ec45b5e94819e82b7b599b10a3a33d5b1e9a3a Mon Sep 17 00:00:00 2001 From: nikophil Date: Tue, 22 Oct 2024 16:51:45 +0000 Subject: [PATCH 2/2] bot: fix cs [skip ci] --- src/Persistence/PersistentObjectFactory.php | 2 ++ src/Persistence/Proxy.php | 1 - src/Persistence/RepositoryDecorator.php | 1 + tests/Integration/Mongo/EmbeddableDocumentFactoryTest.php | 2 -- 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Persistence/PersistentObjectFactory.php b/src/Persistence/PersistentObjectFactory.php index 2165b614c..cdc484df5 100644 --- a/src/Persistence/PersistentObjectFactory.php +++ b/src/Persistence/PersistentObjectFactory.php @@ -134,6 +134,7 @@ public static function first(string $sortBy = 'id'): object { /** @var T $object */ $object = static::repository()->firstOrFail($sortBy); + return $object; } @@ -146,6 +147,7 @@ public static function last(string $sortBy = 'id'): object { /** @var T $object */ $object = static::repository()->lastOrFail($sortBy); + return $object; } diff --git a/src/Persistence/Proxy.php b/src/Persistence/Proxy.php index 6a02e4b0b..eaa49192d 100644 --- a/src/Persistence/Proxy.php +++ b/src/Persistence/Proxy.php @@ -47,7 +47,6 @@ public function _withoutAutoRefresh(callable $callback): static; public function _save(): static; /** - * @return static * @psalm-return T&Proxy * @phpstan-return static */ diff --git a/src/Persistence/RepositoryDecorator.php b/src/Persistence/RepositoryDecorator.php index 922c308e6..805d99002 100644 --- a/src/Persistence/RepositoryDecorator.php +++ b/src/Persistence/RepositoryDecorator.php @@ -94,6 +94,7 @@ public function find($id): ?object if (\is_array($id) && (empty($id) || !array_is_list($id))) { /** @var T|null $object */ $object = $this->findOneBy($id); + return $object; } diff --git a/tests/Integration/Mongo/EmbeddableDocumentFactoryTest.php b/tests/Integration/Mongo/EmbeddableDocumentFactoryTest.php index 450b422ca..90b396c31 100644 --- a/tests/Integration/Mongo/EmbeddableDocumentFactoryTest.php +++ b/tests/Integration/Mongo/EmbeddableDocumentFactoryTest.php @@ -13,8 +13,6 @@ use Zenstruck\Foundry\Persistence\PersistentObjectFactory; use Zenstruck\Foundry\Tests\Fixture\Document\WithEmbeddableDocument; -use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\Contact\CascadeContactFactory; -use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\Contact\ProxyCascadeContactFactory; use Zenstruck\Foundry\Tests\Fixture\Model\Embeddable; use Zenstruck\Foundry\Tests\Integration\Persistence\EmbeddableFactoryTestCase; use Zenstruck\Foundry\Tests\Integration\RequiresMongo;