feat: honor factory state when a create helper is called on an instance - #1164
feat: honor factory state when a create helper is called on an instance#1164Amoifr wants to merge 5 commits into
Conversation
ffac41e to
f47a48f
Compare
There was a problem hiding this comment.
Hi @Amoifr
thank you to take care of this!
Since 2.x does not break in a minor, I went for: deprecate in 2.13, honor the state so nobody gets wrong objects in the meantime, throw in 3
I'm OK with this, let's not break people's CI 😅
I could not run the persistence suites locally
You could run the tests with sqlite (no need for mysql or pgsql). Just do this change:
# .env.local
DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
Also please, rebase and fix the CI (you'll have to add #[IgnoreDeprecation] to ObjectFactoryTest::create_helpers_called_on_an_instance_are_deprecated)
One more thing: whether you're using AI to write code is up to you and I'm totally fine with it, but please, prevent AI slop in the PR description, or at least make it understandable by a human 🙏 such a sentence is pretty hard to grasp (ie: what is "my parenthesis" in this context?):
The one decision I had to make for you. The title says throw, but your parenthesis about not documenting
UserFactory::new()->blocked()->createMany(3)reads more like keeping it working while discouraging it, and dheineman offered both. Since 2.x does not break in a minor, I went for: deprecate in 2.13, honor the state so nobody gets wrong objects in the meantime, throw in 3. Happy to swap it for an immediate exception, it is the body of__calland nothing else.
| * @group legacy | ||
| */ | ||
| #[Test] | ||
| #[IgnoreDeprecations] |
There was a problem hiding this comment.
you can pass a regex to #[IgnoreDeprecations], so that all deprecations are not ignored
There was a problem hiding this comment.
Done in bcf9f2b. One gotcha for the next time: PHPUnit builds the regex itself with preg_match('{' . $pattern . '}', $message), so the pattern goes in without delimiters. With /…/ nothing gets ignored.
| * @return T | ||
| */ | ||
| public static function createOne(array|callable $attributes = []): mixed | ||
| protected static function doCreateOne(array|callable $attributes = []): mixed |
There was a problem hiding this comment.
Since you'll remove PersistentProxyObjectFactory::doCreateOne() method, I think all of those method can become private? or at least @internal?
There was a problem hiding this comment.
All four are private now rather than @internal, since the only override was the one above. __callStatic() still reaches them, it calls them from Factory's own scope.
| * @phpstan-return T&Proxy<T> | ||
| */ | ||
| final public static function createOne(array|callable $attributes = []): mixed | ||
| final protected static function doCreateOne(array|callable $attributes = []): mixed |
There was a problem hiding this comment.
please remove this whole method please
it was used to patch a problem in PHPStorm auto-completion, but it is now not needed anymore
There was a problem hiding this comment.
Removed in bcf9f2b. It was proxying twice on top of that: parent::doCreateOne() calls static::new()->create(), and that create() is the proxying one.
f47a48f to
9bc1a50
Compare
|
Rebased, and On the description: you are right, that paragraph was unreadable. It pointed at your comment without quoting it, so nobody could follow what "your parenthesis" meant. I rewrote it, quoting the passage and stating the choice plainly. On sqlite: thanks, but the tip does not save me here. This machine has neither |
|
hey @Amoifr
why don't you just... install it ? like on the other PR, you're using one last thing: could you give me the recipe of the strawberry pie, please? |
The RequiresPhpunit attribute does not exist there either, so the docblock annotation is what actually skips them, as the rest of the suite does.
|
Both fixed in d153f88:
As for the pie: no recipe, but let me answer the question behind it, with no hard feelings. I contribute here on my own time and for free, because I like the project. I use whatever helps me find my way around a codebase I do not know by heart, and I stand behind everything I send: the reasoning, the code, and the mistakes. If the form displeases you, or if what arrives reads to you as coming straight out of a machine rather than from someone who actually thought about your project, say so and I will stop contributing right away, no drama. That said, Darwin did have a point about adapting or dying. 😉 |
yeah no problem, it just feels more comfortable to be able to run tests locally, given that
I'm not saying not to use any LLM, of course they are pretty useful to write code and understand what's going on, moreover on a new code base. I'm using them everyday as well. But my point is that OSS is a lot about communication, and LLMs are pretty bad for that, and by not filtering its output, you're putting this burden on the package maintainers 🤷 |
|
By the way, you can ignore the failing check "backward compatibility check", no BC break here |
|
The other red check is mine: "Psalm on factories generated with maker" passes on 2.x, fails here. Psalm does not bind the class template through Fixing it means Also, the PR description says the conditional return types are preserved: I checked PHPStan, not Psalm. |
yes that would be nice, thanks! 🙏 |
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WUCMYykYqJHv67fmwiWZip
|
Done in 576c477, the Psalm job is back to zero errors and inference goes from 98.5% to 100%. One correction to what I proposed:
Verified locally: Psalm clean, |
| * @method static T createOne(array|callable $attributes = []) | ||
| * @method static list<T> createMany(int $number, array|callable $attributes = []) | ||
| * @method static list<T> createRange(int $min, int $max, array|callable $attributes = []) | ||
| * @method static list<T> createSequence(iterable|callable $sequence) | ||
| * | ||
| * @phpstan-method static T createOne(Attributes $attributes = []) | ||
| * @phpstan-method static ($number is positive-int ? non-empty-list<T> : list<T>) createMany(int $number, Attributes $attributes = []) | ||
| * @phpstan-method static ($min is positive-int ? non-empty-list<T> : list<T>) createRange(int $min, int $max, Attributes $attributes = []) | ||
| * @phpstan-method static list<T> createSequence(Sequence $sequence) |
There was a problem hiding this comment.
does this work?
| * @method static T createOne(array|callable $attributes = []) | |
| * @method static list<T> createMany(int $number, array|callable $attributes = []) | |
| * @method static list<T> createRange(int $min, int $max, array|callable $attributes = []) | |
| * @method static list<T> createSequence(iterable|callable $sequence) | |
| * | |
| * @phpstan-method static T createOne(Attributes $attributes = []) | |
| * @phpstan-method static ($number is positive-int ? non-empty-list<T> : list<T>) createMany(int $number, Attributes $attributes = []) | |
| * @phpstan-method static ($min is positive-int ? non-empty-list<T> : list<T>) createRange(int $min, int $max, Attributes $attributes = []) | |
| * @phpstan-method static list<T> createSequence(Sequence $sequence) | |
| * @psalm-method static T createOne(array|callable $attributes = []) | |
| * @psalm-method static list<T> createMany(int $number, array|callable $attributes = []) | |
| * @psalm-method static list<T> createRange(int $min, int $max, array|callable $attributes = []) | |
| * @psalm-method static list<T> createSequence(iterable|callable $sequence) | |
| * | |
| * @method static T createOne(Attributes $attributes = []) | |
| * @method static ($number is positive-int ? non-empty-list<T> : list<T>) createMany(int $number, Attributes $attributes = []) | |
| * @method static ($min is positive-int ? non-empty-list<T> : list<T>) createRange(int $min, int $max, Attributes $attributes = []) | |
| * @method static list<T> createSequence(Sequence $sequence) |
because otherwise, PHPStorm does not understands anymore that PostFactory::createOne() returns a Post
There was a problem hiding this comment.
Almost. Psalm is happy with it and PHPStorm gets its T back, but PHPStan prefers @psalm-method over @method too, so it falls back to the plain signatures and the stubs/phpstan assertions drop to 15 errors. I kept your ordering and added a third block of @phpstan-method tags: PHPStorm reads @method, PHPStan @phpstan-method, Psalm @psalm-method. Done in bcf9f2b.
…ers down Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WUCMYykYqJHv67fmwiWZip
Fixes #799
The bug
SomeFactory::new()->someState()->createMany(2)silently threw the state away. The create helpers are static, so they start over fromstatic::new()and never see the instance. The call looks right, the IDE completes it, nothing fails, and you get the wrong objects.The fix
The four helpers now go through
__callStatic, so a call made on an instance lands in__callinstead, where$thisis available and the state can be honored.This is the route you suggested in the issue. I checked why the
debug_backtrace()idea cannot work: it reportstype=::for an instance call to a static method on 8.2, 8.4 and 8.5 alike, so there is no signal to read inside the method.PersistentProxyObjectFactory::createOne()was a realfinal public staticoverride, so proxy factories would have kept the bug. It is nowdoCreateOne()like the others.Deprecate now, throw in 3
The issue title asks for an exception. Your comment on it says something slightly different:
That reads as "let the call work, just do not advertise it", rather than "make it throw". Since 2.x cannot break in a minor, I went for the middle: deprecate in 2.13, honor the state meanwhile so nobody gets wrong objects, throw in 3. Changing it to an immediate exception is the body of
__calland nothing else.Notes
@phpstan-methodkeeps the conditional return types and the aliases, and the assertions instubs/phpstanstill pass: without them PHPStan reports 17 errors, with them it is back to the one that already existed.@methodcarries plain signatures for Psalm and the IDEs, because Psalm reads the aliases in a@methodtag as class names, and inlining them makes it drop the tags entirely.FixCreateHelpersReturnType. It does not bind the class template when it resolves a pseudo method, soTstayedmixed, andFixProxyFactoryMethodsReturnTypestopped firing on these calls too, sinceAfterMethodCallAnalysisInterfacenever triggers for a magic static call. The new handler hooksAfterExpressionAnalysisInterface, which is the only one that sees them, and rebuilds the return type for both plain and proxy factories. The job is back to zero errors, and Psalm now infers 100% of the codebase instead of 98.5%.FactoriesTraitNotUsed.php:74reports the same unused@phpstan-ignoreon the base commit.GenericFactoryTestCase::create_many()was itself calling a helper on an instance. It now usesmany(3)->create().UPGRADE-2.13.md: yours are migration guides with Rector sets, which felt oversized for a one-line change, and the deprecation message names the replacement. Happy to add one.pdo_sqlitenorext-mongodb, so the kernel does not boot for those tests. Unit tests and PHPStan are what I can verify locally.