Skip to content

Filesystem

The filesystem abstraction is built on Flysystem and consists of three layers: adapter factories (create Flysystem adapters for a named backend), a filesystem factory (wraps adapters in typed reader/writer instances), and a pool (the primary injection point with pre-wired convenience methods for each well-known storage location).


Using the pool

Inject FilesystemPoolInterface and use the convenience methods for the location you need. The pool caches instances internally, so multiple calls with the same arguments return the same object.

use Amarant\Framework\Contract\Factory\Filesystem\FilesystemPoolInterface;

public function __construct(
    private readonly FilesystemPoolInterface $filesystemPool
) {
}

public function writeReport(string $content): void
{
    $writer = $this->filesystemPool->getApplicationWriter('var/reports');
    $writer->write('report.csv', $content);
}

public function readConfig(string $path): string
{
    $reader = $this->filesystemPool->getApplicationReader();
    return $reader->read($path);
}

Convenience methods

Method Adapter Root
getLocalWriter(?string $location) local $location (default /)
getLocalReader(?string $location) local $location (default /)
getApplicationWriter(?string $subdirectory) application {app.root}[/subdirectory]
getApplicationReader(?string $subdirectory) application {app.root}[/subdirectory]
getMediaWriter(?string $subdirectory) configured media adapter subdirectory within the adapter's root
getPrivateWriter(?string $subdirectory) configured private adapter subdirectory within the adapter's root
getTempWriter(?string $subdirectory) configured temp adapter subdirectory within the adapter's root
getLogWriter(?string $subdirectory) configured log adapter subdirectory within the adapter's root
getWriter(string $name, ?string $location) any adapter by name $location passed to adapter
getReader(string $name, ?string $location) any adapter by name $location passed to adapter

Methods returning a "writer" return FilesystemOperatorInterface (read + write). Methods returning a "reader" return FileSystemReaderInterface (read-only).

The adapter used for getMediaWriter, getPrivateWriter, getTempWriter, and getLogWriter is determined at runtime from configuration (see Storage configuration).


Operator interfaces

Interface Extends Description
FileSystemReaderInterface Flysystem FilesystemReader Read operations + getPublicUrl(string): ?string + checksum(string): string
FileSystemWriterInterface Flysystem FilesystemOperator Full Flysystem operator (read + write + move + delete + list)
FilesystemOperatorInterface FileSystemReaderInterface + FileSystemWriterInterface Combines both — returned by all writer methods

All standard Flysystem methods are available: read(), readStream(), write(), writeStream(), delete(), deleteDirectory(), createDirectory(), move(), copy(), fileExists(), directoryExists(), listContents(), mimeType(), fileSize(), lastModified(), etc.

getPublicUrl() returns a public-facing URL for the file when the adapter supports it (e.g. merchant-local and S3 adapters), or null for adapters without URL generation.


Built-in adapters

Name Class Root Public URL
local LocalFilesystemAdapterFactory $location argument (default /) No
application ApplicationFilesystemAdapterFactory {app.root} No
application-log ApplicationLogFilesystemAdapterFactory {app.root}/var/log No
application-private ApplicationPrivateFilesystemAdapterFactory {app.root}/private No
application-temp ApplicationTempFilesystemAdapterFactory {app.root}/var No
merchant-local MerchantLocalFilesystemAdapterFactory Merchant shared filesystem path Yes — merchant base media URL
s3_public S3FilesystemAdapterFactory S3 bucket root (public ACL) Yes — S3 public URL
s3_private S3PrivateFilesystemAdapterFactory S3 bucket root (private ACL) No

local is a bare adapter that takes any absolute $location. application is identical but always roots at the application root, resolved from the kernel context. The application-* variants are convenience aliases into well-known subdirectories.

The merchant-local adapter wraps LocalFilesystemAdapter with a PublicUrlGenerator decorator that prepends the merchant's base media URL to any path, making getPublicUrl() work correctly in multi-tenant environments.


Storage configuration

The adapter used for each logical storage type is set in the store configuration. The value must be the adapter name string (the value returned by FilesystemAdapterFactoryInterface::getName()).

Config key FilesystemConfiguration method Default intention
system/filesystem/media_type getMediaFilesystemType() Public media files (images, documents)
system/filesystem/private_type getPrivateFilesystemType() Private files (invoices, exports)
system/filesystem/temp_type getTempFilesystemType() Temporary files, uploads in progress
system/filesystem/log_type getLogFilesystemType() Application logs

S3/compatible storage settings (each has separate public and private variants):

Config key Description
system/filesystem/type/s3/region AWS region
system/filesystem/type/s3/bucket_name Bucket name
system/filesystem/type/s3/endpoint Custom endpoint URL (for S3-compatible services)
system/filesystem/type/s3/access_key Access key ID
system/filesystem/type/s3/secret_key Secret access key
system/filesystem/type/s3_private/region (same fields, private variant)

Adding a custom adapter

Implement FilesystemAdapterFactoryInterface. Implement SelectableFilesystemAdapterFactoryInterface instead if the adapter should appear in the backoffice storage type dropdown.

use Amarant\Framework\Contract\Factory\Filesystem\SelectableFilesystemAdapterFactoryInterface;
use Amarant\Framework\Contract\Io\FilesystemAdapterInterface;
use League\Flysystem\FilesystemAdapter;
use Override;

final class GcsFilesystemAdapterFactory implements SelectableFilesystemAdapterFactoryInterface
{
    public function __construct(
        private readonly GcsConfiguration $config
    ) {
    }

    #[Override] public static function getName(): string
    {
        return 'gcs_public';
    }

    #[Override] public static function getLabel(): string
    {
        return 'Google Cloud Storage (public)';
    }

    #[Override] public function create(?string $location = null): FilesystemAdapter|FilesystemAdapterInterface
    {
        // build and return a Flysystem FilesystemAdapter
    }
}

Then extend the FilesystemFactoryInterface binding to add your adapter to the adapterFactories map:

Vendor/ModuleName/Configuration/Di.php
use Amarant\Framework\Contract\Di\ConfigurableContainerInterface;
use Amarant\Framework\Contract\Factory\Filesystem\FilesystemFactoryInterface;
use Amarant\Framework\Enum\DiEnum;
use Vendor\ModuleName\Factory\Filesystem\Adapter\GcsFilesystemAdapterFactory;

$container->extend(
    FilesystemFactoryInterface::class,
    function (\Amarant\Framework\Contract\Di\BindInterface $bind, ConfigurableContainerInterface $container) {
        $bind->addValueToArgument(
            'adapterFactories',
            DiEnum::getIdInjection(GcsFilesystemAdapterFactory::class),
            GcsFilesystemAdapterFactory::getName()
        );
    }
);

After registration, use the adapter by name via getWriter('gcs_public') / getReader('gcs_public'), or set one of the configuration keys (e.g. system/filesystem/media_type) to 'gcs_public' to route a logical storage slot to it.


FinderFactory

For searching local files by name, extension, or content, inject FinderFactory and call create() to get a fresh Symfony Finder instance:

use Amarant\Framework\Factory\Filesystem\FinderFactory;

public function __construct(
    private readonly FinderFactory $finderFactory
) {
}

public function findPhpFiles(string $directory): iterable
{
    return $this->finderFactory->create()
        ->in($directory)
        ->name('*.php')
        ->files();
}

Each call to create() returns a new, unconfigured Finder. FinderFactory does not use Flysystem — it operates directly on the local filesystem via Symfony Finder.