phpdot / filesystem
Coroutine-safe, PSR-native file storage for the PHPdot ecosystem: local and S3-compatible (AWS S3, Cloudflare R2, MinIO, DigitalOcean Spaces) over a PSR-18 + SigV4 client, with typed streams, resumable chunked uploads and first-class progress.
Requires
- php: >=8.5
- ext-fileinfo: *
- ext-hash: *
- league/mime-type-detection: ^1.15
- psr/event-dispatcher: ^1.0
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^2.0
- psr/http-server-handler: ^1.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.94
- nyholm/psr7: ^1.8
- phpdot/console: ^0.1
- phpdot/container: ^0.1
- phpstan/phpstan: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^13.0
- symfony/http-client: ^8.0
README
Coroutine-safe, PSR-native file storage. One FilesystemInterface spans local disk and S3-compatible
backends (AWS S3, Cloudflare R2, MinIO, DigitalOcean Spaces) through a hand-rolled PSR-18 + Signature V4
client — no AWS SDK. Bodies flow as PSR-7 streams with bounded memory, uploads are resumable, validation
is built in, and the server (not the browser) picks where bytes land.
Table of Contents
Requirements
| Requirement | Constraint |
|---|---|
| PHP | >= 8.5 |
ext-fileinfo |
* |
ext-hash |
* |
league/mime-type-detection |
^1.15 |
psr/event-dispatcher |
^1.0 |
psr/http-client |
^1.0 |
psr/http-factory |
^1.0 |
psr/http-message |
^2.0 |
psr/http-server-handler |
^1.0 |
Bring any PSR-17/PSR-18 implementation. phpdot/console and phpdot/container are dev-only suggestions
(the CLI commands and binding attributes are inert without them).
Installation
composer require phpdot/filesystem
Usage
use Nyholm\Psr7\Factory\Psr17Factory; use PHPdot\Filesystem\Adapter\LocalAdapter; use PHPdot\Filesystem\Filesystem; use PHPdot\Filesystem\FilesystemConfig; use PHPdot\Filesystem\Write\WriteContents; $psr17 = new Psr17Factory(); $adapter = new LocalAdapter(new FilesystemConfig(root: '/var/storage'), $psr17); $filesystem = new Filesystem($adapter, new WriteContents($psr17)); $filesystem->write('invoices/2026.pdf', $pdfBytes); // string | PSR-7 stream | UploadedFile $pdf = $filesystem->read('invoices/2026.pdf'); $url = $filesystem->url('invoices/2026.pdf'); // public or presigned, by visibility foreach ($filesystem->listContents('invoices', deep: true) as $entry) { echo $entry->path(), PHP_EOL; }
You talk to one interface; swap the backend by swapping one binding. In a PHPdot application the container
auto-binds FilesystemInterface, so the wiring above disappears.
Beyond read/write/list, the package adds a collect-all validation pipeline (MIME, size, extension,
image dimensions over a bounded prefix), a token path generator ({date}/{uuid}/{hash}…) so the
server picks the key, an optional managed-files layer that persists a FileRecord per file with
soft-delete and quarantine, and a tus-compatible resumable upload endpoint plus CLI uploader.
Architecture
Filesystem is the operator — it drives an AdapterInterface (LocalAdapter native fopen/rename, or the
S3 adapter over a PSR-18 + SigV4 client with no AWS SDK) and a WriteContents pipeline that collapses a
string, stream, or uploaded file into one readable stream. Validation, path generation, and the resumable
upload engine sit alongside, and I/O goes non-blocking automatically under Swoole without any
ext-swoole dependency.
graph TD
APP["Application / FilesystemInterface"]
FS["Filesystem<br/><br/>read / write / url / listContents"]
WRITE["WriteContents<br/><br/>string | stream | UploadedFile → readable stream"]
ADAPTER["AdapterInterface<br/><br/>LocalAdapter (fopen/rename) / S3 adapter"]
S3["S3Client + SignatureV4<br/><br/>PSR-18 client, header-auth + presign, no AWS SDK"]
EXTRAS["Validation · Path generator · Upload engine<br/><br/>MIME/size/dimensions, token keys, resumable multipart"]
APP --> FS
FS --> WRITE
FS --> ADAPTER
ADAPTER --> S3
FS --> EXTRAS
Loading
Testing
composer install composer test # PHPUnit composer analyse # PHPStan, level max + strict rules composer cs-check # PHP-CS-Fixer composer check # All three
The unit suite (including the S3 client, signing vectors, and adapters against an in-memory HTTP client)
runs with no external services. The S3 integration suite connects to a real bucket and skips unless AWS
credentials and PHPDOT_S3_TEST_BUCKET are set.
License
MIT.
This repository is a read-only mirror, generated by CI from phpdot/monorepo. Pull requests and issues belong in the monorepo.