Skip to content

Serving Stored Files ​

since 3.0

Every 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:

php
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:

RuleWhat the response does
The stored type, nothing elseContent-Type is your mime; X-Content-Type-Options: nosniff holds the browser to it
Shown in place only where nothing can runPDF, 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 nameContent-Disposition carries the name per RFC 6266 (filename*, umlauts included) and an ASCII fallback
Every read reaches the serverCache-Control: private, no-cache with the hash as ETag: an unchanged file answers 304, and the check runs on every read
Large filesfrom a local disk the response serves Range requests — resume a download, page through a PDF
Cloud storageon 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 ​