heyjorgedev / sqlite-flysystem-driver
A Flysystem storage driver backed by SQLite.
Package info
github.com/heyjorgedev/sqlite-flysystem-driver
pkg:composer/heyjorgedev/sqlite-flysystem-driver
Requires
- php: ^8.2
- league/flysystem: ^3.0
Requires (Dev)
- league/flysystem-adapter-test-utilities: ^3.30
- orchestra/testbench: ^9.0 || ^10.0
- phpunit/phpunit: ^11.0
Suggests
- ext-sqlite3: Enables O(n) incremental blob streaming on file databases (streamContents/readStream); without it the adapter falls back to windowed substr() reads.
- illuminate/support: ^10.0|^11.0|^12.0 — the Laravel disk driver registers itself automatically when present.
Provides
None
Conflicts
None
Replaces
None
README
A Flysystem 3 storage adapter that keeps your files inside a single SQLite database, with an optional Laravel disk driver that can serve public files over HTTP.
It works well when you want uploads to live next to the rest of your app's data: one file to back up, copy or ship, with no object storage or shared volume to set up.
- Passes Flysystem's official adapter conformance suite.
- Supports checksums (
ChecksumProvider) and public URLs (PublicUrlGenerator). - Metadata reads,
fileExists()and listings never load file contents, so they cost the same for a 1 KB file and a 1 GB file. - Streams byte ranges without loading the whole file into memory.
- Laravel driver with an optional static-file route that sends
ETag/Last-Modifiedand handles304s andRangerequests.
Requirements
- PHP 8.2+ with
pdo_sqlite league/flysystem^3.7- Optional:
ext-sqlite3, which enables incremental blob streaming (see Streaming) - Optional: Laravel 12 or 13 for the disk driver
Installation
composer require heyjorgedev/sqlite-flysystem-driver
Usage with Flysystem
use HeyJorgeDev\SqliteFlysystem\SqliteAdapter; use League\Flysystem\Filesystem; $filesystem = new Filesystem(new SqliteAdapter(__DIR__ . '/storage/files.sqlite')); $filesystem->write('avatars/jorge.png', $contents); $filesystem->read('avatars/jorge.png'); $filesystem->listContents('avatars', deep: true);
The schema is created on first use. The constructor takes:
| Argument | Default | Description |
|---|---|---|
$connection |
required | A database file path, ':memory:', or an existing PDO instance. |
$table |
'files' |
Table to store files in, so several adapters can share one database. |
$mimeTypeDetector |
FinfoMimeTypeDetector |
Any League\MimeTypeDetection\MimeTypeDetector. |
$urlPrefix |
null |
Enables publicUrl() (e.g. https://example.com/files). |
Passing a file path vs a PDO
When you pass a path, the adapter owns the connection and tunes it for file storage. It always sets busy_timeout, and for file databases it also enables WAL with synchronous=NORMAL, memory-maps up to 256 MB, and uses 16 KB pages when it creates the file.
When you pass a PDO, the adapter leaves its pragmas alone. Use this to share a connection your app already manages, or for an in-memory database in tests:
$adapter = new SqliteAdapter(new PDO('sqlite::memory:'));
An in-memory database exists only for the lifetime of its connection.
Streaming
readStream() and streamContents() read files in chunks instead of loading them whole:
// Yields up to 64 KB at a time, starting at byte 1024, for 1 MB total. foreach ($adapter->streamContents('videos/intro.mp4', 65536, 1024, 1048576) as $chunk) { echo $chunk; }
On file databases with ext-sqlite3 installed, streaming uses SQLite's incremental blob API through a separate read-only connection, so memory use and time stay proportional to the bytes you read. Without it (or for in-memory databases), the adapter falls back to reading 4 MB windows, which is slower for very large files.
Usage with Laravel
The service provider is auto-discovered. Add a disk to config/filesystems.php:
'disks' => [ 'sqlite' => [ 'driver' => 'sqlite-flysystem', 'database' => storage_path('files.sqlite'), // default: storage_path('flysystem.sqlite') 'table' => 'files', // default: 'files' ], ],
Storage::disk('sqlite')->put('reports/q3.pdf', $pdf);
Serving public files
Set serve to have the package register a GET route that streams public files straight from the database:
'sqlite' => [ 'driver' => 'sqlite-flysystem', 'database' => storage_path('files.sqlite'), 'serve' => 'files', // GET /files/{path} 'serve_cache_control' => 'public, max-age=86400', // default: 'public, max-age=3600' 'serve_middleware' => [], // default: none ],
Storage::disk('sqlite')->url('avatars/jorge.png') then returns https://your-app.test/files/avatars/jorge.png.
The route behaves like a static file server:
- Files with
privatevisibility return403; missing paths and directories return404. Files written without a visibility are public. - Responses carry an
ETagandLast-Modified, and conditional requests (If-None-Match,If-Modified-Since) get a304. - Single byte ranges (
Range: bytes=0-1023,bytes=1024-,bytes=-500) return206, so video seeking and download resumption work.If-Rangeis respected. - No middleware runs by default, so serving files never starts a session.
Public URLs without the route
If something else serves the files (a CDN, a reverse proxy, your own controller), set url_prefix to make Storage::url() work without registering a route:
'sqlite' => [ 'driver' => 'sqlite-flysystem', 'url_prefix' => 'https://cdn.example.com/files', ],
The prefix comes from url_prefix, then url, then the serve route, in that order. The result is also written into the disk's url config key, so packages that check it (Statamic asset containers, for example) see the disk as public.
Storage model
Each file and each directory is one row in the table. Writing a file creates its parent directory rows automatically. File contents are stored as a BLOB in the last column, so reading metadata never touches the file data, even for large files.
To inspect or back up the store, open the database with any SQLite tool:
sqlite3 storage/files.sqlite "SELECT path, file_size, mime_type FROM files WHERE is_dir = 0"
Testing
composer test
CI runs the suite on PHP 8.2–8.5 against Laravel 12 and 13, with both the lowest and the latest allowed dependencies, plus one job without ext-sqlite3 to cover the fallback streaming path.
License
MIT