Перейти до вмісту
en English

Версія v1.6.5 Попередні версії: документація на GitHub

Файли й зображення

На цій сторінці

Поля File і Image завантажують файли на диск Laravel (config/filesystems.php) і зберігають шлях у колонці моделі. Image показує мініатюру в списку, попередній перегляд на сторінці запису й у формі. File показує назву файлу з посиланням. Обидва поля працюють з одним файлом або з кількома (multiple()), з публічними й приватними дисками, зокрема з S3.

Додаткових залежностей Composer чи npm не потрібно.

Для моделей зі spatie/laravel-medialibrary (колекції, перетворення, галереї) встановіть окреме доповнення maxcxam/larsadmin-medialibrary (1.0, потребує Larsadmin 1.6+). Воно додає поля Media і MediaGallery й описане у власному посібнику, vendor/maxcxam/larsadmin-medialibrary/docs/guide/medialibrary.md.

Швидкий старт

Публічне зображення

Міграція: звичайна рядкова колонка для шляху.

$table->string('cover')->nullable();

Entity:

use Maxcxam\Larsadmin\Fields\Image;

Image::make('Обкладинка', 'cover')->disk('public'),

Диск public має 'visibility' => 'public', тож файл віддає вебсервер за адресою Storage::disk('public')->url($path). Для цього потрібне посилання public/storage:

php artisan storage:link

Storage::url() дає абсолютну адресу з url диска, у стандартному конфігу це APP_URL.'/storage'. Якщо панель відкривається з іншого хоста, ніж APP_URL (наприклад, 127.0.0.1:8000 замість localhost), зображення не завантажаться. Задайте правильний APP_URL або filesystems.disks.public.url.

Приватний файл

$table->string('contract')->nullable();
$table->string('contract_name')->nullable();
use Maxcxam\Larsadmin\Fields\File;

File::make('Договір', 'contract')
    ->types(['pdf'])
    ->maxSize(5120)                    // КБ
    ->storeOriginalName('contract_name'),

Диск не вказано, тож береться larsadmin.files.disk, за замовчуванням local (у Laravel 11+ це storage/app/private). Такий файл недоступний напряму: панель видає підписане посилання з обмеженим часом дії і перед кожним завантаженням знову перевіряє права користувача (див. Приватні файли й підписані посилання).

Кілька файлів

$table->json('attachments')->nullable();
// app/Models/Post.php
protected function casts(): array
{
    return ['attachments' => 'array'];
}
File::make('Вкладення', 'attachments')
    ->multiple()
    ->maxFiles(5)
    ->types(['pdf', 'docx', 'xlsx']),

Користувач може додати файли кнопкою «Обрати файли» або перетягнути їх у форму, прибрати окремі файли й повернути прибрані до збереження. Нові файли додаються після наявних. Змінювати порядок файлів у 1.1 не можна.

Приклад: допис з обкладинкою, вкладеннями й приватним документом

// міграція
$table->string('cover')->nullable();
$table->string('cover_name')->nullable();
$table->json('attachments')->nullable();
$table->string('contract')->nullable();
$table->string('contract_name')->nullable();
use Maxcxam\Larsadmin\Fields\File;
use Maxcxam\Larsadmin\Fields\Image;
use Maxcxam\Larsadmin\Layout\Panel;

public function fields(Request $request): array
{
    return [
        // Публічний диск: файл віддає вебсервер через storage:link.
        Image::make('Обкладинка', 'cover')->disk('public')->storeOriginalName('cover_name')->maxSize(2048),
        Text::make('Заголовок', 'title')->rules('required', 'max:255'),
        Panel::make('Файли', [
            // Кілька файлів в одній JSON-колонці на приватному диску local (за замовчуванням).
            File::make('Вкладення', 'attachments')->multiple()->maxFiles(5)->maxSize(5120)
                ->types(['pdf', 'docx', 'xlsx', 'txt', 'zip', 'jpg', 'png']),
            // Приватний документ лише для першого користувача.
            File::make('Внутрішній документ', 'contract')->private()->types(['pdf', 'docx'])
                ->storeOriginalName('contract_name')
                ->hideFromIndex()
                ->canSee(fn (Request $request) => $request->user()?->getKey() === 1),
        ]),
    ];
}

У моделі attachments має cast array. Посилання на contract бачить і може відкрити лише користувач, для якого canSee() повертає true: для інших маршрут файлу відповідає 404.

