Skip to content

Qualify every reference to a FOG core class - #28

Merged
mastacontrola merged 1 commit into
mainfrom
qualify-core-references
Aug 27, 2026
Merged

Qualify every reference to a FOG core class#28
mastacontrola merged 1 commit into
mainfrom
qualify-core-references

Conversation

@mastacontrola

Copy link
Copy Markdown
Member

Step 3 of fogproject's alias retirement (docs/composer-psr4-plan.md:815). This must ship before the core change that deletes the aliases — ADR 0009's ordering: downloadplugins() runs before configureHttpd(), so a server is never serving a core without aliases against plugins that still need them.

Why

Plugins are global-namespace by design (ADR 0009) and have always named core classes bare — extends Hook, Route::listem(), new Image(). That works only because every file under fogproject's packages/web/src/ ends in a class_alias() re-exporting itself globally. Once those go, a bare name resolves to nothing and the failure is a fatal on the page or hook that uses it.

574 references across 163 files now carry their namespace. The plugins stay global — only the names they reach into core with have changed.

The map has three sources, and the first cut used one

Source Shape Example
src/ PSR-4, path is the name \FOG\Base\Hook
lib/ the 46 discovery-named classes, flat FOG namespace \FOG\ReportManagement — 8 plugin reports extend it
commons/ Initiator, genuinely global and staying that way \Initiator — just a leading backslash

Two rounds were wrong before this was right — and the gates caught both

The tool skipped ~90 references silently. It built its "classes this tree declares" set from the whole tree including tests/stubs, which declares doubles named FOGController, FOGManagerController and Schema. Every reference to those three therefore looked like a plugin's own class. 313 rewritten, ~90 left behind. The strict stubs failed the suite immediately with Class "FOGController" not found.

Initiator and ReportManagement were invisible to a src-only map. Neither lives under src/. The self-contained test found all 18.

Neither was caught by reading the diff.

What makes the tests a real gate

tests/stubs/fog-stubs.php now declares its doubles in the namespaces core declares them in — FOG\Base\FOGController, FOG\Base\FOGManagerController, FOG\Items\Schema — and deliberately not also under their bare global names. A plugin file still saying extends FOGController resolves nothing and fails to load, exactly as it would on a real server. Adding a global alias back would make the stubs kinder than production and the suite would go green on code that cannot run.

The stub's own Schema::createTable() needed qualifying too — it sits in FOG\Base and Schema is in FOG\Items, so the bare name resolved to FOG\Base\Schema. Same bug class that white-screened fogproject's UI last week.

The standing gate

tests/core-references-are-qualified.test.php carries no copy of core's class list. The invariant is self-contained: a name this tree does not declare, and that PHP does not have, must be qualified. That needs no fogproject checkout — which matters, because CI has none.

  • Mutation-confirmed: reverting one extends \FOG\Base\Hook fails naming that file and line.
  • Handles both tokenizer shapes — PHP 8 folds a qualified name into one T_NAME_FULLY_QUALIFIED token, 7.4 emits T_NS_SEPARATOR + T_STRING per segment, and CI runs both.
  • Reports 574 qualified reference(s), 70 bare one(s) checked — the 70 being the plugins' own classes and PHP built-ins.

Verification

Suite: 10 passed, 0 failed (9 existing + the new gate).

Full end-to-end proof — every plugin class loading against a core with the aliases actually deleted — lands with the fogproject PR, because core's own 46 lib/ files have the same bare-name problem and are fixed there. Confirmed in a shadow tree: with plugin-referenced aliases stripped, the first failure is core's lib/hooks/bootitem.hook.php, not a plugin.

Plugins are global-namespace by design (fogproject ADR 0009) and have always
named core classes bare -- `extends Hook`, `Route::listem()`, `new Image()`.
That works only because every file under fogproject's packages/web/src/ ends
in a class_alias() re-exporting itself into the global namespace, and that
alias set is being retired (fogproject ADR 0013 §2, docs/composer-psr4-plan.md).
A bare name resolves to nothing once it goes.

574 references across 163 files now carry their namespace. The plugins stay
global -- only the names they reach into core with have changed.

ADR 0009 requires this to ship BEFORE the core change that deletes the
aliases: downloadplugins() runs before configureHttpd(), so a server is
never serving a core without aliases against plugins that still need them.

bin/qualify-core-references.php does the rewrite. It reads the map from a
fogproject checkout rather than carrying a copy, because which bucket a class
lives in is fogproject's to decide and a stale list here would rewrite names
to somewhere they are not. Tokenised, never regex, so a name in a docblock or
a string is left alone and `$obj->Route` is not mistaken for a class.

Three sources feed that map, and the first cut used only the first:

  src/       PSR-4, so the path is the name
  lib/       the 46 discovery-named classes, flat FOG namespace -- eight
             plugin reports extend FOG\ReportManagement
  commons/   Initiator, genuinely global and staying that way; qualifying it
             is just a leading backslash

Two rounds of the sweep were wrong before this was right, and the gates
caught both rather than review doing it:

  - The tool built its "classes this tree declares" set from the whole tree,
    including tests/stubs, which declares doubles NAMED FOGController,
    FOGManagerController and Schema. Every reference to those three therefore
    looked like a plugin's own class and was skipped -- 313 rewritten, ~90
    silently left. The strict stubs failed the suite immediately.
  - Initiator and ReportManagement are not under src/ at all, so the src-only
    map could not see them. The self-contained test found all 18.

tests/stubs/fog-stubs.php now declares its doubles in the namespaces core
declares them in, and deliberately NOT also under their bare global names.
That is what makes the plugin tests a gate for this: a file still saying
`extends FOGController` fails to load exactly as it would on a real server.
The stub's own `Schema::createTable()` needed qualifying too -- it sits in
FOG\Base and Schema is in FOG\Items.

tests/core-references-are-qualified.test.php is the standing gate, and it
carries no copy of core's class list: the invariant is that a name this tree
does not declare and PHP does not have must be qualified. That needs no
fogproject checkout, which matters because CI has none. Mutation-confirmed --
reverting one `extends \FOG\Base\Hook` names that file and line. It handles
both tokenizer shapes, since PHP 8 folds a qualified name into one
T_NAME_FULLY_QUALIFIED token and 7.4 does not, and CI runs both.

Suite: 10 passed, 0 failed.

Co-Authored-By: Claude <noreply@anthropic.com>
@mastacontrola
mastacontrola merged commit 9139759 into main Aug 27, 2026
2 checks passed
@mastacontrola
mastacontrola deleted the qualify-core-references branch August 27, 2026 20:29
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