Name the class at the call site instead of fetching it by string - #38
Merged
Conversation
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
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.
The plugin half of FOGProject/fogproject#1710 — 163 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 voidon a method endingreturn $queries;,@return objecton methods thatreturn 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.phpin 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 shapenewcannot express, and it is what the function is for now.The harness change is the interesting part
tests/stubs/fog-stubs.phpansweredHook::getClass()from a registry keyed by short name, so a test could writeand then assert on
$manager->batches, because the plugin was handed that very object. A plainnewresolves for real and cannot hand back somebody else's instance.So
StubProxyforwards every call into the registered fixture, and an autoloader materialises any unloadedFOG\class as a subclass of it. The registry keeps working unchanged and no test's assertions moved —group-push-became-mass-edit.test.phppasses with its fixture wiring untouched.Nearly went wrong
The two
NtfyHandlersites are four-argument calls.getClass()'s third named parameter is$props, so: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.phpis new: refuses a literalgetClass(), still allows the props form and'ReflectionClass'(neither has anewequivalent). Verified by reintroducing a literal and watching it go red.main. PSR-2 clean..gitignorefor.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