chore: raise PHPStan to level 9 - #134
Merged
Merged
Conversation
Level 9 stops accepting `mixed` where a concrete type is required. Most of the 35 errors came from one place: the renderer's config arrays and Nette's `getOption()`/`translate()`/`getValue()` all hand back `mixed`, which was then passed straight into typed APIs. - `getConfig()`/`getConfigOverride()` now declare their real shape (`array<string, mixed[]>`), so `fetchConfig()` returns an array instead of `mixed`. It reads them through the getters rather than the SmartObject magic properties, which is the same call without the untyped detour. - `configElem()` narrows what it reads out of the config instead of trusting it: a non-string element name is ignored, class config is normalized once, and a non-array `attributes` entry is skipped. Class handling moved into `fetchClasses()`, and `CLASS_REMOVE` uses a strict `array_filter` rather than `array_diff`, which required every class to be castable to string. - Group labels and validation messages are drawn only when they are something drawable (`HtmlStringable`, string, `Stringable`), instead of handing whatever the option happened to hold to `addHtml()`/`addText()`. - `ChoiceInputTrait` only indexes the disabled-values array with a usable array key, and compares choice values through `stringifyChoiceValue()`. - `CheckboxInput`, `CheckboxListInput` and `RadioInput` narrow the result of `translate()` before using it as a caption. `DateInput::validateFormat()` was a real bug, not just a typing gap: it passed `getValue()` to a `string` parameter, and `getValue()` returns a `DateTime` once the input has been validated, so calling the rule in that state was a guaranteed TypeError. The rule now checks the format only when there is text to check. `getValue()` itself gained the same guard before handing its value to `DateTime::createFromFormat()`. Rendered HTML is unchanged — no fixture updates. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #134 +/- ##
============================================
+ Coverage 97.48% 97.51% +0.02%
- Complexity 313 333 +20
============================================
Files 25 25
Lines 996 1007 +11
============================================
+ Hits 971 982 +11
Misses 25 25 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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.
Follow-up to #133. Raises the PHPStan level in
make phpstanfrom 8 to 9.Level 9 stops accepting
mixedwhere a concrete type is required. Most of the 35 errors traced back to one place: the renderer's config arrays and Nette'sgetOption()/translate()/getValue()all returnmixed, and that value was passed straight into typed APIs.Renderer config plumbing
getConfig()/getConfigOverride()now declare their real shape (array<string, mixed[]>andarray<int, array<string, mixed[]>>), sofetchConfig()returns an array rather thanmixed. It reads them through the getters instead of theSmartObjectmagic properties — the same call, without the untyped detour.configElem()narrows what it reads out of the config instead of trusting it. A non-string element name is ignored, class config is normalized once up front, and anattributesentry that is not an array is skipped rather than fed toforeach.fetchClasses()helper.CLASS_REMOVEnow uses a strictarray_filterinstead ofarray_diff, which insisted every class be castable to string — Nette also accepts['class-name' => bool]class attributes, and the filter handles those without a cast while preserving keys exactly asarray_diffdid.Drawing values that came from options
Group labels and validation messages are drawn only when they are actually drawable —
HtmlStringable/string/Stringable— instead of handing whatever the option happened to hold toaddHtml()oraddText(). Previously a group label option holding, say, an int went toaddHtml()unchecked.Inputs
ChoiceInputTraitonly indexes the disabled-values array with something usable as an array key, and compares choice values through astringifyChoiceValue()helper instead of castingmixedto string.CheckboxInput,CheckboxListInputandRadioInputnarrow the result oftranslate()before using it as a caption.CheckboxInput::makeCheckbox()'s$captiondoc widened fromstring|Html|nulltostring|Stringable|null, which is what it always accepted —setText()takesStringable, andHtmlis one.One real bug:
DateInput::validateFormat()This one was not just a typing gap. The rule passed
$input->getValue()into astringparameter, butgetValue()returns aDateTimeonce the input has been validated — so calling the rule in that state was a guaranteedTypeError:It survived in practice only because
BaseControl::validate()callscleanErrors()first, whichDateInputoverrides to reset itsisValidatedflag — so during normal form validationgetValue()still returns the raw text. Any other route to the rule crashed.The rule now checks the format only when there is text to check, and returns
trueotherwise (an already-parsed value has no textual format to reject; emptiness is the required rule's job).getValue()gained the same guard before handing its value toDateTime::createFromFormat().Verification
make phpstan(now-l 9) — no errorsmake cs— cleanmake tests— 173 tests, 266 assertions, all passing4 new tests: three pin the
DateInputrule behaviour (parsed value, unparseable text, null value on a nullable input), one covers class config given as a plain string rather than an array.Rendered HTML is unchanged — no fixture updates. The existing
RendererConfigTestcases forCLASS_SET/CLASS_ADD/CLASS_REMOVE/ATTRIBUTES/CONTAINERall still pass against the rewrittenconfigElem().🤖 Generated with Claude Code