Repository navigation
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Unconditional factory annotations allow static calls that fail at runtime on supported nette/utils versions and override existing variadic instance setters, requiring version-gated annotations and matching reflection handling.
2 open issues
What changed in this PR
This PR teaches the Nette extension to recognize Html::text() and Html::html() as static factories rather than instance attribute setters.
Changes:
- Adds factory annotations with late-static return types to the
Htmlstub. - Excludes both factory names from the dynamic attribute-method handler.
- Extends unit tests to verify those exclusions.
| File | Description |
|---|---|
| tests/Reflection/Nette/HtmlClassReflectionExtensionTest.php | Tests method-name-specific handling for factories and attribute setters. |
| stubs/Utils/Html.stub | Declares the two static factories. |
| src/Reflection/Nette/HtmlClassReflectionExtension.php | Defers factory-name resolution to the annotations. |
Review effort: Balanced
Comment on lines
+8
to
+9
| * @method static static text(mixed $text) | ||
| * @method static static html(mixed $html) |
| { | ||
| $classReflection = $this->reflectionProvider->getClass($className); | ||
| self::assertSame($result, $this->extension->hasMethod($classReflection, 'href')); | ||
| self::assertSame($result, $this->extension->hasMethod($classReflection, $methodName)); |
nette/utils 4.1.5 added `Html::text()` and `Html::html()` as `@method static` on the class and deprecated `Html::fromText()` and `Html::fromHtml()` in their favor. PHPStan never sees those declarations: the stub for the class replaces its docblock, and `HtmlClassReflectionExtension` claims every method name as an attribute setter, an instance method, so a call to either factory is reported as a static call to an instance method. The stub now declares the two methods the way the class does, and the extension leaves their names alone, because PHPStan asks the extension before it reads the annotations and the extension would otherwise keep winning. Either change alone is not enough: with only the stub the extension still answers first, with only the extension the calls are undefined, since the stub also hides `__callStatic()`.
spaze
force-pushed
the
spaze/html-methods
branch
from
November 6, 2026 00:29
3b028de to
7c6653b
Compare
spaze
added a commit
to spaze/michalspacek.cz
that referenced
this pull request
Nov 6, 2026
The app's packages move to
- nette/application 3.3.1
- nette/forms 3.3.1
- nette/utils 4.1.6
- nette/component-model 4.0
- Latte 3.1.7
- ... among others
The Lambda and dev-tools get their updates too. The Nette releases deprecate things this code used, with notices at runtime that Tester turns into failures, and tighten types the analysers now hold the code to, so the rest of the branch follows them:
- a running presenter's request is never null
- `#[Persistent]` replaces the `@persistent` annotation
- `Request::Forward` replaces `Request::FORWARD`
- captions and selected items go through their methods
- `Html::text()` and `Html::html()` replace `fromText()` and `fromHtml()`
- `{form}` and `{control}` take a comma between the name and the rest
Our `LinkGenerator` existed to turn the `string|null` of Nette's into a string. 3.3 types `link()` as a string unless a mode is given, so the wrapper is gone and its consumers inject Nette's directly.
The forms lose their CSRF token. nette/forms 3.3 deprecates `addProtection()` because a form submit that is not same-origin is refused before any handler runs, going by `Sec-Fetch-Site`, with the `_nss` cookie for browsers that don't send it; the check was same-site before and is same-origin now.
On top of that, `ResourceIsolationPolicy` already forwards a cross-site request that is not a plain top-level GET navigation to the Forbidden page, so the token was a third layer.
`FormFactory` and `UnprotectedFormFactory` existed only to add it or skip it, so they are deleted, every form factory builds its form itself, and the Forms section of CLAUDE.md describes what protects the forms now. One visible effect of the tightening: a form submit or a `#[Requires(sameOrigin: true)]` action reached from another subdomain is refused like a cross-site one, which a new test pins.
nette/forms types validators as `callable(never, never): bool` so that a callable with any parameter types fits. PHPStan infers an inline closure's parameter types from that and gets `never`, which turned the type checks in the validator closures into dead code, so those closures are methods now. Psalm reads the same type as a mismatch with the `callable(Control)` we pass, hence the handler in `psalm.xml`.
PHPStan doesn't know `Html::text()` and `Html::html()` yet: phpstan-nette's stub of the class hides their declaration and its reflection extension takes any method name for an attribute setter. `phpstan.neon` ignores exactly those two calls until phpstan/phpstan-nette#235 is released.
webauthn-lib 5.3.12 validates the submitted credential in its denormalizers and throws its own exception for valid JSON of the wrong shape. The guards around `deserialize()` caught only the serializer's exceptions, so such a credential ended as a 500 instead of the sign-in or registration error; they catch both now.
The vendor PHPStan config allows the `mt_rand()` and `uniqid()` calls the updated nette/caching and nette/utils make; neither picks anything secret.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


nette/utils 4.1.5 added
Html::text()andHtml::html()as@method staticon the class and deprecatedHtml::fromText()andHtml::fromHtml()in their favor. PHPStan never sees those declarations: the stub for the class replaces its docblock, andHtmlClassReflectionExtensionclaims every method name as an attribute setter, an instance method, so a call to either factory is reported as a static call to an instance method.The stub now declares the two methods the way the class does, and the extension leaves their names alone, because PHPStan asks the extension before it reads the annotations and the extension would otherwise keep winning. Either change alone is not enough: with only the stub the extension still answers first, with only the extension the calls are undefined, since the stub also hides
__callStatic().