Skip to content

11: Loader boot and load path - #11

Open
nikolaystrikhar wants to merge 4 commits into
10-notices-queuefrom
11-loader-load-path
Open

11: Loader boot and load path#11
nikolaystrikhar wants to merge 4 commits into
10-notices-queuefrom
11-loader-load-path

Conversation

@nikolaystrikhar

@nikolaystrikhar nikolaystrikhar commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Adds Loader::boot(), load_all(), the load path's five gates, and the should_load filter — the code deciding whether to require_once a bundled plugin.

Stacked on #10.

  • file_exists() was the gate before require_once. It returns true for a directory and for an unreadable file, both of which fatal. Now is_file() plus is_readable().
  • boot() called too late loaded nothing, silently. A hook added at a priority the running dispatch has passed is accepted and never fires. The guard is now inclusive of the load priority and loads inline.
  • A missing hook prefix fatalled the site. An uncaught Config_Exception escaped a core action; load_all() and render_notices() now report and return.
  • Gate order was wrong. is_already_loaded() must run before are_dependencies_met(), which calls an arbitrary host callable and could blame dependencies for a visibly running plugin.
  • Documented, not fixed: the bundled file is included from inside a method, so it gets no global scope — a top-level $var is function-local. No wrapper can fix that; the README says so.

Verify: slic run unit — 155 tests, 241 assertions. --env multisite green, 1 deliberate skip. composer test:analysis[OK] No errors.

Not covered: a parse error in a sub-plugin still fatals, deliberately.

…path

file_exists() was the gate before require_once, but it is true for a
directory and for a file with no read permission, and require_once fatals
on both. A deploy that loses a read bit would have taken the site down from
the one function whose contract is that it never fatals. is_file() and
is_readable() now.

is_already_loaded() moves ahead of are_dependencies_met(). The dependency
check calls an arbitrary host callable and was running first, so with the
standalone active a host whose check happened to fail would queue
"requirements are not met" for a plugin the admin can watch running. The
defined() check is the cheapest gate and the one carrying the whole
re-declaration guarantee.

A missing bundled file reports through _doing_it_wrong() rather than the
notice queue. Queueing a dependency notice renders the host's own
dependency_notice_message, so a broken build would have told the site owner
to upgrade a dependency that was fine. WordPress draws the same line.

boot() detects that plugins_loaded has already passed priority 2 and loads
inline. Adding an action at a passed priority is accepted and never fires,
so a host who drops the ', 0' from the documented bootstrap got a site
where nothing loaded at all, with no warning.

load_all() and render_notices() report a missing hook prefix instead of
throwing out of a core action, where it would have been a total outage over
a bootstrap mistake.

Tests: the fake-registrar test passed with the instanceof guard deleted, so
it now binds a registrar returning mixed junk; adds the require_once dedupe
case, the unreadable and directory cases, and the late-boot cases. The boot
tests rewind did_action( 'plugins_loaded' ), which the harness has already
fired before any test can run.
Booting from plugins_loaded at priority 2 wired the load hook into the
priority bucket the dispatch was already iterating, which WP_Hook never
reaches, and the exclusive comparison meant nothing was reported either.
Nothing loaded and the site looked healthy.

Also document that a bundled file included from a method does not get the
global scope wp-settings.php gives a plugin.
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