Лаб. работа 6 "Работа с файлами (эмуляция S3-хранилища)"

Тема: Реализация загрузки, хранения и управления файлами с использованием локального хранилища в качестве эмуляции S3.
Цель: Научиться работать с файловой системой Laravel, имитируя облачное хранилище, и реализовать CRUD для мультимедийных данных.


Задачи

  1. Настроить фейковый S3-диск для локальной разработки.
  2. Реализовать загрузку файлов к постам.
  3. Добавить валидацию и оптимизацию файлов.
  4. Организовать просмотр и удаление файлов.

Методическая часть

1. Настройка файлового хранилища

  1. Добавьте новый диск в config/filesystems.php:

    1'disks' => [
    2    // ... другие диски
    3    's3-fake' => [
    4        'driver' => 'local',
    5        'root' => storage_path('app/s3-fake'),
    6        'url' => env('APP_URL') . '/storage/s3-fake',
    7        'visibility' => 'public',
    8    ],
    9],
    
  2. Обновите .env:

    1FILESYSTEM_DISK=s3-fake
    
  3. Создайте директорию и симлинк:

    1mkdir -p storage/app/s3-fake
    2php artisan storage:link
    

2. Модификация модели и миграции

  1. Создайте модель Media:

    1php artisan make:model Media -m
    
  2. Добавьте поля в миграцию:

    1Schema::create('media', function (Blueprint $table) {
    2    $table->id();
    3    $table->string('path');
    4    $table->foreignId('post_id')->constrained()->cascadeOnDelete();
    5    $table->string('type'); // image, video, document
    6    $table->timestamps();
    7});
    
  3. Добавьте связь в модели:

    • В Post.php:
      1public function media() {
      2    return $this->hasMany(Media::class);
      3}
      

3. Обновление контроллера PostController

  1. Метод store для загрузки файлов:

     1use Illuminate\Support\Facades\Storage;
     2
     3public function store(Request $request) {
     4    $validated = $request->validate([
     5        'title' => 'required|max:255',
     6        'files.*' => 'file|mimes:jpg,png,pdf,mp4|max:5120',
     7    ]);
     8
     9    $post = Post::create($validated);
    10
    11    if ($request->hasFile('files')) {
    12        foreach ($request->file('files') as $file) {
    13            $path = $file->store("posts/{$post->id}", 's3-fake');
    14            Media::create([
    15                'post_id' => $post->id,
    16                'path' => $path,
    17                'type' => $file->getMimeType(),
    18            ]);
    19        }
    20    }
    21
    22    return redirect()->route('posts.index');
    23}
    
  2. Метод destroyFile для удаления:

    1public function destroyFile(Media $media) {
    2    Storage::disk('s3-fake')->delete($media->path);
    3    $media->delete();
    4    return back()->with('success', 'Файл удалён!');
    5}
    

4. Blade-шаблоны

  1. Форма загрузки файлов (posts/create.blade.php):

    1<form method="POST" action="{{ route('posts.store') }}" enctype="multipart/form-data">
    2    @csrf
    3    <div class="mb-4">
    4        <label>Прикрепить файлы:</label>
    5        <input type="file" name="files[]" multiple class="border p-2 w-full">
    6        @error('files.*') <div class="text-red-500">{{ $message }}</div> @enderror
    7    </div>
    8    <!-- Остальные поля формы -->
    9</form>
    
  2. Галерея файлов (posts/show.blade.php):

     1<div class="grid grid-cols-3 gap-4">
     2    @foreach ($post->media as $media)
     3        <div class="relative">
     4            @if (Str::startsWith($media->type, 'image'))
     5                <img src="{{ Storage::disk('s3-fake')->url($media->path) }}" 
     6                     class="w-full h-48 object-cover">
     7            @else
     8                <div class="bg-gray-100 p-4">
     9                    <p>{{ basename($media->path) }}</p>
    10                </div>
    11            @endif
    12            <form method="POST" action="{{ route('posts.deleteFile', $media) }}" 
    13                  class="absolute top-2 right-2">
    14                @csrf @method('DELETE')
    15                <button type="submit" class="bg-red-500 text-white p-1 rounded">×</button>
    16            </form>
    17        </div>
    18    @endforeach
    19</div>
    

Индивидуальные задания

1. Валидация имен файлов

  • Запретите загрузку файлов с пробелами в имени:
    1Validator::extend('no_spaces', function ($attr, $value, $params) {
    2    return !preg_match('/\s/', $value->getClientOriginalName());
    3});
    4// Использование:
    5'files.*' => 'no_spaces',
    

2. Логирование операций

  • Создайте middleware LogFileOperations:
    1public function handle($request, Closure $next) {
    2    if ($request->hasFile('files')) {
    3        \Log::info("Uploaded files: " . json_encode($request->file('files')));
    4    }
    5    return $next($request);
    6}
    
  • Зарегистрируйте его в bootstrap/app.php:
    1->withMiddleware(function (Middleware $middleware) {
    2    $middleware->append(\App\Http\Middleware\LogFileOperations::class);
    3})
    

3. Оптимизация изображений (опционально)

  • Установите пакет:
    1composer require spatie/image-optimizer
    
  • Оптимизируйте изображения перед сохранением:
    1use Spatie\ImageOptimizer\OptimizerChainFactory;
    2// ...
    3foreach ($request->file('files') as $file) {
    4    if (Str::startsWith($file->getMimeType(), 'image')) {
    5        $optimizer = OptimizerChainFactory::create();
    6        $optimizer->optimize($file->getRealPath());
    7    }
    8    // Сохранение файла...
    9}
    

Проверка работы

  1. Загрузка файлов:

    • Откройте форму создания поста.
    • Загрузите несколько файлов (изображения, PDF).
    • Убедитесь, что файлы сохранились в storage/app/s3-fake/posts/{post_id}.
  2. Просмотр файлов:

    • Откройте страницу поста.
    • Проверьте, что изображения отображаются, а PDF показывается как ссылка.
  3. Удаление файлов:

    • Нажмите кнопку удаления файла.
    • Убедитесь, что файл удалён из хранилища и БД.

Примечания

  • Преимущества подхода:

    • Эмуляция облачного хранилища экономит ресурсы.
    • Легко переключиться на реальный S3 через .env.
  • Важно:

    • Для продакшена замените s3-fake на s3 в .env.
    • Для тестирования используйте Storage::fake('s3-fake').
← Лаб. работа 5 "Создание форм для работы с … Лаб. работа "Создание маршрутов в Laravel" →