Диски й видимість

Що Як визначається
Диск ->disk('s3'), інакше larsadmin.files.disk (за замовчуванням local)
Публічний чи приватний ->public() або ->private(); якщо не викликано, публічним вважається диск з 'visibility' => 'public' у config/filesystems.php
Каталог ->path(...), інакше {files.directory}/{uriKey Entity}/{атрибут}, наприклад larsadmin/posts/cover

Невідомий диск (немає в filesystems.disks) кидає InvalidArgumentException, щойно поле прив'язується до Entity. Так само поле з ->public() на диску з драйвером local без ключа url (наприклад, стандартний local): публічного посилання на такий файл немає. Використайте диск public або ->private().

Як будуються посилання:

Видимість Драйвер диска Посилання
публічний будь-який Storage::disk()->url($path), без терміну дії
приватний не local, підтримує тимчасові URL (S3 і сумісні) temporaryUrl() диска з терміном files.url_ttl
приватний local (зокрема з serve => true) або без тимчасових URL маршрут панелі з відносним підписом і терміном files.url_ttl

Файл записується з видимістю public або private відповідно до поля.

S3

Image::make('Фото', 'photo')->disk('s3'),             // приватно: тимчасові посилання S3
Image::make('Банер', 'banner')->disk('s3')->public(), // публічно: Storage::url()

Для приватних файлів S3 панель підписує посилання локально, без запиту до S3. У посиланні задаються ResponseContentType, ResponseContentDisposition і ResponseCacheControl: растрові зображення відкриваються в браузері, решта файлів завантажується як application/octet-stream і не кешується (див. Кешування).

Тимчасове посилання S3 перевіряється лише в момент видачі (користувач мав право бачити запис і поле). Після цього його обмежує тільки термін дії files.url_ttl. Якщо доступ треба перевіряти під час кожного завантаження, використовуйте диск з драйвером local.

Каталог

File::make('Звіт', 'report')->path('reports'),
File::make('Звіт', 'report')->path(fn (Request $request, Model $record) => 'reports/'.now()->format('Y/m')),

Каталог має бути відносним і складатися з сегментів [A-Za-z0-9._-], без . і ... Інакше InvalidArgumentException: для рядка одразу, для замикання під час збереження. Під час створення запис у замиканні ще не збережений, тож ключа в нього немає.

Формат зберігання

Ім'я файлу на диску завжди генерується: 40 випадкових символів і розширення, визначене за вмістом файлу (bin, якщо тип невідомий). Ім'я з комп'ютера користувача на диску не використовується.

Один файл. Колонка містить шлях відносно кореня диска:

larsadmin/posts/cover/Xk3fq…40 символів….jpg

Розмір файлу не зберігається (розмір і тип зберігає лише multiple()), тип визначається за розширенням. Оригінальна назва теж не зберігається: без storeOriginalName() файл показується й завантажується під назвою поля з розширенням, наприклад Договір.pdf. Тому для одного файлу чи зображення рекомендуємо зберігати оригінальну назву в окремій колонці:

File::make('Договір', 'contract')->storeOriginalName('contract_name'),

Назва очищується: лише останній сегмент шляху, без керівних і форматувальних символів Unicode (зокрема перемикачів напрямку тексту й символів нульової ширини, якими підробляють розширення) і "<>:*?|, не довше 200 символів (розширення зберігається). storeOriginalName() разом з multiple() кидає LogicException.

Кілька файлів. Колонка містить JSON-масив об'єктів:

[{"path":"larsadmin/posts/attachments/Q9…40.pdf","name":"Звіт за жовтень.pdf","size":48213,"mime":"application/pdf"}]

size і mime визначає сервер (getSize(), fileinfo), а не браузер. Рекомендуємо колонку json (або text) і cast array. Якщо модель приводить колонку до array, json, object, collection, їх encrypted: варіантів, AsArrayObject чи AsCollection, поле записує PHP-масив, інакше JSON-рядок.

Читання толерантне: null і '' означають «файлів немає», список рядків сприймається як список шляхів, записи без коректного шляху пропускаються. Запис без назви показується як {назва поля} (N).{розширення}, де N його позиція. Коректний шлях непорожній, відносний, без .., \, : і керівних символів. Некоректні записи не показуються, не віддаються й не видаляються.

Валідація й обмеження

