tobento/service-file-storage

File storage for PHP applications.

Installs: 116

Dependents: 4

Suggesters: 1

Security: 0

Stars: 0

Watchers: 1

Forks: 0

Open Issues: 0

pkg:composer/tobento/service-file-storage

2.0.1 2026-01-28 14:15 UTC

This package is auto-updated.

Last update: 2026-01-28 14:17:12 UTC


README

File storage interface for PHP applications using Flysystem as default implementation.

Table of Contents

Getting started

Add the latest version of the file storage service project running this command.

composer require tobento/service-file-storage

Requirements

  • PHP 8.4 or greater

Highlights

  • Framework-agnostic, will work with any project
  • Decoupled design

Documentation

Create Storage

Check out the Available Storages section to create storages.

Public Storage

Public storages are intended for assets that can be accessed directly by end-users.

  • URL available (when configured, e.g. via public_url)
  • No signing required
  • For public assets, such as:
    • website images
    • CSS / JS files
    • thumbnails
    • media intended for direct embedding

Public storage is ideal when files should be openly accessible without authentication.

Create Public Storage

Check out the Available Storages section to create storages using the appropriate storage adapter.
A public storage is created by setting its type to public.
If the storage adapter supports direct URL generation (for example in the Flysystem Storage),
you may configure a public_url to enable public file URLs.

Note

The storage type does not automatically make files public.
It is a semantic flag that your application and configuration must handle correctly
(for example by defining a public_url or implementing access control).

Private Storage

Private storages are intended for files that must not be directly exposed.

  • No direct URL
  • Access only through your application
  • Can generate signed URLs (if your app implements this)
  • Used for, for example:
    • user uploads
    • original images (before processing)
    • protected downloads
    • documents behind authentication
    • editor-only or unpublished assets

Private storage is ideal when you need full control over who can access a file.

Create Private Storage

Check out the Available Storages section to create storages using the appropriate storage adapter.
A private storage is created by setting its type to private.

Note

The storage type does not automatically make files private.
It is a semantic flag that your application and configuration must handle correctly.

File

Write File

use Tobento\Service\FileStorage\FileWriteException;

try {
    $storage->write(
        path: 'folder/file.txt',
        content: 'message',
    );
} catch (FileWriteException $e) {
    //
}

supported content

  • string
  • resource
  • any object implementing Stringable
  • Psr\Http\Message\StreamInterface
  • Tobento\Service\Filesystem\File

File Exists

Returns true if file exists, otherwise false.

$exists = $storage->exists(path: 'folder/image.jpg');

var_dump($exists);
// bool(true)

Retrieve File

Use the with method to retrieve specific file attributes. Check out the Available File Attributes for more detail.

use Tobento\Service\FileStorage\FileInterface;
use Tobento\Service\FileStorage\FileNotFoundException;

try {
    $file = $storage
        ->with('stream', 'mimeType')
        ->file(path: 'folder/image.jpg');

    var_dump($file instanceof FileInterface);
    // bool(true)
} catch (FileNotFoundException $e) {
    //
}

Check out the File Interface to learn more about it.

Retrieve Files

Use the with method to retrieve specific file attributes. Check out the Available File Attributes for more detail.

use Tobento\Service\FileStorage\FilesInterface;

$files = $storage->with('stream', 'mimeType')->files(
    path: 'folder',
    recursive: false // is default
);

var_dump($files instanceof FilesInterface);
// bool(true)

Check out the Files Interface to learn more about it.

Delete File

use Tobento\Service\FileStorage\FileException;

try {
    $storage->delete(path: 'folder/image.jpg');
} catch (FileException $e) {
    // could not delete file
}

Move File

use Tobento\Service\FileStorage\FileException;

try {
    $storage->move(from: 'old/image.jpg', to: 'new/image.jpg');
} catch (FileException $e) {
    // could not move file
}

Copy File

use Tobento\Service\FileStorage\FileException;

try {
    $storage->copy(from: 'old/image.jpg', to: 'new/image.jpg');
} catch (FileException $e) {
    // could not copy file
}

Available File Attributes

$file = $storage
    ->with(
        'stream',
        'mimeType',
        'size', // not needed if stream is set as it can get size from stream.
        'width', 'height', // ignored if not image.
        'lastModified',
        'url',
    )
    ->file(path: 'folder/image.jpg');

$stream = $file->stream();
$mimeType = $file->mimeType();
$size = $file->size();
$width = $file->width();
$height = $file->height();
$lastModified = $file->lastModified();
$url = $file->url();

Check out the File Interface to learn more about it.

Folder

Create Folder

use Tobento\Service\FileStorage\FolderException;

try {
    $storage->createFolder(path: 'folder/name');
} catch (FolderException $e) {
    // could not create folder
}

Folder Exists

Returns true if folder exists, otherwise false.

$exists = $storage->folderExists(path: 'folder/name');

var_dump($exists);
// bool(true)

Retrieve Folders

use Tobento\Service\FileStorage\FoldersInterface;

$folders = $storage->folders(
    path: '',
    recursive: false // is default
);

var_dump($folders instanceof FoldersInterface);
// bool(true)

Check out the Folders Interface to learn more about it.

Delete Folder

Deleting a folder will delete the specified folder and all of its files.

use Tobento\Service\FileStorage\FolderException;

try {
    $storage->deleteFolder(path: 'folder/name');
} catch (FolderException $e) {
    // could not delete folder
}

Storages

Create Storages

use Tobento\Service\FileStorage\Storages;
use Tobento\Service\FileStorage\StoragesInterface;

$storages = new Storages();

var_dump($storages instanceof StoragesInterface);
// bool(true)

Add Storages

add

use Tobento\Service\FileStorage\StorageInterface;

$storages->add($storage); // StorageInterface

register

You may use the register method to only create the storage if requested.

use Tobento\Service\FileStorage\StorageInterface;

$storages->register(
    'name',
    function(string $name): StorageInterface {
        // create storage:
        return $storage;
    }
);

Get Storage

If the storage does not exist or could not get created it throws a StorageException.

use Tobento\Service\FileStorage\StorageInterface;
use Tobento\Service\FileStorage\StorageException;

$storage = $storages->get('name');

var_dump($storage instanceof StorageInterface);
// bool(true)

$storages->get('unknown');
// throws StorageException

You may use the has method to check if a storage exists.

var_dump($storages->has('name'));
// bool(false)

Default Storages

You may add default storages for your application design.

use Tobento\Service\FileStorage\Storages;
use Tobento\Service\FileStorage\StorageInterface;
use Tobento\Service\FileStorage\StorageException;

$storages = new Storages();

// add "locale" storage:
$storages->add($storage);

// add default:
$storages->addDefault(name: 'primary', storage: 'local');

// get default storage for the specified name.
$primaryStorage = $storages->default('primary');

var_dump($primaryStorage instanceof StorageInterface);
// bool(true)

var_dump($storages->hasDefault('primary'));
// bool(true)

var_dump($storages->getDefaults());
// array(1) { ["primary"]=> string(5) "local" }

$storages->default('unknown');
// throws StorageException

Available Storages

Flysystem Storage

Check out the League Flysystem to learn more about it.

use Tobento\Service\FileStorage\Flysystem;
use Tobento\Service\FileStorage\StorageInterface;
use Nyholm\Psr7\Factory\Psr17Factory;

$filesystem = new \League\Flysystem\Filesystem(
    adapter: new \League\Flysystem\Local\LocalFilesystemAdapter(
        location: __DIR__.'/root/directory/'
    )
);

$storage = new Flysystem\Storage(
    name: 'local',
    flysystem: $filesystem,
    fileFactory: new Flysystem\FileFactory(
        flysystem: $filesystem,
        streamFactory: new Psr17Factory()
    ),
    type: 'private', // or 'public'
);

var_dump($storage instanceof StorageInterface);
// bool(true)

Null Storage

use Tobento\Service\FileStorage\NullStorage;
use Tobento\Service\FileStorage\StorageInterface;

$storage = new NullStorage(
    name: 'null',
    type: 'private', // or 'public'
);

var_dump($storage instanceof StorageInterface);
// bool(true)

Read Only Storage Adapter

Any storage implementing the StorageInterface::class can be made read-only by decorating them using the ReadOnlyStorageAdapter:

use Tobento\Service\FileStorage\ReadOnlyStorageAdapter;
use Tobento\Service\FileStorage\StorageInterface;

$storage = new ReadOnlyStorageAdapter(
    storage: $storage, // StorageInterface
    
    // You may throw exeptions if files are not found
    // otherwise an "empty" file is returned.
    throw: true, // false is default
);

var_dump($storage instanceof StorageInterface);
// bool(true)

