Serving Stored Files
since 3.0Every stored file — an invoice's PDF, an e-invoice's XML, a receipt photographed on a phone, a bank statement, a plan, a logo — is read at one address:
GET /ui5/files/{ulid}The address names the file, never an app. It sits behind the session, like the shell's assets and the help: a request without one is sent to the login.
Who may read a file: your answer
The SDK stores no files. The package that does binds one interface, and that binding is the whole authorization:
use LaravelUi5\Sdk\Files\Contracts\FileDeliveryInterface;
use LaravelUi5\Sdk\Files\DeliverableFile;
final class StoredFileDelivery implements FileDeliveryInterface
{
public function resolve(string $file): ?DeliverableFile
{
// find the file, ask the owner of its subject whether the actor may read it — or return null
return new DeliverableFile(disk: 'files', path: $row->path, mime: $row->mime,
name: $row->name, size: $row->size, sha256: $row->sha256);
}
}
// your service provider — bind, the SDK's default binds with bindIf
$this->app->bind(FileDeliveryInterface::class, StoredFileDelivery::class);null answers 404 — for a file that does not exist and for one the actor may not read alike, so the address never tells the two apart. The route names no app, so build the context of the app that owns the file's subject for the check. Without a binding of your own, the SDK's default knows no file and every request answers 404.
mime is the type you detected when the file was stored — never the extension, never the request. sha256 is the hash over the bytes, which never change.
How the bytes go out: the SDK's rules
The same rules hold for every kind of file:
| Rule | What the response does |
|---|---|
| The stored type, nothing else | Content-Type is your mime; X-Content-Type-Options: nosniff holds the browser to it |
| Shown in place only where nothing can run | PDF, PNG, JPEG, GIF and WebP are inline; everything else is downloaded — SVG and HTML never inline, since they can carry script and would run it on your domain; office files, CAD, HEIC, XML likewise |
| The real name | Content-Disposition carries the name per RFC 6266 (filename*, umlauts included) and an ASCII fallback |
| Every read reaches the server | Cache-Control: private, no-cache with the hash as ETag: an unchanged file answers 304, and the check runs on every read |
| Large files | from a local disk the response serves Range requests — resume a download, page through a PDF |
| Cloud storage | on a disk that issues temporary URLs (S3 and compatible), the response redirects to one, valid for five minutes — the bytes never pass through PHP |
Every read is recorded
The route writes each read into the report log, kind File, under the app whose context your delivery built for the check: the file's ULID as the parameter, its extension as the format, the actor and on whose behalf. One look is one row: the whole file, the range that starts it and the redirect to a cloud disk count; a 304 and the later ranges a PDF viewer fetches do not. There is nothing to call — the record is the route's. Without a context of your own (the SDK's default delivery), nothing is written, because nothing is delivered.
See also
- Routes Reference: the address among the SDK's other routes
- Table Export: the other route that hands out data as a file