Skip to content

Introduce a Config module to own the config-file write path (#1062)#1067

Open
donnchawp wants to merge 15 commits into
trunkfrom
config-module-write-path-1062
Open

Introduce a Config module to own the config-file write path (#1062)#1067
donnchawp wants to merge 15 commits into
trunkfrom
config-module-write-path-1062

Conversation

@donnchawp

@donnchawp donnchawp commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

This PR was generated by AI.

Implements #1062 — a Config module that owns the on-disk wp-cache-config.php write path. Conservative, behaviour-preserving refactor: the write path is centralised and made testable while runtime behaviour, the globals read API, and the public function surface stay exactly as they were.

Closes #1062.

What changed

  • New owner. Automattic\WPSC\Config (src/config/class-config.php) is the single owner of the config-file format: set() (updates $GLOBALS[$field], formats, writes), format_value() (per-type encoding), and write_line() (the atomic tempnamrenamechmodopcache_invalidate rewriter). write_line() is a verbatim transplant of the old wp_cache_replace_line() body.
  • Public functions become delegating shims. wp_cache_setting() and wp_cache_replace_line() keep identical signatures and remain in wp-cache-phase2.php (third-party cache plugins call them); they lazy-load the class via a class_exists + file_exists guard so the engine-only/debug-log path keeps working.
  • Caller migration. 39 direct wp_cache_replace_line() callers across inc/ that are simple $field = value; writes were migrated to Config::set(). 50 internal wp_cache_setting() callers were converted to Config::set() for consistency (phase2's engine-path debug-log calls deliberately stay on the guarded shim).
  • Documented exceptions. Writes whose on-disk format isn't reproducible by Config::format_value() (define rewrites, line removals, var_export cache_path, imploded wp_cache_mobile_groups, numeric-but-quoted cache_time_interval/cache_max_time, cache_page_secret, $wp_cache_pages[…] sub-keys, the ___-encoded cache_rejected_user_agent, the unanchored sem_id regex) stay on the raw function — catalogued in ADR 0002.
  • Scope held tight. No read site migrated; no Config::get(); no new locking; no runtime autoloader (explicit require_once; classmap is dev/test-only).

Out of scope (recorded in ADR 0002)

Read migration (global $foo / $GLOBALS), removing/deprecating the globals (a permanent read API), moving config off the flat pre-WordPress PHP file, and file locking.

Tests

  • New: tests/php/integration/ConfigWritePathTest.php (characterizes the write path byte-for-byte), ConfigClassTest.php (the class API), plus 3 schedule-cluster cases added to SettingsFormUpdatersTest.php that pin the quoted-vs-unquoted cache_time_interval/cache_max_time distinction.
  • Docs: ADR docs/adr/0002-config-module-write-path.md + a CONTEXT.md entry.

Test plan

  • composer test-php — smoke suite (no DB): 34/34
  • make test-integration — WP + DB integration: 62/62 (incl. WpCacheSettingBackCompatTest locking the public shim contract)
  • composer test-e2e — Docker/Jest e2e: 6 suites / 28 tests
  • composer lint — changed-lines PHPCS: exit 0

donnchawp added 13 commits June 22, 2026 14:13
Converts the one migratable wp_cache_replace_line() call in
inc/lifecycle.php (line 84) to \Automattic\WPSC\Config::set().

Seven raw-function sites left unchanged:

- Lines ~416, ~418: rewrite define('WPCACHEHOME', ...) — define rewrite,
  not a key/value set; Config::set() does not emit define statements.
- Line ~424: regex is bare unanchored 'sem_id', not '^ *\$sem_id';
  Config::set() emits an anchored regex, which would change match
  semantics.
- Lines ~467, ~608: rewrite define('WPCACHEHOME'/'WP_CACHE', ...) in
  $global_config_file (site wp-config.php) — define rewrite.
- Lines ~637, ~647: replacement string is '' (line removal, not a set).
Converts 14 direct wp_cache_replace_line() callers in inc/settings-forms.php
to \Automattic\WPSC\Config::set() per the #1062 migration plan.

Adds three characterization test methods to SettingsFormUpdatersTest covering
wp_cache_time_update() (interval path, initialization path, clock path). Tests
ran GREEN against the pre-migration code and remain GREEN after migration,
confirming byte-identical config output.

Six raw wp_cache_replace_line() calls left unchanged:

- L26:  define('WPLOCKDOWN', ...) — not a key/value write; different format.
- L77:  $cached_direct_pages — hand-built array( ) literal uses legacy syntax
        (no integer keys); format_value's var_export form would differ. Byte difference.
- L150: $cache_time_interval (init) — numeric value but written QUOTED by the
        original; Config::set() would emit it unquoted. Byte difference.
- L176: $cache_time_interval (interval path) — same numeric-quoted trap as L150.
- L219: $cache_rejected_user_agent — $text has ___ → space restored AFTER
        wp_cache_sanitize_value(); the array still holds ___ at write time so
        format_value(array) != $text. Leave on raw call.
- L249: $wp_cache_pages["$page"] — array sub-key write; Config::set() cannot
        express it.
Converts 24 of 28 wp_cache_replace_line() calls in inc/admin-ui.php
(the wp_cache_manager_updates() cluster and related warning-dismiss
handlers) to \Automattic\WPSC\Config::set().

Four sites left on raw wp_cache_replace_line() — each is a byte-
divergence trap:

- L373 cache_page_secret: value is md5() (hex string) written QUOTED.
  Config::set would unquote it if the md5 were all-digits (is_numeric).
- L397 cache_time_interval: numeric value 600 written QUOTED here;
  Config::set emits unquoted.
- L398 cache_max_time: numeric value 1800 written QUOTED here;
  Config::set emits unquoted.
- L434 cache_path: uses var_export() for backslash/quote escaping;
  Config::set string branch does raw interpolation — diverges on paths
  containing backslashes.
@donnchawp donnchawp self-assigned this Jun 22, 2026
…t/* row (#1062)

- wp_cache_setting() / wp_cache_replace_line() shims now early-return false
  when src/config/class-config.php is absent, instead of fataling on the
  Config:: call. Restores the old inline functions' graceful degradation on
  the engine-only/partial-install path the lazy-load guard protects.
- ADR 0002: split the stale 'plugins/* and rest/* out of scope' row. The REST
  handler was migrated to Config::set(); only set_ossdlcdn stays on the raw
  call. Document that separately.
@donnchawp

Copy link
Copy Markdown
Contributor Author

This comment was generated by AI.

Reviewer testing checklist

This is a behaviour-preserving refactor, so the pass condition for most checks is: save a settings group, then diff the generated wp-cache-config.php against a pre-PR copy — byte-identical (modulo the value you changed). Do this on a real install with caching + WP_CACHE enabled.

Highest value — the one path CI can't cover

  • Engine-only / drop-in lazy-load. The Config class lazy-loads inside the wp_cache_setting() / wp_cache_replace_line() shims; every automated test loads the full plugin, so this branch is never exercised by CI. Trigger the debug-log write path (Settings → Debug: set/change the debug log file or debug username) and confirm the config file updates with no fatal. This runs the shim with the class not pre-loaded.
  • Unusual install layout. If you can, test where WPCACHEHOME resolves oddly (symlinked plugin dir, or the realpath( WPCACHEHOME ) != realpath( __DIR__ ) branch in wp-cache.php) — confirm the shim finds src/config/class-config.php. (If src/ is ever missing and the class isn't loaded, the shim now fatals where the old code returned false — only on a broken/partial install.)

Settings clusters with quoting subtleties (save → diff config file)

  • GC / preload schedule (cache_schedule_type, cache_time_interval, cache_max_time): confirm cache_time_interval stays quoted and cache_max_time stays unquoted-in-settings-forms / quoted-in-admin-ui. (Pinned by SettingsFormUpdatersTest, but the most likely place a real divergence would show.)
  • Rejected/accepted URIs, rejected user-agents, tracking parameters, rejected cookies — array writes; the user-agent one has the ___/space round-trip.
  • Advanced toggles (304, mfunc, mobile, compression, not-logged-in, late-init, …) — lowest risk; covered by e2e.

Alternative entry points (route through the same shims)

  • REST settings save (the React admin UI, if used) — exercises the shim from a different path than the classic forms.
  • Bundled plugins that call wp_cache_replace_line() directly (not migrated, now route through the shim): WPtouch, Jetpack mobile, domain mapping, bad-behaviour, awaiting-moderation. Confirm their settings still persist.

Two more

  • opcache on: confirm config changes take effect without a manual flush (the writer's opcache_invalidate() is preserved verbatim).
  • $GLOBALS sync (intentional): migrated raw-rewriter sites now also write $GLOBALS[$field]. Verified inert; if a settings page reflects a fresher value than before, that's the reason — a fix, not a regression.

Automated (already green at branch head)

  • composer test-php — 34/34 · make test-integration — 62/62 (incl. WpCacheSettingBackCompatTest) · composer test-e2e — 6 suites / 28 · composer lint — exit 0

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.

Refactor: introduce a Config module to own the config-file write path

1 participant