Обмеження Як задати За замовчуванням
Типи ->types(['pdf', 'docx']) File: будь-який незаблокований тип; Image: jpg, jpeg, png, gif, webp, avif
Розмір одного файлу ->maxSize(5120) (КБ, як правило max Laravel) files.max_size = 10240 (10 МБ)
Кількість файлів ->maxFiles(5) (лише з multiple()) files.max_files = 10; без multiple() завжди 1
Обов'язковість ->rules('required'), ->creationRules('required') не обов'язкове
Заблоковані типи files.blocked_extensions див. Конфігурація
  • types() перевіряє і вміст (mimes), і розширення імені з браузера (extensions). Це основний засіб контролю типів: задавайте types() для кожного поля File, а список заблокованих розширень лишайте запобіжником. types([]) кидає InvalidArgumentException; щоб дозволити будь-який незаблокований тип, не викликайте types() взагалі.

  • Image::types() приймає лише растрові типи (jpg, jpeg, png, gif, webp, avif), інші, і SVG зокрема, кидають InvalidArgumentException.

  • required означає, що після збереження в полі має лишитися хоча б один файл. Під час редагування вже завантажений файл задовольняє правило.

  • Кількість рахується після збереження: лишені наявні файли плюс нові.

  • Інші ваші правила з rules(), creationRules(), updateRules() застосовуються до кожного завантаженого файлу окремо. Наприклад, мінімальний розмір зображення:

    Image::make('Обкладинка', 'cover')->rules('required', 'dimensions:min_width=600'),
    

    required, nullable, sometimes і array стосуються поля загалом, а не файлів. nullable() приймається, але нічого не змінює: порожнє поле визначає required.

Помилки показуються біля поля, а помилка конкретного файлу біля цього файлу. Браузер теж перевіряє розмір, тип і кількість ще до відправлення, але остаточне рішення завжди за сервером.

Заблоковані розширення

Розширення зі списку files.blocked_extensions відхиляються завжди, навіть якщо їх перелічено в types(). Список за замовчуванням охоплює:

  • серверні скрипти: сімейство PHP (php, phtml, phar, inc тощо), CGI, Perl, Python, Ruby, shell, ASP/ASP.NET, JSP, ColdFusion, SSI (shtml);
  • виконувані файли й скриптові хости Windows: exe, dll, msi, bat, cmd, scr, hta, vbs, ps1, reg, lnk, jar та інші;
  • розмітку й скрипти, які виконує браузер: HTML, XHTML, MHT, SVG, XML, XSL, JS (js, mjs, cjs), swf;
  • конфігурацію сервера: htaccess, htpasswd, ini.

Перевіряються:

  • кожен сегмент імені з браузера після першої крапки, і в сирому, і в очищеному імені (без кінцевих крапок і пробілів, які відкидає Windows): photo.php.jpg, run.bat" і .htaccess відхиляються. Сама основа імені не перевіряється, тож php.pdf проходить (і на диск потрапляє з випадковим іменем);
  • розширення, визначене за вмістом: PHP-код, перейменований на .jpg, теж відхиляється.

Публічні диски віддає вебсервер, тому для них діє мінімальний список, який не можна вимкнути конфігом: розмітка й скрипти для браузера (svg, svgz, html, htm, xhtml, xht, xhtm, shtml, mht, mhtml, xml, xsl, xslt, js, mjs) і серверні (php, phtml, phar, htaccess). Вони відхиляються, навіть якщо прибрати їх з files.blocked_extensions.

SVG може містити скрипти. Дозволити його можна лише на приватному диску, прибравши svg (і за потреби svgz) з files.blocked_extensions, і лише в полі File: Image SVG не приймає. Такий файл віддається як вкладення, не відкривається в браузері.

Ліміти PHP

PHP відкидає завеликі запити ще до Laravel. Перевірте php.ini:

Директива Що обмежує
upload_max_filesize розмір одного файлу; за замовчуванням 2M
post_max_size розмір усього запиту з усіма файлами; за замовчуванням 8M
max_file_uploads кількість файлів в одному запиті; за замовчуванням 20

Поле повідомляє ці ліміти браузеру, тож форма показує менше з двох значень (maxSize і upload_max_filesize) і не дає відправити завеликі файли. Файл більший за upload_max_filesize сервер відхиляє як незавантажений. Запит більший за post_max_size Laravel зупиняє з відповіддю 413; форма показує повідомлення «Файли завеликі для одного збереження», а не сторінку помилки. Ліміти вебсервера (client_max_body_size у nginx) теж мають бути не меншими.

