Report array_column() reading a property absent from the array's object elements#5887
Report array_column() reading a property absent from the array's object elements#5887phpstan-bot wants to merge 7 commits into
array_column() reading a property absent from the array's object elements#5887Conversation
…ject elements - Add `PHPStan\Rules\Functions\ArrayColumnRule` that checks `array_column()` calls whose source array holds objects: when the `$column_key` (and `$index_key`) argument is a constant string naming a property that none of the element classes declare, it reports the call instead of silently inferring an empty array. - Cover both parallel arguments: `phpstan#2 $column_key` and `phpstan#3 $index_key` are checked with the same logic. - Stay conservative to avoid false positives: only objects are checked (array elements use offset access, scalars are skipped), enums are skipped (their `name`/`value` are always present), and classes with `__isset` + `__get` magic or that still allow dynamic properties (e.g. on PHP < 8.2) are skipped because the property might exist at runtime. This matches `AccessPropertiesRule`, which likewise reports undefined properties on non-final/abstract/interface types. - `array_column()` reads object properties even on `ArrayAccess` elements (never the offset), so the check keys off `Type::isObject()` rather than offset accessibility. - Gate the rule behind the new `arrayColumnObjectArrays` bleeding-edge feature toggle and register it on level 5 next to the other array-function rules. - Add `ArrayColumnRuleTest` plus a fixture covering non-final/final classes, the index argument, magic/dynamic/enum/array/union element types and the original issue reproducer.
staabm
left a comment
There was a problem hiding this comment.
the rule should re-use ArrayColumnHelper
Move the object-property-missing detection used by ArrayColumnRule into ArrayColumnHelper, the single home for array_column() semantics, and have the rule delegate to it instead of duplicating the property resolution. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Done. I addressed the reviewer's request to reuse What I changedThe tension: The rule reports missing properties on concrete and non-final element classes (the whole point of issue #5101, whose reproducer is The resolution: I moved the rule's property-missing detection into Mechanics:
Verification: the rule test, the One note for the reviewer: I kept the rule's existing behavior intact (reports non-final/concrete, skips enums/dynamic/magic/private) rather than switching it to the helper's |
staabm
left a comment
There was a problem hiding this comment.
this PR should also cover phpstan/phpstan#12188
staabm
left a comment
There was a problem hiding this comment.
add a test covering phpstan/phpstan#9671
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A pure enum exposes only `name`; a backed enum also exposes `value`. Any other property name read via array_column() yields an empty result, so report it - mirroring the object-property check. Closes phpstan/phpstan#12188 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Covers array<NonFinalObject|FinalObject>: a property present on either member is not reported, only one absent from both members is. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… arrays Adds the issue #9671 reproducer: array_column() on a list of internal-class objects (DateTimeImmutable) reading a property the class lacks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Done. Here's a summary of what I addressed. What the current review asked for
Issue #9671's reproducer is Changes
Reconciliation noteWhile I worked, another commit ( Verification
The other earlier review points were already handled in prior commits on the branch:
Commit pushed: |
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Summary
PHP's
array_column()can read a property off an array of objects(
array_column($objects, 'propertyName')). The reporter expected PHPStan to flagcalls that use a property name the objects don't have, since such a call silently
yields an empty array and is a refactoring footgun. PHPStan already infers the
correct return type for
array_column()on object arrays, but it never reportedthe obviously-wrong property name.
This adds a rule that reports
array_column()calls whose$column_key/$index_keynames a property absent from the objects in the source array.Changes
src/Rules/Functions/ArrayColumnRule.php(new): aRule<FuncCall>that,for
array_column()calls, takes the iterable value type of the source arrayand — when the elements are objects — checks that the constant-string column
and index arguments name an existing property. Reports
arrayColumn.propertyerrors otherwise. HonorstreatPhpDocTypesAsCertainand emits the corresponding tip.
conf/bleedingEdge.neon,conf/config.neon,conf/parametersSchema.neon:add the
arrayColumnObjectArraysfeature toggle (on under bleeding edge,off by default).
conf/config.level5.neon: registerArrayColumnRule(level 5, conditionalon the feature toggle), alongside the existing array-function rules.
tests/PHPStan/Rules/Functions/ArrayColumnRuleTest.phpand.../data/array-column.php(new): the rule test and fixture.Root cause
This is not a regression but a missing check. The return-type extension
(
ArrayColumnHelper) already resolves object properties forarray_column(),so a bad property name just collapses the result to
array{}/listwith nodiagnostic. The new rule fills that gap, mirroring how
AccessPropertiesRulereports
Access to an undefined propertyfor direct property fetches.Key behavioral notes baked into the rule (each verified against real PHP
array_column()semantics):array_column()reads object properties, neverArrayAccessoffsets, evenwhen the element implements
ArrayAccess. The check therefore keys offType::isObject()rather than offset accessibility (a non-final class reportsisOffsetAccessible()as maybe because a subclass could implementArrayAccess, which would otherwise suppress the report the reporter wanted).PHP < 8.2, so classes that allow dynamic properties or expose
__isset+__getare skipped. Enums are skipped because
name/valuealways resolve.Parallel cases considered
#2 $column_key↔#3 $index_key: both arguments are checked with thesame logic (fixed together).
left to other rules), scalar (skipped), enum (skipped),
ArrayAccessobject(reported via property, matching runtime), union of object+array (skipped, not
definitely an object).
reported — consistent with
AccessPropertiesRule.__isset+__getclasses and dynamic-property classesare skipped to avoid false positives.
array_column()reads private properties only from inside the declaring classscope, so a correct check would need scope-aware visibility; this is left as a
follow-up to keep the rule focused on undefined properties (the reported issue).
Test
ArrayColumnRuleTestruns the rule over a fixture that includes the originalissue reproducer (
[new a(), new a()]with a wrong property name) plusnon-final/final classes, the
$index_keyargument, and themagic/dynamic/enum/array/union element types that must not be flagged. The
whole suite (
make tests) and self-analysis (make phpstan, which runs withbleeding edge) pass.
Fixes phpstan/phpstan#5101