Skip to content

Latest commit

 

History

History
367 lines (276 loc) · 11.9 KB

File metadata and controls

367 lines (276 loc) · 11.9 KB

Getting started

Installation

flysystem-bundle requires PHP 8.2+ and Symfony 6.0+.

You can install the bundle using Symfony Flex:

composer require league/flysystem-bundle

Basic usage

The default configuration file created by Symfony Flex provides enough configuration to use Flysystem in your application as soon as you install the bundle:

# config/packages/flysystem.yaml

flysystem:
    storages:
        default.storage:
            local:
                directory: '%kernel.project_dir%/var/storage/default'

This configuration defines a single storage service (default.storage) based on the local adapter and configured to use the %kernel.project_dir%/var/storage/default directory.

For each storage defined under flysystem.storages, an associated service is created using the name you provide (in this case, a service default.storage will be created). The bundle also creates a named alias for each of these services.

This means you can inject the storage services in your services and controllers like this:

1) Using service autowiring: typehint your service/controller argument with FilesystemOperator and use the #[Target] attribute to select the storage by name:

use League\Flysystem\FilesystemOperator;
use Symfony\Component\DependencyInjection\Attribute\Target;

class MyService
{
    public function __construct(
        #[Target('default.storage')] private FilesystemOperator $storage,
    ) {
    }

    // ...
}

2) Using manual service registration: in your services, inject the storage service directly using its configured name (in this case default.storage):

# config/services.yaml
services:
    # ...

    App\MyService:
        arguments:
            $storage: '@default.storage'

Once you have a FilesystemOperator, you can call methods from the Filesystem API to interact with your storage.

Discovering the available configuration options

Every option, including adapter-specific ones, is declared with a description, a default value and whether it's required. Dump the full reference tree instead of reading the source code:

bin/console config:dump-reference flysystem

Transferring files with console commands

If you need to transfer files between the local filesystem and one of your configured storages, the bundle also provides two console commands:

bin/console flysystem:push <storage> <local-source> [remote-destination]
bin/console flysystem:pull <storage> <remote-source> [local-destination]

The <storage> argument is the configured Flysystem storage name (for example default.storage), not the adapter type. When the destination is omitted, the basename of the source path is used.

Using multiple storages to improve readability

While using the default storage can be enough, it is usually recommended to create multiple storages, even if behind the scene you may rely on the same adapter.

The reason for this is the added readability this provides to your project code: by naming your storages using their intents, you will naturally increase the readability of your autowired arguments. For example:

# config/packages/flysystem.yaml

flysystem:
    storages:
        users.storage:
            local:
                directory: '%kernel.project_dir%/storage/users'

        projects.storage:
            local:
                directory: '%kernel.project_dir%/storage/projects'
use League\Flysystem\FilesystemOperator;
use Symfony\Component\DependencyInjection\Attribute\Target;

class MyService
{
    public function __construct(
        #[Target('users.storage')] private FilesystemOperator $usersStorage,
        #[Target('projects.storage')] private FilesystemOperator $projectsStorage,
    ) {
    }

    // ...
}

Using memory storage in tests

One of the best reasons to use a filesystem abstraction in your project is the ability it gives you to swap the actual implementation during tests.

More specifically, it can be useful to swap from a persisted storage to a memory one during tests, both to ensure the state is reset between tests and to increase tests speed.

To achieve this, you need to install the memory provider:

composer require league/flysystem-memory

Then, you can overwrite your storages in the test environment:

# config/packages/flysystem.yaml

flysystem:
    storages:
        users.storage:
            local:
                directory: '%kernel.project_dir%/storage/users'
# config/packages/test/flysystem.yaml

flysystem:
    storages:
        users.storage:
            memory: ~

