Skip to content

fix: Elementor idempotent-lifecycle mu-plugin (redeclaration fatal)#4

Merged
luthermonson merged 1 commit into
mainfrom
fix/elementor-idempotent-lifecycle
Jul 11, 2026
Merged

fix: Elementor idempotent-lifecycle mu-plugin (redeclaration fatal)#4
luthermonson merged 1 commit into
mainfrom
fix/elementor-idempotent-lifecycle

Conversation

@luthermonson

@luthermonson luthermonson commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds muplugins/elementor-idempotent-lifecycle.php to fix the Elementor Cannot redeclare class Elementor\Element_Column fatal that hits every request from N >= 2 under this adapter's per-request init re-fire. Reproduced locally against Elementor 4.1.4; fix verified.

Root cause (source-verified)

Elementor's Plugin::init() (registered on init priority 0 at plugin.php:833) calls $this->init_components(), which instantiates new Elements_Manager(). Elements_Manager::__construct() at elements.php:58 calls the private require_files(), which at elements.php:461-463 does:

private function require_files() {
    require_once ELEMENTOR_PATH . 'includes/base/element-base.php';
    require ELEMENTOR_PATH . 'includes/elements/column.php';    // NOT require_once
    require ELEMENTOR_PATH . 'includes/elements/section.php';
    require ELEMENTOR_PATH . 'includes/elements/repeater.php';
}

Under FPM, init fires once per process -> require_files() runs once -> correct. Under this adapter's per-request init re-fire, request N >= 2 -> new Elements_Manager() again -> require (not require_once) -> redeclaration fatal.

Fix

Mu-plugin at wp_loaded PHP_INT_MAX that on the first fire (boot-time) does remove_action('init', [\Elementor\Plugin::instance(), 'init'], 0) — strips exactly the callback that triggers the redeclaring chain. All other Elementor hooks (REST, admin, editor, widget-render) stay intact. Static flag ensures the callback only fires once even though wp_loaded re-fires per request.

Reproduction (local, this branch)

  • WP 7.0 + Elementor 4.1.4, installed via wordpress:cli-2.10.0-php8.2
  • Composer pulls ephpm/wordpress-worker:dev-fix/elementor-idempotent-lifecycle (this branch)
  • ePHPm image: docker.io/ephpm/ephpm:v0.4.2-php8.4
  • Config: mode = "worker", worker_count = 1, worker_populate_superglobals = true

Without the mu-plugin (removed from wp-content/mu-plugins/):

[hit 1] GET / -> 200
[hit 2] GET / -> 200   (succeeded because worker recycled)
[hit 3] GET / -> 200   (recycled again)
worker log:
[PHP] PHP Fatal error: Cannot redeclare class Elementor\Element_Column
  (previously declared in /app/wp-content/plugins/elementor/includes/elements/column.php:19)
  in /app/wp-content/plugins/elementor/includes/elements/column.php on line 19
worker booted worker_id=1
worker booted worker_id=2
worker booted worker_id=3

Each request kills a worker; ePHPm recycles and retries, so the client still gets 200 but the pool is churning.

With the mu-plugin (muplugins/elementor-idempotent-lifecycle.php in wp-content/mu-plugins/):

[hit 1..5] GET / -> 200
worker log: worker booted worker_id=0  (once, no recycles)
              — no `Cannot redeclare` lines —

Same 5 requests, single worker, zero crashes.

Docs

  • Adds a "Running Elementor" section to README mirroring the existing "Running WooCommerce" one, with drop-in copy instructions and the fix rationale.

Not addressed by this PR (scope)

  • Auditing remaining Elementor codepaths for per-request idempotency (widget cache clearing, breakpoint state, editor preview flow). Ship with a conservative worker_max_requests and instrument for regressions.
  • Adding an Elementor-bearing e2e fixture to this adapter's e2e/ suite. Not blocking this PR — validated against a real Elementor install as described above.

Related

Elementor's `Plugin::init()` handler (registered on `init` priority 0)
`include`s its element class files via `Elements_Manager::init_elements()`.
Under FPM, `init` fires once per process — correct. Under this adapter's
per-request `init` re-fire, the second request re-runs the include and
crashes the worker:

  PHP Fatal error: Cannot redeclare class Elementor\Element_Column
  .../wp-content/plugins/elementor/includes/elements/column.php:19

Elementor's Plugin singleton and element registry are already fully
populated at boot time; the per-request replay of Elementor's `init`
handler adds no functional value and only re-triggers the include.

Add `muplugins/elementor-idempotent-lifecycle.php` mirroring the shape
of the existing WooCommerce mu-plugin: on the first `wp_loaded` fire
(boot-time, at PHP_INT_MAX so it runs after all other boot-time init),
snapshot Elementor's `[Plugin::instance(), 'init']` binding and
`remove_action('init', ..., 0)` it. Subsequent request-loop `init`
re-fires skip Elementor's initializer. All other Elementor hooks
(REST, admin, editor, widget-render) stay intact.

Discovered by the tinfoyle/ePHPm-lab wordpress-v5 retest after this
adapter bumped to v0.1.1 (the WooCommerce cart lifecycle fix
introduced the per-request `init`/`wp_loaded` replay that surfaces
this Elementor compatibility gap). See:
https://github.com/tinfoyle/ePHPm-lab/blob/main/docs/wordpress-v5.md
and
https://github.com/tinfoyle/ePHPm-lab/blob/main/docs/wordpress-worker-investigation.md
§ "v5 Retest Against v0.1.1".

Ship together with a conservative `worker_max_requests` and instrument
for regressions until validated end-to-end in the lab.
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