Detector: UselessPropertyHookDetector
Report (why the flagged code is CORRECT and the detector is wrong):
The body reads static::DRIVER, which is late static binding: SecretWizard, AgentWizard, SchemaWizard and IntegrationWizard each redeclare DRIVER, so the hook answers a different class per subclass. The detector looks for $this and parent:: and treats static:: as a constant, so it reads a polymorphic body as a fixed one. Laziness is the second reason it cannot be a stored property: a Wizard is built from its own defaults while the template is compiled, where the container has no request-scoped driver bound and Queries::refuse() is in force, so resolving in a constructor runs the resolution at compile time. And a Wizard's constructor must stay argument-free for the same reason, which rules out passing the driver in.
Cleanest design the reporter can conceive:
A wizard names its driver class as a constant and reads the instance lazily through a get hook, exactly as written. The constant is what makes each subclass a wizard of its own; the hook is what keeps the resolution out of compile time.
⚖️ Maintainer litmus: a valid detector-report needs the flagged code to ALREADY BE the
cleanest design. If the design above differs from the flagged code at all, THAT design is
the owed fix — close this report; the fix is still owed.
Where: src/Ui/Scenes/Wizards/Wizard.php:53
Code (src/Ui/Scenes/Wizards/Wizard.php:53):
50 * @var TDriver
51 */
52 protected WizardDriver $driver {
→ 53 get => App::make(static::DRIVER);
54 }
55
56 protected function setup(): void
57 {
58 $this->driver->observe(fn () => $this->signal(new ScreenChanged));
59 }
60
61 public function cacheState(): string
62 {
63 return $this->driver->currentScreen();
64 }
65
66 # ----------[ Scripts ]----------
67
68 #[ClientSide]
69 public function shut(Element $element, Document $page): void
70 {
71 $page->onKeys(Keys::of(Key::Escape), fn (Element $node) => $node->state->open = false);
72 }
73
74 protected function compose(): Renderable | array | null
75 {
76 return Modal::of(
77 Frame::dialog(Region::of(WizardBody::of(App::make($this->driver->currentScreen()))))
Where: src/Ui/Scenes/Wizards/Secret/SecretWizard.php:13
Code (src/Ui/Scenes/Wizards/Secret/SecretWizard.php:13):
10 * @extends Wizard<SecretDriver>
11 */
12 #[Scoped]
→ 13 final class SecretWizard extends Wizard
14 {
15 protected const string DRIVER = SecretDriver::class;
16 }
Filed via commandments report from a consumer project.
Detector:
UselessPropertyHookDetectorReport (why the flagged code is CORRECT and the detector is wrong):
The body reads static::DRIVER, which is late static binding: SecretWizard, AgentWizard, SchemaWizard and IntegrationWizard each redeclare DRIVER, so the hook answers a different class per subclass. The detector looks for $this and parent:: and treats static:: as a constant, so it reads a polymorphic body as a fixed one. Laziness is the second reason it cannot be a stored property: a Wizard is built from its own defaults while the template is compiled, where the container has no request-scoped driver bound and Queries::refuse() is in force, so resolving in a constructor runs the resolution at compile time. And a Wizard's constructor must stay argument-free for the same reason, which rules out passing the driver in.
Cleanest design the reporter can conceive:
A wizard names its driver class as a constant and reads the instance lazily through a get hook, exactly as written. The constant is what makes each subclass a wizard of its own; the hook is what keeps the resolution out of compile time.
Where:
src/Ui/Scenes/Wizards/Wizard.php:53Code (
src/Ui/Scenes/Wizards/Wizard.php:53):Where:
src/Ui/Scenes/Wizards/Secret/SecretWizard.php:13Code (
src/Ui/Scenes/Wizards/Secret/SecretWizard.php:13):Filed via
commandments reportfrom a consumer project.