Приватні файли й підписані посилання

Посилання на приватні файли на дисках local ведуть на маршрут панелі:

GET {path}/files/{entity}/{id}/{field}/{token}?expires=…&signature=…

Ім'я маршруту larsadmin.files.show. Шлях до файлу в посиланні не передається: token це перші 32 hex-символи HMAC від APP_KEY, диска й шляху. Підпис відносний (signed:relative), тож посилання працює за проксі й з іншим хостом. Параметр download=1 (частина підпису) вмикає завантаження замість перегляду.

Права перевіряються під час кожного запиту, у тому ж порядку, що й для сторінки запису:

  1. підпис і термін дії (інакше 403), вхід у панель і viewLarsadmin, ліміт запитів;
  2. Entity існує (інакше 404) і viewAny (інакше 403);
  3. запис знаходиться через detailQuery (інакше 404);
  4. view для запису (інакше 403);
  5. поле File/Image з цим атрибутом показується користувачу на detail чи index, або на формі редагування, якщо є право update; canSee() повертає true (інакше 404);
  6. токен збігається з одним з поточних файлів запису (інакше 404), файл існує на диску (інакше 404).

Отже, посилання перестає працювати, щойно користувач втратив доступ або файл замінили, навіть якщо термін дії ще не минув.

Інші правила:

  • Посилання видаються лише тоді, коли користувач може переглядати запис (view). Інакше в списку видно тільки назви файлів.
  • Термін дії не менший за files.url_ttl (хвилини, за замовчуванням 30). Якщо сторінка була відкрита довше, клік по посиланню спершу оновлює дані сторінки й відкриває свіже посилання.
  • Ліміт files.throttle завантажень на хвилину на користувача (на IP для гостей), лімітер larsadmin-files. null вимикає ліміт. Кожен запит віддає файл повністю (без Range), тож за 600 запитів на хвилину один користувач може завантажити кілька гігабайтів. Якщо трафік сховища платний, зменшіть ліміт.
  • Відповідь передається потоком, файл не читається в пам'ять повністю.
  • Заголовки: растрові зображення (jpg, jpeg, png, gif, webp, avif) віддаються inline зі своїм типом, усе інше як application/octet-stream з Content-Disposition: attachment. Завжди X-Content-Type-Options: nosniff, Content-Security-Policy з sandbox, Cross-Origin-Resource-Policy: same-origin і Referrer-Policy: no-referrer. Ім'я файлу очищується й кодується за RFC 6266 з ASCII-варіантом.

Кешування

  • Вкладення (усе, що завантажується, а не показується) віддаються з Cache-Control: private, no-store: копія не лишається в браузері після виходу чи втрати доступу.
  • Растрові зображення, показані inline, віддаються з Cache-Control: private, max-age=<url_ttl>.
  • Термін дії посилання округлюється вгору до чверті url_ttl (не менше хвилини). Тому протягом цього інтервалу посилання на файл однакові, і браузер бере мініатюри з кешу. Фактичний термін дії від url_ttl до url_ttl плюс чверть.
  • Тимчасові посилання S3 отримують ті самі значення через ResponseCacheControl.

Видалення й очищення

Диск не транзакційний, тому панель веде журнал файлів одного збереження:

  • нові файли записуються до транзакції бази даних;
  • якщо заповнення, save() чи колбек після збереження кидає виняток, файли, записані цим запитом, видаляються, а старі лишаються;
  • замінені й прибрані файли видаляються лише після коміту найзовнішньої транзакції. Якщо ваш код обгортає збереження власною транзакцією, видалення чекає її коміту, а при відкаті файли лишаються;
  • помилки видалення з диска передаються в report() (з диском, шляхом, Entity й ключем) і не ламають запит.

Очищення (pruning)

Очищення увімкнене за замовчуванням (files.prune). Воно видаляє:

  • файл, замінений новим;
  • файли, прибрані у формі;
  • усі файли запису, видаленого через панель.

Вимкнути для поля:

File::make('Архів', 'archive')->prunable(false),

або для всієї панелі: 'prune' => false у config/larsadmin.php.

Очищення розраховане на правило «один файл, один запис» і не рахує посилань. Панель сама його дотримується: кожне завантаження отримує нове випадкове ім'я. Але якщо ваш код записує той самий шлях у кілька записів ($post->replicate(), спільний файл за замовчуванням на кшталт avatars/default.png із сідера), заміна файлу чи видалення одного запису видалить файл і для решти. Для таких колонок використовуйте prunable(false).

Одночасне редагування

Форма надсилає два списки токенів: файли, які вона показала (known), і файли, які користувач залишив (keep). Прибраними вважаються лише файли з known, яких немає в keep. Тому:

  • файли, які хтось інший додав, поки форма була відкрита, зберігаються;
  • токени зі старим APP_KEY (форма відкрита до зміни ключа) не збігаються ні з чим, і нічого не прибирається;
  • з removable(false) сервер не прибирає жодного файлу, навіть якщо запит надіслано в обхід форми. Новий файл в одиночному полі все одно замінює старий.

Для запитів не з форми панелі (API-клієнти, тести): без known нічого не прибирається, нові файли multiple() додаються до наявних, а файл, завантажений в одиночне поле, замінює старий.

Видалення записів

Кнопка видалення в панелі викликає Entity::deleteRecord(Request $request): delete() у транзакції, а після коміту видалення файлів усіх полів File/Image Entity, навіть прихованих чи недоступних поточному користувачу (запису вже немає). prunable(false) і files.prune діють і тут.

У власних діях видалення використовуйте той самий метод замість $model->delete(). handle() не отримує Request, тож передайте request(). $this->entity містить клас Entity, до якої панель прив'язала дію:

public function handle(ActionFields $fields, Collection $models): ?ActionResponse
{
    // Через Entity, а не $model->delete(): файли полів File/Image видаляються після коміту.
    $models->each(fn (Model $model) => $this->entity::make($model)->deleteRecord(request()));

    return ActionResponse::message('Видалено записів: '.$models->count());
}

$this->entity дорівнює null, якщо дію створено поза панеллю (наприклад, у тесті через new). Тоді вкажіть клас Entity явно: \App\Larsadmin\Post::make($model)->deleteRecord(request()).

М'яке видалення

Якщо модель використовує SoftDeletes, видалення з панелі лише встановлює deleted_at, а файли лишаються, щоб запис можна було відновити.

Остаточне видалення поза панеллю (forceDelete(), $model->delete() у вашому коді, команди очищення) файли не видаляє. Додайте слухача моделі:

// app/Models/Post.php
use Illuminate\Support\Facades\Storage;

protected static function booted(): void
{
    static::forceDeleted(function (Post $post) {
        $cover = $post->cover;                                             // Image, диск public
        $attachments = collect($post->attachments ?? [])->pluck('path')->filter()->all(); // multiple(), диск local

        $post->getConnection()->afterCommit(function () use ($cover, $attachments) {
            if ($cover) {
                Storage::disk('public')->delete($cover);
            }

            Storage::disk('local')->delete($attachments);
        });
    });
}

afterCommit() чекає коміту транзакції, а без транзакції виконується одразу. Приклад розраховано на cast array для attachments.

Власні поля з файлами

Поле розширення, що записує файли, реєструє їх у тому самому журналі збереження, яким користуються File і Image: перевизначає Field::fillInto(Request $request, Model $record, FileJournal $journal) і викликає $journal->created($disk, $path) для записаних файлів (видаляються, якщо збереження не вдалося) та $journal->obsolete($disk, $path) для замінених (видаляються після коміту). Форма такого поля надсилається як multipart/form-data, якщо поле має multipart(). Див. Розширення: хуки збереження.

FileSafety для розширень

З 1.6 перевірки безпеки, якими користуються File і Image, доступні розширенням через стабільний фасад Maxcxam\Larsadmin\Files\FileSafety. Розширення, що зберігає чи віддає файли (як доповнення медіатеки), не тримає власної копії цих перевірок.