This configuration will swap every reference to the users.storage service (or to a #[Target('users.storage')] FilesystemOperator argument) from a local adapter to a memory one during tests.

Using read only to disallow any write operations

In some contexts, it can be useful to protect any write operations on your storages service.

To achieve this, you need to install the read-only package:

composer require league/flysystem-read-only

And then, you can configure your storage with the read_only option.

# config/packages/flysystem.yaml

flysystem:
    storages:
        users.storage:
            local:
                directory: '%kernel.project_dir%/storage/users'
            read_only: true

With this configuration, any write operation will throw a suitable exception.

Storage options

Beyond the adapter-specific options, every storage accepts a set of general options, regardless of the adapter used. Most of them are passed to the underlying Flysystem Filesystem instance:

# config/packages/flysystem.yaml

flysystem:
    storages:
        users.storage:
            local:
                directory: '%kernel.project_dir%/storage/users'

            # Default visibility (public/private) applied to files that don't specify one
            visibility: public

            # Default visibility (public/private) applied to directories that don't specify one
            directory_visibility: public

            # Keep the original file visibility when copying or moving it, instead of
            # recomputing it from the defaults above
            retain_visibility: true

            # Base URL(s) used to build public URLs for adapters that can't generate one
            # natively (a list of URLs is sharded across paths using a hash of the path)
            public_url: 'https://cdn.example.com/'

            # Service ID of a League\Flysystem\PathNormalizer implementation, to override
            # how paths are normalized before being sent to the adapter
            path_normalizer: 'App\Flysystem\MyPathNormalizer'

            # Service ID of a League\Flysystem\UrlGeneration\PublicUrlGenerator
            # implementation, used instead of the adapter/public_url logic
            public_url_generator: 'App\Flysystem\MyPublicUrlGenerator'

            # Service ID of a League\Flysystem\UrlGeneration\TemporaryUrlGenerator
            # implementation, required to call $storage->temporaryUrl() on adapters that
            # don't support it natively (AWS S3, AsyncAws S3, Azure and Google Cloud
            # Storage do support it out of the box)
            temporary_url_generator: 'App\Flysystem\MyTemporaryUrlGenerator'

            # Converts the storage to read-only, see "Using read only to disallow any
            # write operations" above (requires league/flysystem-read-only)
            read_only: false

All of these options are optional: adapters that natively support public/temporary URLs (such as AWS S3, AsyncAws S3, Azure Blob Storage, Google Cloud Storage and WebDAV for public URLs) don't require public_url or public_url_generator to be configured.

Using a custom mime type detector

The aws, asyncaws, ftp, gcloud, gridfs, local, memory and sftp adapters accept a mimeTypeDetector option to override how mime types are detected when writing files (for example when the built-in fileinfo-based detection guesses the wrong type for some of your files). It expects a service ID implementing League\MimeTypeDetection\MimeTypeDetector (from league/mime-type-detection, already installed as a Flysystem dependency). When left unset, each adapter falls back to its own default detector (usually League\MimeTypeDetection\FinfoMimeTypeDetector).

Overriding or adding mime types for specific extensions

The most common need is not to reimplement detection from scratch, but to correct or extend the mime type guessed for a few extensions, while keeping the default fileinfo-based behavior for everything else. league/mime-type-detection provides the building blocks for this: FinfoMimeTypeDetector accepts an ExtensionToMimeTypeMap, and OverridingExtensionToMimeTypeMap lets you layer your own overrides on top of the package's built-in GeneratedExtensionToMimeTypeMap.

Since these classes live outside the App\ namespace, they aren't autoconfigured by Symfony and need to be wired explicitly as services, using named arguments to only override what's needed:

# config/services.yaml
services:
    # ...

    App\Flysystem\MyMimeTypeDetector:
        class: League\MimeTypeDetection\FinfoMimeTypeDetector
        arguments:
            $extensionMap: !service
                class: League\MimeTypeDetection\OverridingExtensionToMimeTypeMap
                arguments:
                    $innerMap: !service
                        class: League\MimeTypeDetection\GeneratedExtensionToMimeTypeMap
                    $overrides:
                        env: 'text/plain'
                        myapp: 'application/vnd.myapp+json'

Then reference it by its service ID in the adapter configuration:

# config/packages/flysystem.yaml

flysystem:
    storages:
        users.storage:
            local:
                directory: '%kernel.project_dir%/storage/users'
                mimeTypeDetector: App\Flysystem\MyMimeTypeDetector

Implementing a detector from scratch

For more advanced needs (calling an external content-sniffing service, adding caching or logging around detection, enforcing a strict allow-list of mime types, ...), implement the interface directly:

// src/Flysystem/MyMimeTypeDetector.php
namespace App\Flysystem;

use League\MimeTypeDetection\MimeTypeDetector;

class MyMimeTypeDetector implements MimeTypeDetector
{
    public function detectMimeType(string $path, $contents): ?string
    {
        // ...
    }

    public function detectMimeTypeFromBuffer(string $contents): ?string
    {
        // ...
    }

    public function detectMimeTypeFromPath(string $path): ?string
    {
        // ...
    }

    public function detectMimeTypeFromFile(string $path): ?string
    {
        // ...
    }
}

With autowiring enabled (the Symfony default), this service is registered under its own class name, so you can reference it directly the same way:

# config/packages/flysystem.yaml

flysystem:
    storages:
        users.storage:
            local:
                directory: '%kernel.project_dir%/storage/users'
                mimeTypeDetector: App\Flysystem\MyMimeTypeDetector

Next

Cloud storage providers