Auto-gegenereerd via Reflection — blijft altijd in sync met src/.
AltTagger
Framework\Files\AI\AltTaggerProvider-onafhankelijk contract om in één AI-call:
- Per taal een korte ALT-tekst (max 1 zin) te genereren
- Maximaal 10 generieke tags voor de afbeelding op te leveren
Implementaties: {@see GroqAltTagger}. Caller bind 'm in de container
en gebruikt 'm via de Files-facade-routes.
1 public method
tag(string $imageUrl, array $languages): arrayGroqAltTagger
Framework\Files\AI\GroqAltTaggerAltTagger via de Groq API (OpenAI-compatible). Default model is
`meta-llama/llama-4-scout-17b-16e-instruct` — vision-capable.
$tagger = new GroqAltTagger($_ENV['GROQ_API_KEY']);
$r = $tagger->tag('https://example.com/foto.jpg', ['nl', 'en', 'de']);
// $r = ['alts' => ['nl' => '...', 'en' => '...', 'de' => '...'], 'tags' => [...]]
Werkt ook met `data:image/jpeg;base64,...` URL's voor lokale files —
Groq's vision endpoint accepteert beide.
__construct(string $apiKey, string $model = 'meta-llama/llama-4-scout-17b-16e-instruct', ?\TransportInterface $transport = NULL)1 public method
tag(string $imageUrl, array $languages): arrayFileQuery
Framework\Files\FileQueryFilter/sort-parameters voor `FileRepository::findInFolder()` en `::search()`.
Defaults: alfabetisch oplopend op naam, geen filter, geen limiet.
__construct(?string $q = NULL, ?\FileType $type = NULL, string $sort = 'name', string $direction = 'asc', ?int $limit = NULL, int $offset = 0)FileRecord
Framework\Files\FileRecordEén regel uit de files-tabel.
Een file is óf lokaal opgeslagen (`contentHash` set, `externalUrl` null),
óf extern (`externalUrl` set, `contentHash` null). Nooit allebei, nooit geen.
`metadata` is een vrij-vorm associative array (JSON in storage). Voor images:
dimensies; voor externals: provider/embed-id; etc.
__construct(int $id, ?int $folderId, string $name, string $mime, int $size, ?string $contentHash, ?string $externalUrl, \FileType $type, array $metadata, int $createdAt, int $updatedAt)4 public methods
isExternal(): boolisLocal(): boolurl(): stringRender-vriendelijke URL: lokaal → /files/{id}/raw, extern → externalUrl.
with(array $changes): selfFileRepository
Framework\Files\FileRepositoryStorage-contract voor file-records (DB-laag, los van de blob-bytes).
Implementaties: {@see JsonFileRepository} (default voor demo, geen DB-extensie),
later eventueel een PDO-impl voor MySQL/SQLite.
8 public methods
countInFolder(?int $folderId, \FileQuery $query): intTel files in een folder (voor pagination).
create(\FileRecord $record): \FileRecordInsert een nieuwe row. `id`/`createdAt`/`updatedAt` worden door de
repository gegenereerd; meegegeven waarden worden genegeerd.
delete(int $id): voidfind(int $id): ?\FileRecordfindByHash(string $hash): arrayfindInFolder(?int $folderId, \FileQuery $query): arraysearch(\FileQuery $query): arrayGlobale zoek over alle files (folder-onafhankelijk).
update(\FileRecord $record): \FileRecordUpdate bestaande row. Geeft de geüpdatete record terug, met nieuwe
`updatedAt`.
FileStorage
Framework\Files\FileStorageBlob-IO-laag — los van de DB-laag. Werkt content-addressed via sha256.
Twee uploads van dezelfde file produceren één blob (dedup), maar de
`FileRepository` houdt nog steeds twee aparte rows zodat hernoemen/verplaatsen
onafhankelijk werkt.
Implementaties: {@see LocalFileStorage} (default), later eventueel S3.
5 public methods
delete(string $hash): voidexists(string $hash): boolpath(string $hash): ?stringAbsoluut bestandspad voor de blob — handig voor X-Sendfile / readfile().
Null als de hash niet bestaat.
read(string $hash): ?stringLees bytes. Null als de hash niet bestaat.
write(string $hash, string $contents): voidSchrijf bytes; idempotent — als de hash al bestaat, no-op.
FileType
Framework\Files\FileTypeCategorie van een file — bepaalt rendering en filter-knoppen in de UI.
Geleid uit de mime-type bij upload of uit de URL-parser bij externals.
4 public methods
static cases(): arraystatic from(string|int $value): staticstatic fromMime(string $mime): selfstatic tryFrom(string|int $value): ?staticImage, Video, Audio, Pdf, External, BinaryFiles
Framework\Files\FilesFacade die `FileRepository` + `FolderRepository` + `FileStorage` koppelt
en de "smart" operaties levert: upload (met dedup), delete (met refcount-
blob-cleanup), externe URL toevoegen.
$files = new Files(
new JsonFileRepository($store),
new JsonFolderRepository($store),
new LocalFileStorage('/data/files'),
);
$rec = $files->upload(folderId: 1, name: 'foo.jpg', contents: $bytes, mime: 'image/jpeg');
$files->delete($rec->id);
Voor low-level operaties (`findByHash`, etc.) kun je de repos direct uit
de container halen — de facade is voor de gebruikelijke schrijfacties.
__construct(\FileRepository $files, \FolderRepository $folders, \FileStorage $storage, \FilesConfig $config = \Framework\Files\FilesConfig::__set_state(array(
'languages' =>
array (
0 => 'nl',
),
'languageLabels' =>
array (
'nl' => 'Nederlands',
),
'aiEnabled' => false,
'aiModel' => 'grok-2-vision-1212',
)))6 public methods
addExternal(?int $folderId, string $url, ?string $name = NULL, array $metadata = array (
)): \FileRecordVoeg een externe URL toe als file (YouTube, Vimeo, willekeurige PDF-URL).
Geen blob — alleen een `files`-row met `externalUrl`.
delete(int $fileId): voidVerwijder een file. Bij lokale files wordt na refcount-check de blob
van disk verwijderd als geen andere row 'm gebruikt.
deleteFolder(int $folderId): arrayVerwijder een folder + alle descendants + alle files daarin (met
refcount-blob-cleanup per file).
move(int $fileId, ?int $newFolderId): \FileRecordrename(int $fileId, string $newName): \FileRecordupload(?int $folderId, string $name, string $contents, string $mime, array $metadata = array (
)): \FileRecordUpload nieuwe file. Dedup-aware: als een blob met dezelfde hash al
bestaat, wordt 'm hergebruikt — alleen een nieuwe `files`-row.
FilesConfig
Framework\Files\FilesConfigConfiguratie die de caller meegeeft aan {@see Files}. Bepaalt hoe de
front-end zich gedraagt — talen voor multi-lingual ALT-tekst, of de AI-
suggestie zichtbaar is, etc.
$config = new FilesConfig(
languages: ['nl', 'en', 'de'],
languageLabels: ['nl' => 'Nederlands', 'en' => 'English', 'de' => 'Deutsch'],
aiEnabled: true,
);
Defaults zijn NL-only, geen AI. De waarden worden naar de browser gestuurd
via `GET /api/files/config` en gebruikt door file-browser.js om de juiste
inputs te renderen.
__construct(array $languages = array (
0 => 'nl',
), array $languageLabels = array (
'nl' => 'Nederlands',
), bool $aiEnabled = false, string $aiModel = 'grok-2-vision-1212')1 public method
toArray(): arrayFolderRecord
Framework\Files\FolderRecordEén regel uit de folders-tabel.
`path` is gedenormaliseerd ("/foo/bar/baz") voor snelle breadcrumb-render
en zoek-zonder-recursie. Wordt onderhouden door de repository bij rename/move.
Root = `parentId === null && path === '/'` (of equivalent).
__construct(int $id, ?int $parentId, string $name, string $path, int $createdAt, array $metadata = array (
))2 public methods
isRoot(): boolwithMetadata(array $metadata): selfFolderRepository
Framework\Files\FolderRepositoryStorage-contract voor folder-records.
9 public methods
all(): arrayAlle folders plat — caller bouwt de tree client-side via `parentId`.
children(?int $parentId): arrayDirect kinderen van een folder (geen recursie).
create(?int $parentId, string $name): \FolderRecordMaak een nieuwe folder. `path` wordt gedenormaliseerd uit parent + name.
Faalt als naam al bestaat onder dezelfde parent.
delete(int $id): arrayVerwijder folder + alle descendants. Caller is verantwoordelijk voor
het verwijderen van de bijbehorende files (om refcount-blob-cleanup
juist te doen).
find(int $id): ?\FolderRecordfindByPath(string $path): ?\FolderRecordmove(int $id, ?int $newParentId): \FolderRecordVerplaats — werkt ook descendants bij (path-prefix).
`$newParentId === null` = naar root.
rename(int $id, string $newName): \FolderRecordHernoem — werkt ook descendants bij (path-prefix).
update(\FolderRecord $record): \FolderRecordUpdate een folder. Voor v1 worden alleen mutaties op `metadata`
toegepast — `name`/`parentId`/`path` gaan via `rename()`/`move()`.
JsonFileRepository
Framework\Files\JsonFileRepositoryJSON-backed FileRepository.
__construct(\JsonStore $store)8 public methods
countInFolder(?int $folderId, \FileQuery $query): intcreate(\FileRecord $record): \FileRecorddelete(int $id): voidfind(int $id): ?\FileRecordfindByHash(string $hash): arrayfindInFolder(?int $folderId, \FileQuery $query): arraysearch(\FileQuery $query): arrayupdate(\FileRecord $record): \FileRecordJsonFolderRepository
Framework\Files\JsonFolderRepositoryJSON-backed FolderRepository.
__construct(\JsonStore $store)9 public methods
all(): arraychildren(?int $parentId): arraycreate(?int $parentId, string $name): \FolderRecorddelete(int $id): arrayfind(int $id): ?\FolderRecordfindByPath(string $path): ?\FolderRecordmove(int $id, ?int $newParentId): \FolderRecordrename(int $id, string $newName): \FolderRecordupdate(\FolderRecord $record): \FolderRecordJsonStore
Framework\Files\JsonStoreGedeeld read/write/atomic-update mechanisme voor de JSON-repos.
Eén JSON-file met `{folders: [...], files: [...], next_id: int}`. Alle writes
gaan via `transaction()` die file-lock + read-modify-write doet zodat parallelle
processen elkaar niet overschrijven.
$store = new JsonStore('/path/to/files.json');
$store->transaction(function (array $data) {
$data['files'][] = [...];
return $data;
});
Niet bedoeld voor productie-load met veel concurrent writers — voor 100k+
files of veel parallel uploads: gebruik straks de PDO-impl. Voor de demo
en kleine apps prima.
__construct(string $path)2 public methods
read(): arrayLees de hele store. Bij corrupte file wordt een lege store geretourneerd
(caller kan dan opnieuw beginnen).
transaction(callable $fn): ?mixedAtomaire read-modify-write. Callback krijgt de huidige data, returnt
de nieuwe data; ertussen blijft de file ge-flock'd.
LocalFileStorage
Framework\Files\LocalFileStorageDisk-backed FileStorage. Layout:
<baseDir>/<sha256[0:2]>/<sha256>.bin
De `[0:2]`-sub-dir voorkomt dat we duizend files in één directory plempen.
Sha256 is exact 64 hex chars; we accepteren niks anders (anti-traversal).
__construct(string $baseDir)5 public methods
delete(string $hash): voidexists(string $hash): boolpath(string $hash): ?stringread(string $hash): ?stringwrite(string $hash, string $contents): void