Interfaces

Storage Factory Interface

You may use the storage factory interface for creating storages.

use Tobento\Service\FileStorage\StorageFactoryInterface;
use Tobento\Service\FileStorage\StorageInterface;
use Tobento\Service\FileStorage\StorageException;

interface StorageFactoryInterface
{
    /**
     * Create a new Storage based on the configuration.
     *
     * @param string $name Any storage name.
     * @param array $config Configuration data.
     * @return StorageInterface
     * @throws StorageException
     */
    public function createStorage(string $name, array $config = []): StorageInterface;
}

Storage Interface

All methods from:

name

Returns the storage name.

var_dump($storage->name());
// string(5) "local"

type

Returns the storage type (public or private).

var_dump($storage->type());
// string(6) "public"

isPublic

Returns true if the storage is public.

var_dump($storage->isPublic());
// bool(true)

isPrivate

Returns true if the storage is private.

var_dump($storage->isPrivate());
// bool(false)

Storages Interface

All methods from:

File Interface

use Tobento\Service\FileStorage\FileInterface;

$file = $storage
    ->with(
        'stream', 'mimeType', 'size', 'width',
        'lastModified', 'url',
    )
    ->file(path: 'folder/image.jpg');
    
var_dump($file instanceof FileInterface);
// bool(true)

Methods

var_dump($file->storageName());
// string(5) "local"

// Modify storage name returning a new instance:
var_dump($file->withStorageName(name: 'foo'));
// string(3) "foo"

var_dump($file->path());
// string(16) "folder/image.jpg"

var_dump($file->name());
// string(9) "image.jpg"

var_dump($file->filename());
// string(5) "image"

var_dump($file->extension());
// string(3) "jpg"

var_dump($file->folderPath());
// string(6) "folder"

var_dump($file->stream() instanceof \Psr\Http\Message\StreamInterface);
// bool(true) or NULL

var_dump($file->content());
// string(...) or NULL

var_dump($file->mimeType());
// string(10) "image/jpeg" or NULL

var_dump($file->size());
// int(20042) or NULL

var_dump($file->humanSize());
// string(5) "15 KB"

var_dump($file->width());
// int(450) or NULL

var_dump($file->height());
// int(450) or NULL

var_dump($file->lastModified());
// int(1672822057) or NULL

var_dump($file->url());
// string(40) "https://www.example.com/folder/image.jpg" or NULL

// Modify url returning a new instance:
$file = $file->withUrl('https://www.example.com/folder/image.jpg');

var_dump($file->metadata());
// array(0) { }

var_dump($file->isHtmlImage());
// bool(true)

Files Interface

use Tobento\Service\FileStorage\FilesInterface;

$files = $storage->with('stream', 'mimeType')->files(
    path: 'folder',
    recursive: false // is default
);

var_dump($files instanceof FilesInterface);
// bool(true)

var_dump($files instanceof \IteratorAggregate);
// bool(true)

filter

Returns a new instance with the filtered files.

use Tobento\Service\FileStorage\FileInterface;

$files = $files->filter(
    fn(FileInterface $f): bool => in_array($f->mimeType(), ['image/jpeg'])
);

sort

Returns a new instance with the files sorted.

use Tobento\Service\FileStorage\FileInterface;

$files = $files->sort(
    fn(FileInterface $a, FileInterface $b) => $a->path() <=> $b->path()
);

all

Returns all files.

use Tobento\Service\FileStorage\FileInterface;

foreach($files->all() as $file) {
    var_dump($file instanceof FileInterface);
    // bool(true)
}

// or just
foreach($files as $file) {}

Folder Interface

use Tobento\Service\FileStorage\FolderInterface;

foreach($storage->folders(path: 'foo') as $folder) {
    var_dump($folder instanceof FolderInterface);
    // bool(true)
}

Methods

var_dump($folder->storageName());
// string(5) "local"

// Modify storage name returning a new instance:
var_dump($folder->withStorageName(name: 'foo'));
// string(3) "foo"

var_dump($folder->path());
// string(7) "foo/bar"

var_dump($folder->parentPath());
// string(3) "foo"

var_dump($folder->name());
// string(3) "bar"

var_dump($folder->lastModified());
// int(1671889402) or NULL

var_dump($folder->metadata());
// array(0) { }

Folders Interface

use Tobento\Service\FileStorage\FoldersInterface;

