Skip to content

Name the class at the call site instead of fetching it by string - #38

Merged
mastacontrola merged 1 commit into
mainfrom
refactor/getclass-to-new
Sep 4, 2026
Merged

Name the class at the call site instead of fetching it by string#38
mastacontrola merged 1 commit into
mainfrom
refactor/getclass-to-new

Conversation

@mastacontrola

Copy link
Copy Markdown
Member

The plugin half of FOGProject/fogproject#1710163 sites across 49 files. Same reasoning, recorded as fogproject ADR 0043.

Why

getClass() is declared @return object|mixed, so nothing can check what you then do with the result and no editor can follow it to a definition. Converting core's 459 literal sites surfaced 90 PHPStan errors against a baseline that reports zero — every one a pre-existing annotation that had drifted from its body (@return void on a method ending return $queries;, @return object on methods that return false;).

It was never a substitution seam either: FOGBase::qualify() consults core's map before the plugins', which ADR 0013 says is what stops a plugin answering a core name.

Fully qualified rather than imported, because tests/core-references-are-qualified.test.php in this repository refuses a bare core name outright — this tree is fetched on its own and cannot assume a fogproject checkout is nearby.

getClass() with a variable is untouched (2 sites). That is the one shape new cannot express, and it is what the function is for now.

The harness change is the interesting part

tests/stubs/fog-stubs.php answered Hook::getClass() from a registry keyed by short name, so a test could write

Hook::$classes['LocationAssociationManager'] = $manager;

and then assert on $manager->batches, because the plugin was handed that very object. A plain new resolves for real and cannot hand back somebody else's instance.

So StubProxy forwards every call into the registered fixture, and an autoloader materialises any unloaded FOG\ class as a subclass of it. The registry keeps working unchanged and no test's assertions movedgroup-push-became-mass-edit.test.php passes with its fixture wiring untouched.

Nearly went wrong

The two NtfyHandler sites are four-argument calls. getClass()'s third named parameter is $props, so:

  • an arity rule that skips "three or more arguments" reads them as the properties form and leaves them behind;
  • an argument-copying rule that takes only the second argument silently drops two.

The first draft of the tool did both. The new gate caught it before the sweep landed, which is the argument for the gate existing.

Tests

  • tests/getclass-literals.test.php is new: refuses a literal getClass(), still allows the props form and 'ReflectionClass' (neither has a new equivalent). Verified by reintroducing a literal and watching it go red.
  • Three tests read the source as text and pinned the old spelling — updated to the new one, not loosened to accept both.
  • Full suite 18/18, against a baseline of 17/17 on main. PSR-2 clean.
  • Added a .gitignore for .php-cs-fixer.cache; the repo had none and the cache is generated.

Merge alongside fogproject#1710 — the docs there (docs/plugin-development.md) are updated in the same pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01684dJC9hw1jY4KjzV81BLC

fogproject retired the literal `getClass('X')` in favour of a plain `new`
(its ADR 0043), and this is the same sweep here: 163 sites across 49
files. The reason is that getClass() is declared `@return object|mixed`,
so nothing can check what you then do with the result and no editor can
follow it to a definition -- converting core's 459 sites surfaced 90
PHPStan errors on a baseline that reported zero, every one a pre-existing
annotation that had drifted from its body.

It was never a substitution seam either: FOGBase::qualify() consults
core's map before the plugins', which ADR 0013 says is what stops a
plugin answering a core name.

Fully qualified rather than imported, because
tests/core-references-are-qualified.test.php refuses a bare core name
outright -- this tree is fetched on its own and cannot assume a
fogproject checkout is anywhere nearby.

getClass() with a VARIABLE is untouched. That is the one shape `new`
cannot express, and it is what the function is for now; two sites use it.

tests/getclass-literals.test.php is the new gate. It refuses a literal
getClass() while still allowing the props form and 'ReflectionClass',
neither of which has a `new` equivalent, and it was verified by
reintroducing a literal and watching it go red.

THE HARNESS NEEDED A REAL CHANGE, and it is the interesting part of this
commit. tests/stubs/fog-stubs.php answered Hook::getClass() from a
registry keyed by short name, so a test could write
`Hook::$classes['LocationAssociationManager'] = $manager` and then assert
on $manager->batches, because the plugin was handed that very object. A
plain `new` resolves for real and cannot hand back somebody else's
instance. So StubProxy forwards every call into the registered fixture,
and an autoloader materialises any unloaded FOG class as a subclass of
it. The registry keeps working unchanged and no test's assertions moved.

Three tests read the source as text and pinned the old spelling; they
were updated to the new one rather than loosened to accept both.

The two NtfyHandler sites are worth noting because they nearly went
wrong: they are FOUR-argument calls, and getClass()'s third named
parameter is $props, so an arity rule that skips "three or more" reads
them as the properties form and an argument-copying rule that takes only
the second argument silently drops two. Both were caught by this gate
before the sweep landed.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01684dJC9hw1jY4KjzV81BLC
@mastacontrola
mastacontrola merged commit f317f6c into main Sep 4, 2026
2 checks passed
@mastacontrola
mastacontrola deleted the refactor/getclass-to-new branch September 4, 2026 19:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant