Skip to content

Fix the init-time fatal: require Booking_Adapter, and guard the whole manifest - #4

Merged
Shubochandrosarker merged 1 commit into
mainfrom
fix/init-fatal-booking-adapter
Aug 8, 2026
Merged

Fix the init-time fatal: require Booking_Adapter, and guard the whole manifest#4
Shubochandrosarker merged 1 commit into
mainfrom
fix/init-fatal-booking-adapter

Conversation

@Shubochandrosarker

Copy link
Copy Markdown
Contributor

What this changes

The published public_release_v_2.0.0 tag fatals on init for every install. Before: activating the plugin on a stock WordPress with no configuration throws Error: Class "WordPressistic\Memberistic\Integrations\Booking_Adapter" not found. After: init completes.

includes/integrations/class-booking-adapter.php shipped in 2.0.0 but was never added to the manual require list in Plugin::load_dependencies(), and the plugin has no autoloader. It was the only file under includes/ absent from that list — 71 listed, 73 on disk, the other being class-plugin.php, which the bootstrap requires directly.

Waiver_Booking_Bridge::register() calls Booking_Adapter::hook() as its first statement, and is registered on init priority 4 whenever the Waiver Manager integration is enabled — whose default is 'yes'. So the chain fires unconditionally:

init(4) → Waiver_Booking_Bridge::register()
        → Booking_Adapter::hook('waiver_satisfied')
        → Error: Class "…\Integrations\Booking_Adapter" not found

Booking_Engine, POS_Bridge and Staff_Dashboard reach the same class.

Delivers backlog item P0-11.

Type

  • Bug fix

Rules touched

  • None of the above

No invariant is affected. The fix restores intended behaviour; it grants nothing, enables nothing, and makes no request.

Multi-edit checklist

  • New class files added to the require_once list in Plugin::load_dependencies() — nothing autoloads

That is this PR's entire subject. This repository's own PR template lists it first, which is a fair indication of how easy it is to miss.

  • No schema change, so MEMBERISTIC_DB_VERSION is untouched
  • No version bump — 2.0.1 is its own release PR (P0-1, P0-2)

Documentation

  • CHANGELOG.md — new Unreleased section with the Fixed and Added entries

The one-line fix is not the interesting part

The require is one line. The rest of this PR is about why CI was green for the entire life of the 2.0.0 release:

  • php -l proves every file parses — and every file does parse, perfectly, on its own. A missing require_once is not a syntax error.
  • The unit suite never boots the plugin, so nothing ever walked the require list.

tests/unit/DependencyManifestTest.php closes that gap by asserting the property that was violated, not the single instance:

Test Asserts
test_every_file_under_includes_is_in_the_require_list Manifest and directory agree (class-plugin.php excepted — the bootstrap requires it directly)
test_every_path_in_the_require_list_exists No entry points at a deleted file; require_once on a missing path is also fatal
test_the_require_list_has_no_duplicates Harmless to run, but a signal the list is edited without being read
test_booking_adapter_precedes_its_consumers Named regression test, so a recurrence says which bug came back

A file added under includes/ and forgotten now fails on the pull request that adds it.

Source inspection rather than execution, deliberately — the same approach FreshInstallDefaultsTest and PmproRemovalTest already take. Requiring the real files needs a live WordPress and this suite has none; the question here is what the manifest says, which is exactly what the fatal depended on.

What I ran

The guard fails on the unfixed tree — a guard that never fails is not a guard. Reverting only the require, keeping the test:

  adapter entries in require list: 0

1) DependencyManifestTest::test_every_file_under_includes_is_in_the_require_list
These files exist under includes/ but are not in Plugin::load_dependencies().

2) DependencyManifestTest::test_booking_adapter_precedes_its_consumers
class-booking-adapter.php is missing from Plugin::load_dependencies(). This is the
2.0.0 regression: Waiver_Booking_Bridge::register() calls Booking_Adapter::hook()
on init priority 4, and the Waiver Manager integration it is gated on defaults to
enabled, so every stock install fatals on init.

Tests: 4, Assertions: 12, Failures: 2.

The original reproduction, before → after. Load only the files load_dependencies() lists, then invoke the init path:

before                                                    after
Files listed in Plugin::load_dependencies(): 71           72
Actual .php files under includes/:            73          73

class_exists('Booking_Adapter'): bool(false)              bool(true)
Is the file present on disk?   : bool(true)               bool(true)
Is it in the require list?     : bool(false)              bool(true)