$folders = $storage->folders(path: '');

var_dump($folders instanceof FoldersInterface);
// bool(true)

filter

Returns a new instance with the filtered folders.

use Tobento\Service\FileStorage\FolderInterface;

$folders = $folders->filter(
    fn(FolderInterface $f): bool => in_array($f->storageName(), ['local'])
);

sort

Returns a new instance with the folders sorted.

use Tobento\Service\FileStorage\FolderInterface;

$folders = $folders->sort(
    fn(FolderInterface $a, FolderInterface $b) => $a->path() <=> $b->path()
);

first

Returns the first folder or null if none.

use Tobento\Service\FileStorage\FolderInterface;

$folder = $folders->first();

// null|FolderInterface

get

Returns the folder by path or null if not exists.

use Tobento\Service\FileStorage\FolderInterface;

$folder = $folders->get(path: 'foo');

// null|FolderInterface

all

Returns all folders.

use Tobento\Service\FileStorage\FolderInterface;

foreach($folders->all() as $folder) {
    var_dump($folder instanceof FolderInterface);
    // bool(true)
}

// or just
foreach($folders as $folder) {}

Repositories

The file storage service provides optional repository abstractions for querying files and folders using a consistent, storage-agnostic API.
Repositories allow you to filter, sort, and retrieve filesystem items in a structured way, similar to database repositories.

Using repositories is optional.
To enable them, install the following packages:

composer require tobento/service-repository tobento/service-repository-storage tobento/service-storage

Repositories can also be used together with
tobento/app-crud
to build CRUD interfaces or file manager UIs, as the repository API is fully compatible out of the box.
This integration is optional and not required to use repositories.

Repositories also integrate seamlessly with
tobento/app-search,
allowing you to make files and folders searchable using the RepositorySearchable adapter.
This integration is optional and not required to use repositories.

File Repository

The File Repository offers a structured, storage-agnostic way to query files from a storage location.
It supports filtering, sorting, limits, recursive traversal, and root folder scoping.

The repository is provided by
tobento/service-repository-storage,
which contains the full repository documentation, and relies on
tobento/service-repository
for the base repository interfaces.

use Tobento\Service\FileStorage\Repository\FileRepository;

$repository = new FileRepository(storage: $storage);

// Configure repository returing a new instance:
$repository = $repository->withStorage($anotherStorage);

$repository = $repository->withRootFolder('images/'); // default = ''

$repository = $repository->withFileAttributes(['size', 'lastModified']);

$repository = $repository->withRecursive(true); // default false

By default, all file attributes are loaded, and recursive mode is disabled (false).
See the list of available attributes under
Available File Attributes.

Folder Repository

The Folder Repository provides a structured, storage-agnostic way to query folders from a storage location.
It supports filtering, sorting, limits, recursive traversal, and root folder scoping.

The repository is provided by
tobento/service-repository-storage,
which contains the full repository documentation, and relies on
tobento/service-repository
for the base repository interfaces.

use Tobento\Service\FileStorage\Repository\FolderRepository;

$repository = new FolderRepository(storage: $storage);

// Configure repository returning a new instance:
$repository = $repository->withStorage($anotherStorage);

$repository = $repository->withRootFolder('images/'); // default = ''

$repository = $repository->withRecursive(true); // default false

By default, all folder data is loaded, and recursive mode is disabled (false).

File and Folder Repository

The File and Folder Repository provides a unified way to query both files and folders from a storage location.
It supports filtering, sorting, limits, recursive traversal, and root folder scoping, returning a mixed collection of filesystem items.

The repository is provided by
tobento/service-repository-storage,
which contains the full repository documentation, and relies on
tobento/service-repository
for the base repository interfaces.

use Tobento\Service\FileStorage\Repository\FileRepository;
use Tobento\Service\FileStorage\Repository\FolderRepository;
use Tobento\Service\FileStorage\Repository\FileFolderRepository;

$fileRepo = new FileRepository(storage: $storage);
$folderRepo = new FolderRepository(storage: $storage);

$repository = new FileFolderRepository(
    fileRepository: $fileRepo,
    folderRepository: $folderRepo,
);

// Configure repository returning a new instance:
$repository = $repository->withStorage($anotherStorage);

$repository = $repository->withRootFolder('images/'); // default = ''

$repository = $repository->withRecursive(true); // default false

By default, recursive mode is disabled (false), and all file and folder data is loaded.

Credits