Метод Опис
rule(bool $publicDisk = false): Closure правило валідації: відхиляє заблоковані розширення за кожним сегментом імені й за типом, визначеним за вмістом; з $publicDisk також розмітку й скрипти
blockedExtension(UploadedFile $file, bool $publicDisk = false): ?string перше заблоковане розширення файлу або null
storedExtension(UploadedFile $file, bool $publicDisk = false): string розширення для збереження: визначене за вмістом і дозволене, інакше bin; ніколи не розширення з браузера
sanitizeName(string $name): string очищена назва з браузера: останній сегмент шляху, без керівних, bidi/zero-width і зарезервованих символів, до 200 символів; може бути ''
asciiName(string $name): string ASCII-варіант назви для параметра filename у Content-Disposition
isRasterImage(string $path, ?string $mime = null): bool чи можна показати файл inline: растрове розширення (jpg, jpeg, png, gif, webp, avif; не SVG) і, якщо передано, растровий MIME-тип
response(FilesystemAdapter $disk, string $path, ?string $name = null, bool $download = false, ?int $ttlMinutes = null): StreamedResponse потокова відповідь із заголовками панелі (див. нижче)
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Maxcxam\Larsadmin\Files\FileSafety;

// валідація завантаження
$request->validate(['upload' => ['required', 'file', 'max:10240', FileSafety::rule($public)]]);

// збереження під випадковим ім'ям з розширенням за вмістом
$file = $request->file('upload');
$path = $file->storeAs('acme', Str::random(40).'.'.FileSafety::storedExtension($file, $public), $disk);
$name = FileSafety::sanitizeName($file->getClientOriginalName());

// віддача (після авторизації запиту, наприклад Larsadmin::findRecord())
return FileSafety::response(Storage::disk($disk), $path, $name);

response() віддає inline лише растрові зображення (за розширенням шляху, якщо не $download), решту як вкладення application/octet-stream; завжди з X-Content-Type-Options: nosniff, Content-Security-Policy: sandbox, Cross-Origin-Resource-Policy: same-origin, Referrer-Policy: no-referrer і приватним кешем (inline-зображення на $ttlMinutes, за замовчуванням files.url_ttl; значення менше 1 кидає InvalidArgumentException; вкладення не кешуються). Назва за замовчуванням: базове ім'я шляху. Авторизацію робить код розширення до виклику. FileTypes, FileResponse і StoredFile лишаються внутрішніми.

Безпека

  • Шлях до файлу ніколи не приходить з браузера. Форма надсилає токени наявних файлів, і вони порівнюються лише з поточними файлами запису; невідомі токени ігноруються. Значення поля, що не є масивом (наприклад, підставлений шлях), дає помилку валідації, нічого не зберігається.
  • Ім'я на диску випадкове, розширення визначається за вмістом. Каталог задається лише в коді.
  • Приватні файли перевіряються на кожному запиті (див. вище) і віддаються з безпечними заголовками.
  • Публічні диски віддає вебсервер, і панель не може задати там заголовки. Розмітку й скрипти (HTML, SVG, XML, JS, PHP) на публічних дисках заблоковано без можливості вимкнути, але інші типи, наприклад pdf чи txt, вебсервер віддає inline з домену застосунку. Рекомендації:
    • обмежуйте поля через types(), а не лише через список заблокованих розширень;
    • додайте для /storage у конфігурацію вебсервера заголовки X-Content-Type-Options: nosniff, Content-Security-Policy: sandbox і Content-Disposition: attachment для всього, крім зображень;
    • файли, які не повинні бути публічними, зберігайте на приватному диску.
  • Видаляються лише шляхи, на які посилався запис, і лише після коміту.
  • Поля File і Image, як і будь-яке поле з multipart: true, не можна використовувати в fields() дій (LogicException): дія заповнює тимчасову модель, і файли лишилися б без власника.
  • Розширення, що працюють з файлами, користуються тими самими перевірками через FileSafety.

Довідник API

Класи Maxcxam\Larsadmin\Fields\File і Maxcxam\Larsadmin\Fields\Image (Image extends File).

Метод Опис
File::make(string $name, ?string $attribute = null) компонент file-field; Image::make() дає image-field
multiple(bool $multiple = true) кілька файлів у JSON-колонці
maxFiles(int $max) максимум файлів для multiple(); менше 1 кидає InvalidArgumentException
disk(string $disk) диск; за замовчуванням files.disk
public(), private() видимість; за замовчуванням з visibility диска. public() на диску local без url кидає InvalidArgumentException
path(string|Closure $directory) каталог або fn (Request $request, Model $record): string
types(array $types) дозволені розширення, наприклад ['pdf', 'docx']; без виклику (за замовчуванням у File) будь-який незаблокований тип; types([]) кидає InvalidArgumentException; Image::types() приймає лише растрові типи
maxSize(int $kilobytes) максимальний розмір одного файлу в КБ; менше 1 кидає InvalidArgumentException
storeOriginalName(string $column) колонка для очищеної оригінальної назви; лише для одного файлу
prunable(bool $prunable = true) видаляти замінені, прибрані файли й файли видалених записів; за замовчуванням files.prune. false для шляхів, спільних для кількох записів
removable(bool $removable = true) дозволити прибирати файли. З false форма не показує кнопку «Видалити», а сервер не прибирає файли навіть із запиту в обхід форми; новий файл в одиночному полі все одно замінює старий
Image::thumbnail(Closure $callback) fn (StoredFile $file, Model $record): ?string; URL мініатюри або null для оригіналу
filesOf(Model $record): array поточні файли запису як list<StoredFile> (path, name, size, mime, extension(), isRasterImage())
rules(), creationRules(), updateRules() required для поля, решта для кожного файлу (див. Валідація)
fillUsing(callable) повністю замінює збереження: поле нічого не записує на диск і нічого не видаляє
displayUsing(callable) fn (array $names, Model $record, string $attribute), отримує список назв файлів. Результат потрапляє в displayedAs; вбудовані компоненти показують список файлів, тож він потрібен лише власним компонентам
resolveUsing(callable) для полів файлів ігнорується
sortable() кидає LogicException: сортувати за файлами не можна
nullable() приймається, нічого не змінює

Інше:

API Опис
Entity::deleteRecord(Request $request): void видалення запису в транзакції й очищення файлів після коміту (див. Видалення записів)
маршрут larsadmin.files.show GET {path}/files/{entity}/{id}/{field}/{token}, підписаний
лімітер larsadmin-files files.throttle запитів на хвилину

Поведінка в списку: для одного файлу показується він сам, для multiple() до трьох мініатюр чи назв і лічильник +N. Мініатюри це оригінали, зменшені CSS.

Конфігурація

Секція files у config/larsadmin.php. Конфіг, опублікований до 1.1, працює без неї: усі ключі мають значення за замовчуванням.

Ключ За замовчуванням Опис
disk env('LARSADMIN_FILES_DISK', 'local') диск полів без disk()
directory 'larsadmin' базовий каталог; каталог поля {directory}/{uriKey}/{атрибут}
max_size 10240 максимальний розмір файлу, КБ
max_files 10 максимум файлів для multiple() без maxFiles()
prune true видаляти замінені, прибрані файли й файли видалених записів
url_ttl 30 термін дії підписаних і тимчасових посилань, хвилини
throttle 600 завантажень приватних файлів на хвилину на користувача; null без ліміту
blocked_extensions серверні скрипти, виконувані файли Windows, розмітка й скрипти для браузера, конфігурація сервера розширення, які не приймаються ніколи (див. Заблоковані розширення); запобіжник, основний контроль це types(). На публічних дисках розмітка, скрипти й PHP заблоковані завжди

Обмеження

  • Немає обробки зображень на сервері. Мініатюри це оригінали, зменшені CSS. Якщо у вас є власні мініатюри (CDN з resize, Glide тощо), поверніть їх URL з ->thumbnail(...):

    Image::make('Обкладинка', 'cover')
        ->thumbnail(fn (StoredFile $file, Model $record) => "https://img.example.com/{$file->path}?w=160"),
    
  • Немає зміни порядку файлів перетягуванням: нові файли додаються в кінець.

  • Немає попереднього чи порційного завантаження. Файли надсилаються разом з формою, тож загальний розмір обмежує post_max_size.

  • Поля файлів не підтримуються в діях і не сортуються.

  • Файл може залишитися без власника, якщо процес завершився між записом файлу й комітом або якщо зовнішня транзакція вашого коду відкотилася вже після збереження. Команди для прибирання таких файлів у 1.1 немає.

  • Очищення не рахує посилань: шлях, спільний для кількох записів, потребує prunable(false) (див. Очищення).

  • Ці поля не використовують spatie/laravel-medialibrary. Її колекції підтримує окреме доповнення maxcxam/larsadmin-medialibrary (Media, MediaGallery), побудоване на публічному API розширень і Files\FileSafety.

Далі: Глобальний пошук.

Введіть запит, щоб шукати в усіх посібниках. ↑ ↓ — вибір, Enter — відкрити.