Fasáda MediaLibrary
Všetko ide cez jednu fasádu, RoundlyConsulting\MediaLibrary\Facades\MediaLibrary. Ploché metódy add* začínajú globálne pridanie, for($model) obmedzí pridávanie a čítanie na jedného vlastníka, ploché slovesá pracujú s médiom, ktoré im odovzdáte, variants($media) združuje operácie s variantmi a remote() sťahuje URL od používateľov cez tú istú ochranu, akú používa addFromUrl():
use RoundlyConsulting\MediaLibrary\Facades\MediaLibrary;
// Owner-scoped: add, read and delete one model's media
MediaLibrary::for($user)->add($request->file('avatar'))->toBucket('avatar');
MediaLibrary::for($user)->first('avatar');
// Global (no owner)
MediaLibrary::add(storage_path('brand/logo.svg'))->toBucket('brand');
// Flat verbs act on the media you hand them
MediaLibrary::move($media, to: $otherUser, bucket: 'gallery');
MediaLibrary::replace($media, $request->file('avatar'));
MediaLibrary::delete($media);
// Variant operations for one media
MediaLibrary::variants($media)->missing();
// Guarded fetches of user-supplied URLs — nothing stored
MediaLibrary::remote()->fetch($url);Ploché metódy
| Metóda | Vracia | Čo robí |
|---|---|---|
add($file), addFromUrl(), addFromDisk(), addFromString(), addFromBase64(), addFromStream() | PendingFileAdd | Začne globálne pridanie (bez vlastníka). |
draft($file) | PendingFileAdd | Začne globálne pridanie draftu. |
for($model) | ModelMedia | Handle viazaný na vlastníka — pridanie, drafty, attach, čítanie, clear a delete pre jeden model. |
attach($media, to: ?Model, bucket:) | Media | Pripojí referenciou (bez kopírovania bajtov); to: null = globálna skupina. |
bindDraft($token, to: $model, bucket:) | Media | Naviaže draft na vlastníka — uplatnia sa pravidlá, úložisko, pravidlo jedného súboru aj varianty skupiny; token sa naviaže len raz. |
move($media, to: ?Model, bucket:, disk:) | Media | Presunie k inému vlastníkovi a/alebo na iný disk. |
moveToDisk($media, $disk) / moveVariantsToDisk($media, $disk) | Media | Presunie súbory, alebo len varianty, na iný disk. |
copy($media, to: ?Model, bucket:, disk:) | Media | Skopíruje do nového riadku (nové uuid). |
replace($media, $file) | Media | Nové bajty, rovnaké id/uuid (aj URL, pokiaľ starý súbor nie je zdieľaný a nový súbor je rovnakého typu). |
delete($media) | void | Zmaže riadok aj súbory (strážené refcountom). |
variants($media) | MediaVariants | all(), generated(), missing(), regenerate(only:, force:). |
remote() | RemoteUrls | Strážené sťahovanie URL od používateľov — fetch(), vet(), isPrivateAddress(); nič sa neukladá. |
regenerate($media, only: [], force: false) | list<string> | Prekreslí varianty; vráti názvy vykreslených. |
pruneDrafts() | int | Zmaže expirované, nikdy nenaviazané drafty. |
rulesFor(Model::class, $bucket) | list<string> | Validačné pravidlá z definície skupiny. |
bucket($bucket) / find($uuid) / clearBucket($bucket) | Builder / ?Media / int | Globálne čítanie a vyprázdnenie. |
Handle for($model)
MediaLibrary::for($model) vráti handle ModelMedia. Jeho rozsah je bezpečnostná hranica: find() vráti null a delete() vyhodí MediaDoesNotBelongToModel pre médium, ktoré je globálne alebo patrí inému modelu — kontrolér teda môže bezpečne volať MediaLibrary::for($request->user())->find($uuid).
| Metóda | Vracia | Čo robí |
|---|---|---|
add(), addFromRequest(), addFromUrl(), addFromDisk(), addFromString(), addFromBase64(), addFromStream() | PendingFileAdd | Pridanie pre vlastníka; addFromRequest() vyhodí FileDoesNotExist, ak kľúč nemá upload. |
bindDraft($token, $bucket) | Media | Naviaže draft na tento model. |
attach($media, $bucket) | Media | Pripojí existujúce médium referenciou. |
get($bucket) / first($bucket) / has($bucket) | Collection / ?Media / bool | Čítanie skupiny v poradí. |
find($uuid) | ?Media | Len médiá, ktoré tento model vlastní — inak null. |
url($bucket, $variant) / temporaryUrl($bucket, $variant, $expiry) | string | URL prvého média, inak fallback skupiny, inak prázdny reťazec. |
clear($bucket) | int | Zmaže všetky médiá v skupine. |
delete($media) | void | Pre globálne alebo cudzie médium vyhodí MediaDoesNotBelongToModel. |
Presun, kópia a mazanie
Médium aj jeho varianty sa dajú presunúť na iný disk alebo k inému modelu a skupine — aj z modelu do globálnej skupiny a späť. Presun medzi diskami najprv streamuje bajty na cieľový disk — ak disk zápis odmietne, vyhodí FileCannotBeWritten skôr, než sa zmení riadok či zdrojový súbor —, potom beží zápis do databázy v transakcii a zdrojové súbory sa odstránia až po potvrdení vonkajšej transakcie. Keď teda vrátite späť vlastnú DB::transaction(), bajty, na ktoré ukazuje obnovený riadok, zostanú zachované:
// Move the original (and same-disk variants) to another disk.
MediaLibrary::moveToDisk($media, 'cold');
// Move only the variant files to another disk; the original stays put.
MediaLibrary::moveVariantsToDisk($media, 'hot');
// Re-home to a different model and bucket (deletes the source files).
MediaLibrary::move($media, to: $otherUser, bucket: 'gallery');
// Move a model's media to global storage (no owner).
MediaLibrary::move($media, to: null, bucket: 'brand');
// Copy instead of move: a new row with a fresh UUID, source preserved.
$copy = MediaLibrary::copy($media, to: $otherUser, bucket: 'gallery', disk: 'cold');
// Permanently delete the row and its files (refcount-guarded).
MediaLibrary::delete($media);
// The same verbs on the model:
$media->moveToDisk('cold');
$media->move($otherUser, 'gallery');
$copy = $media->copy($otherUser, 'gallery', 'cold');
$media->deleteWithFiles();Presun či kópia k inému vlastníkovi alebo do inej skupiny vyžaduje uloženého vlastníka (MediaOwnerNotSaved) a uplatní cieľovú skupinu: najprv overí jej pravidlá prijatia (FileUnacceptableForBucket), vynúti jej pravidlo jedného súboru, varianty, ktoré nedefinuje, zahodí a tie, ktoré definuje a médiu chýbajú, vygeneruje. Varianty definované v oboch skupinách sa ponechajú — ak sa líšia, spustite MediaLibrary::regenerate($media, force: true). Presun zachová disk a viditeľnosť média, pokiaľ nezadáte disk:. Presun draftu k vlastníkovi alebo do globálnej skupiny ho uzavrie rovnako ako naviazanie — token prestane platiť a pruneDrafts() sa ho nedotkne. moveToDisk() vlastníka nikdy nemení: vlastníka, skupinu aj varianty ponechá presne tak, ako sú uložené, aj keď je vlastník soft-deleted alebo už neexistuje. Na tom istom disku kópia odkazuje na uložený originál zdroja (bez kopírovania bajtov, strážené refcountom); na inom disku sa originál skopíruje. Kópia na tom istom disku sa uloží pod zámkom riadku zdroja, takže súbežné zmazanie zdroja na ňu počká; ak je zdroj dovtedy už zmazaný, vyhodí sa ModelNotFoundException — riadok nevznikne a varianty, ktoré sa preň skopírovali, sa odstránia.
Presun spustí MediaHasBeenMoved, kópia spustí MediaHasBeenAdded pre nový riadok. Bežné Eloquent delete() je soft delete a súbory ponechá, aby obnovenie nič nestratilo — odstráni ich len MediaLibrary::delete() (alebo deleteWithFiles(), prípadne force delete), ktoré spustí MediaHasBeenDeleted. V transakcii sa súbory odstránia až po potvrdení vonkajšej transakcie a pri jej vrátení zostanú, takže riadok obnovený rollbackom má originál aj varianty stále k dispozícii.
Drafty — nahratie skôr, než model existuje
Draft je bežný riadok média bez vlastníka, s vygenerovaným draft_token a platnosťou (media.drafts.ttl, predvolene 24 hodín). Rozmery a náhľady sa vypočítajú už pri nahratí. Skupina je známa až pri naviazaní, preto sa uplatní práve vtedy: overia sa jej pravidlá prijatia (FileUnacceptableForBucket nechá draft nenaviazaný), originál sa presunie na disk a do viditeľnosti, ktoré skupina deklaruje, predošlé médium skupiny so singleFile() sa zmaže a vygenerujú sa varianty skupiny:
use RoundlyConsulting\MediaLibrary\Facades\MediaLibrary;
// Upload step — no model yet. Hand the token back to the client.
$draft = MediaLibrary::draft($request->file('avatar'))->toBucket('avatar');
$token = $draft->draft_token;
// When the form is submitted and the model is saved, bind by token:
MediaLibrary::for($user)->bindDraft($token, 'avatar'); // sets the owner, clears the token
// …or the flat verb, or the trait shorthand:
MediaLibrary::bindDraft($token, to: $user, bucket: 'avatar');
$user->attachDraftMedia($token, 'avatar');
// Delete expired, never-bound drafts (or schedule media:prune-drafts):
MediaLibrary::pruneDrafts(); // returns the countNaviazanie vyhodí DraftMediaNotFound (neznámy, už použitý alebo presunutý token — token sa naviaže len raz, aj pri súbežných volaniach), DraftMediaExpired (po uplynutí platnosti), MediaOwnerNotSaved (vlastník, ktorý ešte nebol uložený) alebo FileCannotBeWritten (disk odmietol zápis — draft zostane nezmenený); úspešné naviazanie spustí DraftMediaHasBeenBound. Nenaviazané drafty nie sú globálne médiá: MediaLibrary::bucket(), clearBucket() ani media:clear "" sa ich nedotknú a $hidden modelu drží draft_token mimo serializovaného výstupu — čítajte ho ako vlastnosť.
Token draftu sa do stack trace nedostane. Na celej ceste naviazania je #[SensitiveParameter] — v bindDraft() na fasáde aj na handle for(), v attachDraftMedia(), BindDraftMediaAction, továrňach DraftMediaNotFound / DraftMediaExpired aj vo fake — a fasáda používa RedactsSensitiveArguments z Roundly package toolkitu, takže aj rámec MediaLibrary::bindDraft($token, $model) ukáže SensitiveParameterValue, zatiaľ čo ostatné argumenty zostanú viditeľné. Je to dôležité, pretože naviazanie, ktoré skupina odmietne, nechá draft nenaviazaný a jeho token stále platný — pri vypnutom zend.exception_ignore_args by ho inak zobrazil každý error tracker, ktorý zbiera argumenty rámcov.
Pripojenie referenciou a náhrada na mieste
attach() pripojí existujúce médium k modelu bez opätovného nahrávania — nový riadok zdieľa ten istý uložený originál, neskopíruje sa ani bajt a varianty cieľovej skupiny sa vygenerujú nanovo. Platia pravidlá prijatia aj pravidlo jedného súboru cieľovej skupiny a náhrada zdroja nikdy nezmení bajty pripojeného riadku. Nový riadok sa uloží pod zámkom riadku zdroja, takže súbežné zmazanie zdroja naň počká; ak je zdroj dovtedy už zmazaný, vyhodí sa ModelNotFoundException a riadok nevznikne. replace() vymení bajty so zachovaním id aj uuid; nový súbor musí spĺňať skupinu vlastníka rovnako ako pri pridaní, metadáta a varianty sa prepočítajú a spustí sa MediaHasBeenReplaced:
$logo = MediaLibrary::bucket('brand')->first();
MediaLibrary::for($user)->attach($logo, 'avatar'); // new row, same file, no copy
MediaLibrary::attach($logo, bucket: 'shared'); // …or into another global bucket
$user->attachMedia($logo, 'avatar'); // the trait shorthand
MediaLibrary::replace($media, $request->file('avatar')); // same id/uuid, new bytes; same URL for the same type
$media->replace($request->file('avatar')); // the model shorthandURL zostane rovnaká s tromi výnimkami: keď sa nové bajty deduplikujú na iný uložený súbor; keď starý originál zdieľajú iné riadky (deduplikovaný upload, attach() či kópia na tom istom disku) — vtedy zostane pre ne nedotknutý a nové bajty pôjdu na vlastnú cestu tohto média; a keď je nový súbor iného typu — uložený súbor sa premenuje, aby mu zodpovedal, takže z landscape.jpg nahradeného PNG sa stane landscape.png. Po replace() preto URL znova načítajte cez getUrl() namiesto toho, aby ste pracovali s pôvodnou.
Náhrada beží v jednej transakcii (vo vašej ako savepoint), ktorá najprv uzamkne riadok média (SELECT … FOR UPDATE) a až potom uzamykajúcim čítaním zistí, či súbor zdieľajú ďalšie riadky. Deduplikované pridanie na toto médium sa preto buď už potvrdilo — náhrada ho uvidí a zapíše na voľnú cestu —, alebo počká, kým náhrada skončí; na nových bajtoch nikdy neskončí. Náhrada riadku, ktorý už neexistuje, vyhodí ModelNotFoundException. Kontroly platia pri predvolenej izolácii READ COMMITTED v PostgreSQL a v MySQL pri ľubovoľnej úrovni; hostiteľská transakcia s REPEATABLE READ v PostgreSQL však môže skryť riadok, ktorý začal súbor zdieľať až po vzniku jej snapshotu.
Rollback nájde staré bajty riadku tam, kde boli: pred zápisom na mieste sa starý súbor odloží bokom, pri rollbacku sa vráti a pri potvrdení zahodí; súbor zapísaný na novú cestu sa pri rollbacku zmaže. Zápis, ktorý disk odmietne — alebo odloženie kópie, ktoré odmietne —, vyhodí FileCannotBeWritten a riadok sa neuloží. Starý originál sa odstráni, len keď naň už neukazuje žiadny iný riadok, vrátane soft-deleted, a až po potvrdení vonkajšej transakcie.
Validačné pravidlá zo skupiny
Obmedzenia skupiny deklarujete raz a použijete v ľubovoľnom FormRequeste — zmeníte skupinu a pravidlá sa prispôsobia. Pravidlo vznikne len z deklarovaného obmedzenia (max je presný limit v kilobajtoch — maxFileSize(1000) vytvorí max:0.9765625, nikdy nezaokrúhlené nahor); skupina bez maxFileSize() použije media.max_file_size:
$this->addMediaBucket('avatar')
->acceptsMimeTypes(['image/jpeg', 'image/png', 'image/webp'])
->maxFileSize(5 * 1024 * 1024) // bytes
->minDimensions(100, 100)
->maxDimensions(4096, 4096);
// In a FormRequest:
public function rules(): array
{
return [
'avatar' => MediaLibrary::rulesFor(User::class, 'avatar'),
// ['file', 'mimetypes:image/jpeg,image/png,image/webp', 'max:5120',
// 'media_dimensions:min_width=100,min_height=100,max_width=4096,max_height=4096']
];
}Z limitov rozmerov vznikne vlastné pravidlo balíka media_dimensions namiesto dimensions od Laravelu. Meria presne tak ako pridanie — zobrazenú veľkosť po uplatnení EXIF orientácie; obrázok, ktorého rozmery sa nedajú prečítať (SVG), neprejde a súbor, ktorý obrázkom nie je, prejde — takže pravidlá nikdy nepustia súbor, ktorý by pridanie potom odmietlo. Hlásenie berie z vášho riadku validation.media_dimensions, ak ho pridáte, inak z validation.dimensions. Pre URL, ktorú zadá používateľ — odkaz, ktorý aplikácia stiahne, alebo URL pre addFromUrl() — použite namiesto toho pravidlo SafeRemoteUrl (pozrite Vzdialené URL).
Metódy modelu Media — move(), copy(), moveToDisk(), moveVariantsToDisk(), replace() a deleteWithFiles() — aj metódy traitu InteractsWithMedia sú len skratky, ktoré volajú tie isté slovesá manažéra. Všetko uvedené, vrátane MediaLibrary::fake(), teda platí aj pre ne.
Prejavte lásku k open source
Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.
Ďalšie spôsoby podpory vrátane kryptomienOdoslaním daru súhlasíte s našimi podmienkami prijímania darov.
Chcete to zabudovať do svojho produktu?
Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.