--- init(priority 4) -> Waiver_Booking_Bridge::register() ---
Error: Class "…\Booking_Adapter" not found                no error
>>> FATAL REPRODUCED <<<

Full suite:

$ vendor/bin/phpunit -c phpunit.xml
  ...................................................               51 / 51 (100%)
  OK (51 tests, 847 assertions)          # was 47 tests, 831 assertions

$ find . -name '*.php' -not -path './vendor/*' -print0 | xargs -0 -n1 php -l
  clean   (PHP 8.4.19)

$ find assets -name '*.js' -print0 | xargs -0 -n1 node --check
  clean

What I did NOT test

  • The fatal on a real WordPress install. No integration harness exists here yet (P0-0). The reproduction is a faithful replay of the load sequence — it requires exactly the files load_dependencies() names and calls what init priority 4 calls — but it is not WordPress. The class-resolution failure it demonstrates does not depend on WordPress being present, and the fix is verified the same way.
  • The Waiver Manager booking mirror actually functioning. This PR proves init no longer fatals. Whether Waiver_Booking_Bridge then behaves correctly with a booking engine mapped is untested here and needs P0-0.
  • PHP 8.2 and 8.3. Only 8.4 locally. CI covers all three.
  • The other runtime fixes in the working copy (Stripe webhook deprecation, Licensing::build_info() slug, uninstall.php globals, readme.txt limits). Deliberately out of scope — each gets its own PR.

Note on the backlog

P0-11 is defined in docs/strategy/09-execution-backlog.md, which arrives in #3. This branch is based on main, not on #3, so the fix can merge on its own without waiting for a documentation PR — the fatal is live in the published release, and coupling it to a docs merge would be the wrong trade. The P0-11 acceptance boxes should be ticked when both have landed.

Risk and rollback

  • Blast radius: one additional require_once of a file that already ships, placed before its consumers. Booking_Adapter is a static resolver whose hook()/table() return values pass through apply_filters and default to empty when nothing is mapped, so loading it changes no behaviour on a site with no booking engine — it stops the class-resolution failure and nothing else. Sites currently fatalling on init will start working.
  • Rollback: revert the commit. That restores the fatal, so a rollback is only sensible together with a rollback of whatever it was thought to have broken.
  • Data migration to reverse? No. No schema change, no option written, no migration registered.

Generated by Claude Code

… manifest

includes/integrations/class-booking-adapter.php shipped in 2.0.0 but was never
added to the manual require list in Plugin::load_dependencies(), and the plugin
has no autoloader. It was the only file under includes/ absent from that list
(71 listed, 73 on disk; the other is class-plugin.php, which the bootstrap
requires directly).

Waiver_Booking_Bridge::register() calls Booking_Adapter::hook() as its first
statement, and is registered on init priority 4 whenever the Waiver Manager
integration is enabled — whose default is 'yes'. So the chain fires on a stock
install with no configuration:

    init(4) -> Waiver_Booking_Bridge::register()
            -> Booking_Adapter::hook('waiver_satisfied')
            -> Error: Class "...\Integrations\Booking_Adapter" not found

Booking_Engine, POS_Bridge and Staff_Dashboard reach the same class. The
published public_release_v_2.0.0 tag carries this.

The one-line fix is the require. The rest of this commit is about why CI never
saw it: php -l proves each file parses, and it does parse perfectly on its own,
and the unit suite never boots the plugin, so nothing ever walked the list.
DependencyManifestTest closes that gap by asserting the property that was
violated rather than the single instance — every file under includes/ appears
in the manifest, every listed path exists, no duplicates, and Booking_Adapter
precedes each of its four consumers. A file added under includes/ and forgotten
now fails on the pull request that adds it.

Verified:
- Guard fails on the unfixed tree with the right message (2 failures), passes
  with the fix.
- The original reproduction — load only what load_dependencies() lists, then
  invoke the init path — goes from "Class not found" to completing with no
  error. class_exists() is now true; the require list holds 72 of 73.
- Full unit suite 51 tests / 847 assertions / 0 failures, up from 47 / 831.
- php -l clean on 8.4, node --check clean.

No version bump: 2.0.1 is its own release PR (P0-1, P0-2). No schema change, so
MEMBERISTIC_DB_VERSION is untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LgGmwdEzTdVas4VTDzJQ5m
@Shubochandrosarker
Shubochandrosarker marked this pull request as ready for review August 8, 2026 22:36
@Shubochandrosarker
Shubochandrosarker merged commit ea71991 into main Aug 8, 2026
16 checks passed
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.

2 participants