Problem/Motivation

PHP instantiates stream wrapper classes itself, bypassing the DI container. FlysystemStreamWrapper handles that with a static factory reference primed by FlysystemStreamWrapper::primeStaticFactory(), which is documented as being called from FlysystemStreamWrapperManager::register() "at bootstrap (e.g. from hook_boot or a service subscriber)".

Nothing in the module ever calls register(). Grepping 3.0.0-beta2, the only caller of primeStaticFactory() is FlysystemStreamWrapperManager::register() itself, and register() has no callers at all — the schemes are registered as tagged stream_wrapper services through core's StreamWrapperManager instead, which never touches the static reference.

So any wrapper instance that PHP creates internally — every native call such as move_uploaded_file(), is_dir(), fopen() on a Flysystem URI made outside a container-built wrapper — has no factory and cannot resolve its filesystem. We hit this through FlysystemFileSystem::moveUploadedFile(), which calls move_uploaded_file() unconditionally: the upload silently fails to reach the object store.

Steps to reproduce

  1. Configure a Flysystem scheme and set it as the destination of a file field.
  2. Upload a file through the UI (goes through FlysystemFileSystem::moveUploadedFile()move_uploaded_file() → PHP-instantiated FlysystemStreamWrapper).
  3. The internally instantiated wrapper has no factory available.

Proposed resolution

Prime the static factory in the FlysystemFileSystem constructor. file_system is the service every one of those native-function code paths is reached through, so priming there guarantees the reference exists before the first native call, without depending on a bootstrap hook that does not exist:

public function __construct(
  private readonly FilesystemFactoryInterface $factory,
  // ...
) {
  FlysystemStreamWrapper::primeStaticFactory($factory);
}

Alternatives (either is fine, they are not exclusive):

  • Call FlysystemStreamWrapperManager::register() from a real bootstrap point (a service subscriber or hook_boot-equivalent), as its docblock intends.
  • Prime the static factory from the stream wrapper service factory so that it is set as soon as the container builds a wrapper.

Remaining tasks

  • Review; decide where priming belongs (this patch takes the cheapest safe option).
  • Kernel test: upload a file to a Flysystem-backed field and assert the object exists in the adapter.

User interface changes

None.

API changes

None.

Data model changes

None.

CommentFileSizeAuthor
flysystem-02-prime-static-factory.patch1.53 KBluigisa

Issue fork flysystem-3616485

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

luigisa created an issue. See original summary.

lisa.rae made their first commit to this issue’s fork.

lisa.rae’s picture

Issue summary: View changes

@luigisa Turned your patch into a MR and added a test. Verified that test fails without the patch and passes with the patch. Please review and provide feedback. If acceptable, change status of the ticket to RTBC.

luigisa’s picture

Status: Needs review » Reviewed & tested by the community

Perfect. Thank you very much for your help.

  • eaa83826 committed on 3.0.x
    Issue #3616485 by luigisa, lisa.rae: FlysystemStreamWrapper's static...
lisa.rae’s picture

Status: Reviewed & tested by the community » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

lisa.rae’s picture

Status: Fixed » Closed (fixed)