PHPackages                             jardisadapter/filesystem - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [File &amp; Storage](/categories/file-storage)
4. /
5. jardisadapter/filesystem

ActiveLibrary[File &amp; Storage](/categories/file-storage)

jardisadapter/filesystem
========================

Filesystem abstraction for local and cloud storage with unified read, write, and stream API

v1.0.0(1mo ago)042↓77.5%1MITPHPPHP &gt;=8.2CI passing

Since Jun 1Pushed 2w agoCompare

[ Source](https://github.com/jardisAdapter/filesystem)[ Packagist](https://packagist.org/packages/jardisadapter/filesystem)[ Docs](https://docs.jardis.io/en/adapter/filesystem)[ RSS](/packages/jardisadapter-filesystem/feed)WikiDiscussions main Synced 4w ago

READMEChangelog (4)Dependencies (4)Versions (3)Used By (1)

Jardis Filesystem
=================

[](#jardis-filesystem)

[![Build Status](https://github.com/jardisAdapter/filesystem/actions/workflows/ci.yml/badge.svg)](https://github.com/jardisAdapter/filesystem/actions/workflows/ci.yml/badge.svg)[![License: MIT](https://camo.githubusercontent.com/784362b26e4b3546254f1893e778ba64616e362bd6ac791991d2c9e880a3a64e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d677265656e2e737667)](LICENSE.md)[![PHP Version](https://camo.githubusercontent.com/a68b290dcc313d698dc138a1111aa83eee2f143605449d7e8b5416ea6f88558f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d253345253344382e322d3737374242342e737667)](https://www.php.net/)[![PHPStan Level](https://camo.githubusercontent.com/c51bda247654363d3e30bc352674dd761a9557803a14af0226eb411d6dc0006b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d4c6576656c253230382d627269676874677265656e2e737667)](phpstan.neon)[![PSR-12](https://camo.githubusercontent.com/34b10db0caa29bacd49bda5c437a8de95385f036f3230b31fa605326e18da22c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f436f64652532305374796c652d5053522d2d31322d626c75652e737667)](phpcs.xml)[![Zero Dependencies](https://camo.githubusercontent.com/6fcbd984bdb833bedbf8d69da5a4a10c97302c31d1f23c486d8d2785c5cb9a41/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446570656e64656e636965732d5a65726f2d627269676874677265656e2e737667)](composer.json)

> Part of **[Jardis](https://jardis.io)** — the Domain-Driven Design platform for PHP. You model your domain; Jardis generates the production-ready hexagonal code (DTOs, Command/Query handlers, repositories, persistence). This package is part of the open-source foundation that generated code runs on.

**File operations without the framework.** A lean filesystem abstraction for PHP covering local and S3-compatible storage — designed for applications that store uploads, manage assets, or sync backups. No Flysystem, no AWS SDK, no dependency bloat. Just cURL and PHP builtins.

---

Why This Filesystem?
--------------------

[](#why-this-filesystem)

- **Two classes are enough** — `FilesystemService` + a config object, nothing else
- **Multiple instances** — local for uploads, S3 for backups, both in the same project
- **Atomic handler pipeline** — each operation is its own invokable, orchestrated by closures
- **Stream support** — read and write large files without memory overhead
- **S3 without the SDK** — AWS Signature v4 via cURL, works with MinIO, DigitalOcean Spaces, etc.
- **Security hardened** — path traversal protection, symlink containment, XXE prevention, secret masking
- **79% test coverage** — integration tests against real MinIO, not mocks

---

Installation
------------

[](#installation)

```
composer require jardisadapter/filesystem
```

---

Quick Start
-----------

[](#quick-start)

### Local Filesystem

[](#local-filesystem)

```
use JardisAdapter\Filesystem\FilesystemService;

$service = new FilesystemService();
$fs = $service->local('/var/app/storage');

$fs->write('uploads/photo.jpg', $imageData);
$content = $fs->read('uploads/photo.jpg');
```

### S3-Compatible Storage

[](#s3-compatible-storage)

```
$fs = $service->s3(
    bucket: 'my-bucket',
    region: 'eu-central-1',
    key: 'AKIAEXAMPLE',
    secret: 'wJalrXUtnFEMI/K7MDENG...',
);

$fs->write('backups/dump.sql', $sqlDump);
```

### Multiple Backends

[](#multiple-backends)

```
$uploads = $service->local('/storage/uploads');
$backups = $service->s3('company-backups', 'eu-central-1', $env('AWS_KEY'), $env('AWS_SECRET'));

// Upload lokal speichern
$uploads->write('invoice-2026.pdf', $pdf);

// Backup auf S3 sichern
$backups->write('daily/invoice-2026.pdf', $pdf);
```

### Advanced Configuration

[](#advanced-configuration)

For custom permissions, symlink settings, or other advanced options — use `create()` with a config object:

```
use JardisAdapter\Filesystem\Config\LocalConfig;

$fs = $service->create(new LocalConfig(
    root: '/storage/uploads',
    filePermissions: 0600,
    dirPermissions: 0700,
    followSymlinks: false,
));
```

---

File Operations
---------------

[](#file-operations)

```
$fs->write('file.txt', 'content');
$fs->read('file.txt');                  // string
$fs->exists('file.txt');                // bool
$fs->delete('file.txt');
$fs->copy('source.txt', 'target.txt');
$fs->move('old.txt', 'new.txt');
$fs->size('file.txt');                  // int (bytes)
$fs->lastModified('file.txt');          // int (unix timestamp)
$fs->mimeType('file.txt');              // string
```

---

Stream Support
--------------

[](#stream-support)

For large files — no memory overhead:

```
// Write from stream
$stream = fopen('/tmp/video.mp4', 'rb');
$fs->writeStream('videos/intro.mp4', $stream);
fclose($stream);

// Read as stream
$stream = $fs->readStream('videos/intro.mp4');
while (!feof($stream)) {
    $chunk = fread($stream, 8192);
    // process chunk...
}
fclose($stream);
```

---

Directory Operations
--------------------

[](#directory-operations)

```
$fs->createDirectory('uploads/2026');
$fs->deleteDirectory('uploads/2025');    // recursive

foreach ($fs->listContents('uploads', recursive: true) as $item) {
    echo $item->path();           // 'uploads/photo.jpg'
    echo $item->size();           // 1048576
    echo $item->lastModified();   // 1711929600
    echo $item->isFile();         // true
    echo $item->isDirectory();    // false
}
```

---

Visibility
----------

[](#visibility)

Control file permissions (local: Unix chmod, S3: ACL):

```
$fs->setVisibility('public/logo.png', 'public');
$fs->setVisibility('private/secret.pdf', 'private');

$fs->getVisibility('public/logo.png');   // 'public'
```

---

Configuration
-------------

[](#configuration)

### LocalConfig

[](#localconfig)

```
new LocalConfig(
    root: '/var/app/storage',       // required — validated via realpath()
    filePermissions: 0644,          // new files (default: 0644)
    dirPermissions: 0755,           // new directories (default: 0755)
    followSymlinks: true,           // follow symlinks (default: true)
    publicFilePerms: 0644,          // visibility 'public' files
    privateFilePerms: 0600,         // visibility 'private' files
    publicDirPerms: 0755,           // visibility 'public' directories
    privateDirPerms: 0700,          // visibility 'private' directories
)
```

### S3Config

[](#s3config)

```
new S3Config(
    bucket: 'my-bucket',                        // required
    region: 'eu-central-1',                      // required
    key: 'AKIAEXAMPLE',                          // required
    secret: 'wJalrXUtnFEMI...',                  // required, masked in debug output
    endpoint: 'https://s3.amazonaws.com',        // default: AWS (use custom for MinIO etc.)
    prefix: 'uploads/',                          // path prefix in bucket (default: '')
)
```

The secret is protected with `#[\SensitiveParameter]` and masked in `var_dump()` / debug output.

---

Error Handling
--------------

[](#error-handling)

All exceptions implement `FilesystemExceptionInterface` — catch one, catch all:

ExceptionWhen`FileNotFoundException`File or directory does not exist`UnableToReadException`Read failure (permissions, I/O, S3 auth)`UnableToWriteException`Write failure (permissions, disk full, S3)`UnableToDeleteException`Delete failure`FilesystemException`Base — path traversal, null byte, invalid config```
use JardisAdapter\Filesystem\Exception\FileNotFoundException;
use JardisSupport\Contract\Filesystem\FilesystemExceptionInterface;

try {
    $content = $fs->read('missing.txt');
} catch (FileNotFoundException $e) {
    // file does not exist
} catch (FilesystemExceptionInterface $e) {
    // any other filesystem error
}
```

---

Architecture
------------

[](#architecture)

The user only sees `FilesystemService` + config objects. Internally, the orchestrator builds a pipeline of atomic invokable handlers — one `__invoke` per operation:

```
FilesystemService (implements FilesystemServiceInterface)
  ├── local(root): FilesystemInterface
  ├── s3(bucket, region, key, secret): FilesystemInterface
  └── create(LocalConfig|S3Config): FilesystemInterface   ← advanced

Filesystem (Orchestrator)
  │
  │  PathNormalizer — traversal + null byte protection
  │
  ├── Local:
  │   LocalFullPath (symlink containment via realpath)
  │   + 16 atomic handlers: LocalRead, LocalWrite, LocalExists, ...
  │
  └── S3:
      S3Signer (AWS Signature v4)
      S3Request (shared cURL helper)
      + 16 atomic handlers: S3Read, S3Write, S3Exists, ...

```

Each handler is an **invokable object** (`__invoke`) — independently testable, replaceable, composable. The orchestrator extracts closures via `->__invoke(...)` and stores only the closures. No handler object survives as a property.

---

Security
--------

[](#security)

- **Path traversal** — `..` segments and null bytes rejected before any I/O
- **Symlink containment** — `realpath()` check ensures resolved paths stay inside root
- **Root validation** — `LocalConfig` resolves root via `realpath()` at construction time
- **XXE prevention** — `LIBXML_NONET` on all XML parsing (S3 responses)
- **Secret masking** — `S3Config::$secret` uses `#[\SensitiveParameter]` + `__debugInfo()`
- **Bucket wipe guard** — `deleteDirectory('')` with empty prefix is rejected

---

Contracts
---------

[](#contracts)

The package implements interfaces from `jardissupport/contract`:

InterfacePurpose`FilesystemServiceInterface`Factory: `local()`, `s3()``FilesystemInterface`Full API (extends Reader + Writer)`FilesystemReaderInterface`Read-only subset — inject this for read-only contexts`FilesystemWriterInterface`Write-only subset```
// Inject read-only access
public function __construct(
    private readonly FilesystemReaderInterface $storage,
) {}
```

---

Jardis Foundation Integration
-----------------------------

[](#jardis-foundation-integration)

In a Jardis DDD project, the filesystem is available via the resource chain:

```
$uploads = $this->getResource()->filesystem()->local('/storage/uploads');

$backups = $this->getResource()->filesystem()->s3(
    bucket: $env('FS_BACKUPS_BUCKET'),
    region: $env('FS_BACKUPS_REGION'),
    key: $env('FS_BACKUPS_KEY'),
    secret: $env('FS_BACKUPS_SECRET'),
);
```

No singleton, no handler — the developer decides how many filesystem instances exist and how they are configured. The resource chain returns `FilesystemServiceInterface`.

---

Development
-----------

[](#development)

```
cp .env.example .env    # One-time setup
make install             # Install dependencies
make start               # Start MinIO (S3 integration tests)
make phpunit             # Run tests (118 tests, starts MinIO automatically)
make phpstan             # Static analysis (Level 8)
make phpcs               # Coding standards (PSR-12)
```

---

Documentation
-------------

[](#documentation)

Full documentation, guides, and API reference:

**[docs.jardis.io/en/adapter/filesystem](https://docs.jardis.io/en/adapter/filesystem)**

---

License
-------

[](#license)

[MIT License](LICENSE.md) — free for any use, including commercial.

AI-Assisted Development
-----------------------

[](#ai-assisted-development)

This package ships with a skill for Claude Code, Cursor, Continue, and Aider. Install it in your consuming project:

```
composer require --dev jardis/dev-skills
```

More details:

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance95

Actively maintained with recent releases

Popularity10

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

30d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/e07a1b668e9e01ee6d1b85de7b3be1c2513f68aae9494b2011d1592104d5daa0?d=identicon)[jardis](/maintainers/jardis)

---

Top Contributors

[![Headgent](https://avatars.githubusercontent.com/u/245725954?v=4)](https://github.com/Headgent "Headgent (7 commits)")

---

Tags

domain-driven-designfilesystemhexagonal-architecturejardislocal-storagephps3streamfilesystemcloudfilestoragelocalDomain Driven DesignHeadgentjardis

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/jardisadapter-filesystem/health.svg)

```
[![Health](https://phpackages.com/badges/jardisadapter-filesystem/health.svg)](https://phpackages.com/packages/jardisadapter-filesystem)
```

###  Alternatives

[league/flysystem

File storage abstraction for PHP

13.6k679.9M2.5k](/packages/league-flysystem)[league/flysystem-aws-s3-v3

AWS S3 filesystem adapter for Flysystem.

1.7k285.7M1.0k](/packages/league-flysystem-aws-s3-v3)[league/flysystem-local

Local filesystem adapter for Flysystem.

225267.1M81](/packages/league-flysystem-local)[league/flysystem-async-aws-s3

AsyncAws S3 filesystem adapter for Flysystem.

2812.1M44](/packages/league-flysystem-async-aws-s3)[gliterd/backblaze-b2

PHP SDK for working with backblaze B2 cloud storage.

83278.7k8](/packages/gliterd-backblaze-b2)[cwhite92/b2-sdk-php

A SDK for working with B2 cloud storage.

74154.8k2](/packages/cwhite92-b2-sdk-php)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
