Skip to content

Add worker-aware Media app (issues #2, #3)#1

Open
acourtiol wants to merge 1 commit into
opengento:mainfrom
acourtiol:feat/media-app-worker
Open

Add worker-aware Media app (issues #2, #3)#1
acourtiol wants to merge 1 commit into
opengento:mainfrom
acourtiol:feat/media-app-worker

Conversation

@acourtiol
Copy link
Copy Markdown

@acourtiol acourtiol commented May 11, 2026

Companion PR — pub/get.php shim + composer.json extra.map update will land at opengento/magento2-frankenphp-base once this is merged (or in parallel if you'd like to review them together).

Problem this addresses

Two open issues on the sibling magento2-frankenphp-base repo:

  • #2 Product Image Cache generation failure — PLP/PDP product images return 404 because pub/get.php isn't replaced by the worker shim, so it runs stock Magento pub/get.php in classic mode. Under high media load this is either slow (~150–250 ms bootstrap per missing-media request) or fails outright depending on how FrankenPHP routes the request.
  • #3 Make all entry points benefit of shared bootstrapApp\Http and App\StaticResource go through BootstrapPool and reuse the hot ObjectManager. Media doesn't. This PR closes that gap for Magento\MediaStorage\App\Media.

Approach

New class: Opengento\Application\App\Media — sibling to App\Http and App\StaticResource. It's the third worker-mode entry point.

Per request, it:

  1. Snapshots State::$_areaCode (via reflection — see § Why reflection)
  2. Sets the field to null
  3. Constructs Magento\MediaStorage\App\Media via the worker's shared ObjectManager with request-derived (mediaDirectory, configCacheFile, isAllowed, relativeFileName) arguments — same contract that stock pub/get.php builds
  4. Delegates to MagentoMedia::launch() (which sets area to GLOBAL internally, materializes the file, returns the FileResponse)
  5. In finally{}, restores the original area code so the next /index.php or /static.php request on the same worker isn't affected

The companion PR on magento2-frankenphp-base ships:

  • a new pub/get.php that does \$frankengento = \Opengento\Application\App\Media::class; include 'worker.php';
  • a corresponding extra.map entry so the file lands in the project's pub/ directory at install time

Why reflection<a id="why-reflection">

`MagentoMedia::launch()` unconditionally calls `State::setAreaCode(Area::AREA_GLOBAL)` on every invocation. In worker mode the `State` singleton persists across requests and `State::setAreaCode()` throws "Area code is already set" on the second call.

`State` doesn't implement `ResetAfterRequestInterface` and isn't auto-tracked by `AppObjectManager`'s `Resetter` (it's resolved via `get()` not `create()`), so the resetter never clears `_areaCode` between requests.

`State` also exposes no public reset API:

  • `setAreaCode` throws
  • `emulateAreaCode` wraps a callback and restores, but Magento Media itself calls `setAreaCode` inside the callback — which still throws
  • `getAreaCode` throws when unset (so we can't even read the field cleanly to snapshot it)

Reflection on `_areaCode` is the smallest contained workaround. Save+restore lives in a `finally{}` so a media-app failure can't leave the worker in a broken area state for other endpoints.

If/when Magento adds `ResetAfterRequestInterface` to `State` (or exposes a public `clear()`), the reflection here can be deleted.

Open questions / design notes

  1. Should `App\Media` extend or wrap `MagentoMedia` rather than instantiate-and-delegate via `ObjectManager->create()`? I went with delegation to keep our class small and avoid coupling to `MagentoMedia` constructor params changing.
  2. Is there a cleaner alternative to reflection here? I couldn't find one — open to suggestions in review.

Stacking

Standalone — doesn't depend on any in-flight PR on this repo. The companion PR at `magento2-frankenphp-base` depends on this being merged (the new `pub/get.php` shim references `Opengento\Application\App\Media`).

Adds Opengento\Application\App\Media — the missing third worker-mode entry
point alongside App\Http (pub/index.php) and App\StaticResource
(pub/static.php). Closes the get.php gap that's currently behind two open
issues on magento2-frankenphp-base:

  - opengento#2 "Product Image Cache generation failure" (PLP/PDP image 404s)
  - #3 "Make all entry points benefit of shared bootstrap"

How it works
------------
App\Media implements AppInterface. Per request, it:
  1. Snapshots State::$_areaCode (via reflection — see "Why reflection")
  2. Clears it
  3. Constructs Magento\MediaStorage\App\Media via the worker's shared
     ObjectManager with request-derived (mediaDirectory, configCacheFile,
     isAllowed, relativeFileName) arguments — same contract stock
     pub/get.php uses
  4. Delegates to MagentoMedia::launch() (which sets area to GLOBAL
     internally, materializes the file, returns the FileResponse)
  5. In finally{}, restores the original area code so the next /index.php
     or /static.php request on the same worker sees the area BootstrapPool
     set up for it

The Magento\MediaStorage\App\Media constructor signature is byte-identical
between 2.4.8-p4 and 2.4.9-beta1 (verified by diffing both sources), so
this layer is dual-version compatible.

Why reflection
--------------
MagentoMedia::launch() calls State::setAreaCode(AREA_GLOBAL) unconditionally
on every invocation. In worker mode the State singleton persists across
requests and State::setAreaCode() throws "Area code is already set" on the
second call — State doesn't implement ResetAfterRequestInterface (and isn't
auto-tracked by AppObjectManager's Resetter since it's resolved via get(),
not create()), so the Resetter never clears it between requests.

State exposes no public reset API: setAreaCode throws, emulateAreaCode wraps
and restores, getAreaCode throws when unset. Reflection on the private
_areaCode field is the smallest contained workaround. The save+restore is
in a finally{} so a media-app failure can't leave the worker in a broken
area state for other endpoints.

If/when Magento adds ResetAfterRequestInterface to State (or exposes a
clear() method), the reflection here can be deleted.

composer.json
-------------
Also bumps require constraints to match the "PHP 8.4+, Magento 2.4.8+"
support matrix:

  - php: ^8.3 -> ^8.4
  - magento/framework: * -> ^103.0   (covers 2.4.8 + 2.4.9 dev)
  - magento/module-media-storage: * -> ^100.4
  - psr/log: * -> ^3.0
@acourtiol acourtiol force-pushed the feat/media-app-worker branch from 40a3cc3 to a591ccb Compare May 11, 2026 07:34
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