Laravelで画像管理アプリを作成してみよう

簡単な画像管理アプリを作成してみたいと思います。前回のLaravelに画像をアップしてみようをバージョンアップして作成してみます。

目次

基本構想

STEP

コントローラーとルーティング

リソースコントローラを使用すると、一連の関連するアクション(CRUD操作:作成、読み取り、更新、削除)を簡単に管理できます。Laravelのリソースコントローラはこれらの操作に対応するメソッドを予め定義しています。画像管理アプリにおいては、この機能を活用して効率的にコードを整理できます。

コントローラーはリソースコントローラーを使用します。

STEP

データベース

imagesテーブルに、WordPressのように、base画像、小さい画像、サムネイル画像をそれぞれ、呼び出せるような構造にします。

STEP

モデル

Eloquentモデルを使用します。

STEP

ストレージ

Laravel内部のStorageではなく、/var/www/storageに保存します。

初期設定

STEP

コントローラーの作成

php artisan make:controller ImageController --resource

下記の様なコントローラーが生成されました。

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;

class ImageController extends Controller
{
    public function index(){}    
    public function create(){}
    public function store(Request $request) {}
    public function show(string $id) {}
    public function edit(string $id) {}
    public function update(Request $request, string $id) {}
    public function destroy(string $id) {}
}

上記は、余分な表記はカットして、わかりやすく表示していますが、7つのメソッドが既に生成されています。

STEP

7つのメソッドの使い方

Laravelのリソースコントローラでは、CRUD(作成、読み取り、更新、削除)操作に対応する7つのメソッドが一般的に使用されます。以下では、ImageController 内の各メソッドの使い方を説明します。

1. index()

目的: 画像の一覧を表示します。

使用方法:

  • 通常、データベースから全ての画像データを取得し、それらをビューに渡して表示します。
  • 例:Image::all()を使用して全画像を取得し、ビューに渡します。

2. create()

目的: 画像アップロードのためのフォームを表示します。

使用方法:

  • 画像アップロード用のビューを返します。
  • 特にデータを渡す必要はありません。

3. store(Request $request)

目的: フォームから送信された画像を受け取り、保存します。

使用方法:

  • $requestから画像データを取得します。
  • 画像をサーバーに保存し、必要に応じてデータベースに画像情報を記録します。
  • 保存処理後、適切なリダイレクとレスポンスを返します。

4. show(String $id)

目的: 特定の画像の詳細情報を表示します。

使用方法:

  • $idを使って特定の画像をデータベースから取得します。
  • 取得した画像情報をビューに渡して表示します。

5. edit(String $id)

目的: 特定の画像を編集するためのフォームを表示します。

使用方法:

  • $id を使って編集する画像をデータベースから取得します。
  • 取得した画像情報を編集用のビューに渡します。

6. update(Request $request, String $id)

目的: 編集フォームから送信されたデータを使って、特定の画像情報を更新します。

使用方法:

  • $id を使って特定の画像をデータベースから取得し、$request のデータで更新します。
  • 更新処理後、適切なリダイレクトレスポンスを返します。

7. destroy(String $id)

目的: 特定の画像を削除します。

使用方法:

  • $id を使って削除する画像をデータベースから取得し、削除します。
  • 削除処理後、適切なリダイレクトレスポンスを返します。

これらのメソッドを適切に実装することで、画像に関する一連の操作を効率的に管理できます。また、これらのメソッドはリソースルーティングを使って簡単にルートに割り当てることができます。

STEP

ルーターの作成

LaravelでImageControllerのリソースフルルーティングを作成し、ミドルウェアにJetStreamの認証を設定する例です。

リソースフルルーティングは Route::resource メソッドを使用して定義します。このメソッドは最初の引数にリソース名(URLに使用される)、二番目の引数にコントローラ名を取ります。

use App\Http\Controllers\ImageController; //追加

Route::middleware(['auth:sanctum', 'verified'])->group(function () {
    // ImageControllerのリソースフルルーティング
    Route::resource('/images', ImageController::class);
});

このコードは以下のルートを生成します:

  • GET /images => indexメソッド(画像の一覧表示)
  • GET /images/create => create メソッド(画像アップロードフォームの表示)
  • POST /images => store メソッド(画像のアップロード処理)
  • GET /images/{image} => showメソッド(画像の詳細表示)
  • GET /images/{image}/edit => editメソッド(画像の編集フォーム表示)
  • PUT/PATCH /images/{image} => updateメソッド(画像の更新処理)
  • DELETE /images/{image} => destroy メソッド(画像の削除)

ここで、{image} は画像のIDまたは識別子を示すルートパラメータです。

STEP

テーブルに新規カラムの追加

前回作ったimagesテーブルにMサイズの画像用パスとサイズ、サムネイル画像用パスとサイズのカラムを追加します。

php artisan make:migration add_thumbnail_fields_to_images_table

下記の様に記述します。

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddThumbnailFieldsToImagesTable extends Migration
{
    public function up()
    {
        Schema::table('images', function (Blueprint $table) {
            $table->string('m_image_path')->nullable()->after('size');
            $table->integer('m_image_size')->nullable()->after('m_image_path');
            $table->string('thumbnail_path')->nullable()->after('m_image_size');
            $table->integer('thumbnail_size')->nullable()->after('thumbnail_path');
        });
    }

    public function down()
    {
        Schema::table('images', function (Blueprint $table) {
            $table->dropColumn('m_image_path');
            $table->dropColumn('m_image_size');
            $table->dropColumn('thumbnail_path');
            $table->dropColumn('thumbnail_size');
        });
    }
}
STEP

DBの作成(※新規作成の場合)

imagesテーブルを作成していない場合、STEP9とSTP10を実行します。※既にimagesテーブルがある場合は、STEP9とSTEP10を飛ばしてください。

php artisan make:migration create_images_table
STEP

マイグレーションファイルの編集(※新規作成の場合)

下記の様に記述します。

database/migrations/日付とID_create_images_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;

class CreateImagesTable extends Migration
{
    public function up()
    {
        Schema::create('images', function (Blueprint $table) {
            $table->id();
            $table->uuid('uuid')->unique();
            $table->string('name');
            $table->string('path');
            $table->integer('size')->nullable();
            $table->string('m_image_path')->nullable();
            $table->string('m_image_size')->nullable();
            $table->string('thumbnail_path')->nullable();
            $table->string('thumbnail_size')->nullable();
            $table->timestamps();
            $table->softDeletes();

            // インデックスを追加する。
            $table->index('uuid');
        });
    }

    public function down()
    {
        Schema::dropIfExists('images');
    }
}
STEP

マイグレーションの実行

マイグレーションを実行します。

php artisan migrate
STEP

モデルの修正

前回作ったモデルの $fillableに今回追加したカラムを追加します。

// 'm_image_path', 'm_image_size', 'thumbnail_path', 'thumbnail_size'を追加記述
protected $fillable = [
        'uuid', 'name', 'path', 'size', 'm_image_path', 'm_image_size', 'thumbnail_path', 'thumbnail_size'
    ];
STEP

モデルの作成(※新規作成の場合)

新規作成の場合は、STEP13を実行します。

php artisan make:model Image

下記の様に記述します。

app/Models/Image.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;   // SoftDeletesを使用する。
use Illuminate\Support\Str; // Strを使用する(uuidを生成するために使用する。)

class Image extends Model
{
    use HasFactory;
    use SoftDeletes;

    protected $fillable = [
        'uuid',
        'name',
        'path',
        'size',
        'm_image_path',
        'm_image_size',
        'thumbnail_path',
        'thumbnail_size',
    ];
    protected $dates = [
        'deleted_at',
    ];
    protected $hidden = [
        'id',
        'deleted_at',
    ];
    protected $casts = [
        'uuid' => 'string',
        'name' => 'string',
        'path' => 'string',
        'size' => 'integer',
        'm_image_path' => 'string',
        'm_image_size' => 'integer',
        'thumbnail_path' => 'string',
        'thumbnail_size' => 'integer',
    ];
    protected static function boot()
    {
        parent::boot();
        static::creating(function ($model) {
            // モデルが作成される前に、UUIDを生成して割り当てます。
            if (empty($model->uuid)) {
                $model->uuid = Str::uuid();
            }
        });
    }
}
STEP

uuidライブラリの追加(※新規作成の場合)

前回作成した人はSTEP14は飛ばしてください。新規作成の人は、下記を実行します。

Composerを使って ramsey/uuid ライブラリをインストールします。

composer require ramsey/uuid

詳細は、Laravelに画像をアップしてみようのSTEP15〜STEP17を参照してください。

STEP

新規ビューディレクトリの作成

ビューをディレクトリで管理したいので、下記を実行して、イメージ用の新規ビューを作成します。

sudo mkdir -p /var/www/storage/images
sudo chown -R www-data:www-data /var/www/storage
sudo chmod -R 775 /var/www/storage
STEP

シンボリックリンクの作成

まず、既存のシンボリックリンクを削除します。

sudo rm /var/www/laravel/public/storage

そして、新しくシンボリックリンクを作成します。

ln -s /var/www/storage /var/www/laravel/public/storage
STEP

スタイルテンプレートの作成

views/layoutsディレクトリに基本となるテンプレートbase.blade.phpを作成し、下記を入力します。

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@yield('title')</title>
    @vite('resources/css/app.css')
    @livewireStyles
    </head>
    <body class="font-sans antialiased">
        @livewire('header-menu')
        @yield('content')
        @livewireScripts
</body>
</html>
STEP

Livewireでヘッダーメニューの作成

ヘッダーメニューを下記の様に作成します。

php artisan make:livewire header-menu

このコマンドにより、app/Http/Livewire/HeaderMenu.php と resources/views/livewie/header-menu.blade.php の2つのファイルが生成されます。

header-menu.blade.php ファイルを編集して、ヘッダーメニューのマークアップを追加します。

<div x-data="{ isOpen: false }">
    <div class="bg-gray-800">
        <nav class="flex items-center justify-between flex-wrap py-1 px-4">
            <div class="flex items-center flex-shrink-0 text-white mr-6">
                <a href="/images" class="font-semibold text-base tracking-tight">WinRoad徒然草</a>
            </div>
            <div class="block sm:hidden">
                <button @click="isOpen = !isOpen" class="flex items-center px-3 py-2 border rounded text-teal-200 border-teal-400 hover:text-white hover:border-white">
                    <svg class="fill-current h-3 w-3" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
                        <title>Menu</title>
                        <path d="M0 3h20v2H0zM0 9h20v2H0zM0 15h20v2H0z" />
                    </svg>
                </button>
            </div>
            <div :class="{'block': isOpen, 'hidden': !isOpen}" class="w-full sm:flex sm:items-center sm:w-auto sm:justify-end">
                <div class="text-sm sm:flex-grow">
                    <a href="#" class="block mt-4 sm:inline-block sm:mt-0 text-teal-200 hover:text-white mr-4">
                        Flutter<br class="hidden sm:block">
                        <span class="text-xs text-teal-200 hover:text-white">フラッター</span>
                    </a>
                    <a href="#" class="block mt-4 sm:inline-block sm:mt-0 text-teal-200 hover:text-white mr-4">
                        WordPress<br class="hidden sm:block">
                        <span class="text-xs text-teal-200 hover:text-white">ワードプレス</span>
                    </a>
                    <a href="#" class="block mt-4 sm:inline-block sm:mt-0 text-teal-200 hover:text-white mr-4">
                        Laravel<br class="hidden sm:block">
                        <span class="text-xs text-teal-200 hover:text-white">ララベル</span>
                    </a>
                    <a href="#" class="block mt-4 sm:inline-block sm:mt-0 text-teal-200 hover:text-white mr-4">
                        Ubuntu<br class="hidden sm:block">
                        <span class="text-xs text-teal-200 hover:text-white">ウブンツ</span>
                    </a>
                    <a href="#" class="block mt-4 sm:inline-block sm:mt-0 text-teal-200 hover:text-white mr-4">
                        Swell<br class="hidden sm:block">
                        <span class="text-xs text-teal-200 hover:text-white">スウェル</span>
                    </a>
                </div>
            </div>
        </nav>
    </div>
</div>

WinRoad徒然草のヘッダーと同じように作成してみました。

コントローラの編集

STEP

indexメソッドの修正

ImageControllerのindexメソッドを下記の様に修正します。

use App\Models\Image; //追加記述
// ....
public function index()
{
    // Imageモデルから全画像をページネーションを使って取得
    $images = Image::paginate(10); // 1ページあたり10件のデータを表示

    // 取得した画像データをビューに渡す
    return view('images.index', compact('images'));
}

このコードでは、Image::paginate(10)を使って、データベースから画像データを1ページにつき10件ずつ取得しています。paginateメソッドはLaravelのページネーション機能を提供し、自動的にページングを処理してくれます。

STEP

indexビューの作成

resources/view/images/index.blade.php
@extends('layouts.base')

@section('title', '画像一覧表示')

@section('content')
<div class="container mx-auto">
    <div class="flex justify-between items-center my-4">
        <h1 class="text-lg font-bold">画像一覧</h1>
        <a href="{{ route('images.create') }}" class="btn btn-primary">新規追加</a>
    </div>

    <div class="flex flex-wrap">
        @if(count($images) === 0)
        <p>画像はありません</p>
        @else
            @foreach ($images as $image)
            <div class="w-full md:w-1/2 lg:w-1/4 px-2 mb-4">
                <div class="bg-white p-4 rounded-lg shadow-md">
                    <a href="{{ route('images.show', $image->id) }}">
                        <img src="{{ asset($image->m_image_path) }}" alt="{{ $image->name }}" class="w-full h-auto object-cover rounded">
                    </a>
                    <p class="text-center mt-2 truncate">{{ $image->name }}</p>
                </div>
            </div>
            @endforeach
        @endif
    </div>

    <!-- ページネーションリンク -->
    <div class="mt-4">
        {{ $images->links() }}
    </div>
</div>
<!-- アップロード成功メッセージ -->
@if (session('success'))
<div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4 mt-4 mx-auto w-1/2 text-center" role="alert">
    <p>{{ session('success') }}</p>
</div>
@endif
@endsection
STEP

createメソッドの修正

ImageControllerのcreateメソッドを下記の様に修正します。

public function create()
    {
        // 画像アップロードフォームのビューを返す
        return view('images.create');
    }
STEP

createビューの作成

createビューは前回のimage-uploadをそのまま使用します。

@extends('layouts.base')

@section('title', '画像アップロード')

@section('content')
<div class="max-w-md mx-auto my-10 bg-white p-5 rounded-md shadow-sm">
    <div class="text-center">
        <h1 class="my-3 text-3xl font-semibold text-gray-700">画像アップロード</h1>
        <p class="text-gray-400">アップロードする画像を選択してください</p>
    </div>

    <div class="m-7">
        <form action="/images" method="POST" enctype="multipart/form-data">
            @csrf
            <div class="mb-6">
                <label for="image" class="block mb-2 text-sm text-gray-600">画像</label>
                <input type="file" name="image" id="image" class="w-full px-3 py-2 text-gray-700 bg-white border border-gray-300 rounded-md focus:outline-none" required>
            </div>
            <div class="mb-6">
                <button type="submit" class="w-full px-3 py-4 text-white bg-blue-500 rounded-md focus:bg-blue-600 focus:outline-none">アップロード</button>
            </div>
        </form>
    </div>
</div>

<!-- アップロード成功メッセージ -->
@if (session('success'))
<div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4 mt-4 mx-auto w-1/2 text-center" role="alert">
    <p>{{ session('success') }}</p>
</div>
@endif
@endsection
STEP

storeメソッドの修正

use Intervention\Image\Facades\Image as ImageIntervention;  //追加
use Illuminate\Support\Facades\Storage;  //追加
use Illuminate\Support\Str; //追加
...
 // 画像アップロード処理
public function store(Request $request)
{
    $request->validate([
        'image' => 'required|image|max:2048', // 画像ファイルのバリデーション
    ]);

    $file = $request->file('image');
    $originalName = $file->getClientOriginalName();
    $timestamp = time();
    $newPath = 'storage/images/';

    // 拡張子を除いたファイル名の取得
    $filenameWithoutExt = pathinfo($originalName, PATHINFO_FILENAME);

    // オリジナルの画像インスタンスを生成
    $originalImage = ImageIntervention::make($file);

    // 元のサイズでWebP保存
    $newName = $timestamp . '_' . $filenameWithoutExt . '.webp';
    $encodedOriginal = $originalImage->encode('webp', 80);
    Storage::disk('custom_images')->put($newName, (string) $encodedOriginal);
    $size = strlen((string) $encodedOriginal); // サイズを取得

    // 縦横半分のサイズでWebP保存
    $m_image = ImageIntervention::make($file)->resize($originalImage->width() / 2, $originalImage->height() / 2);
    $encodedMImage = $m_image->encode('webp', 80);
    $m_image_path = $timestamp . '_' . $filenameWithoutExt . '_half.webp';
    Storage::disk('custom_images')->put($m_image_path, (string) $encodedMImage);
    $m_image_size = strlen((string) $encodedMImage); // サイズを取得

    // 200x200で中央をクロップしてWebP保存
    $thumbnail_image = ImageIntervention::make($file)->fit(200, 200);
    $encodedThumbnail = $thumbnail_image->encode('webp', 80);
    $thumbnail_path = $timestamp . '_' . $filenameWithoutExt . '_thumb.webp';
    Storage::disk('custom_images')->put($thumbnail_path, (string) $encodedThumbnail);
    $thumbnail_size = strlen((string) $encodedThumbnail); // サイズを取得

    // データベースに画像情報を保存
    $image = new Image;
    $image->uuid = Str::uuid();
    $image->name = $filenameWithoutExt;
    $image->path = $newPath . $newName;
    $image->size = $size;
    $image->m_image_path = $newPath . $m_image_path;
    $image->m_image_size = $m_image_size;
    $image->thumbnail_path = $newPath . $thumbnail_path;
    $image->thumbnail_size = $thumbnail_size;
    $image->save();

    return redirect()->route('images.index')->with('success', '画像がアップロードされました。');
}

ImageInterventionがインストールされていない場合は、composerでインストールして下さい。詳細は、前回のLaravelに画像をアップしてみようのSTEP21〜24に記載しています。

STEP

showメソッドの修正

下記の様にImageControllerにshowメソッドを作成します。

public function show(String $id)
{
    // Image モデルを ID で検索
    $image = Image::findOrFail($id);

    // ビューにデータを渡す
    return view('images.show', compact('image'));
}
STEP

ビューの作成

views/images/show.blade.phpを作成します。

@extends('layouts.base')

@section('title', '画像詳細')

@section('content')
    <div class="container mx-auto p-4">
        <div class="card card-bordered">
            <figure>
                <img src="{{ asset($image->path) }}" alt="{{ $image->name }}" class="rounded-t-lg">
            </figure> 
            <div class="card-body">
                <h2 class="card-title">{{ $image->name }}</h2>
                <p>サイズ: {{ formatBytes($image->size) }}</p> <!-- ここにサイズを表示 -->
                <!-- ここにその他の画像詳細情報を表示 -->
                <div class="justify-end card-actions">
                    <a href="{{ route('images.index') }}" class="btn btn-primary">一覧に戻る</a>
                    <a href="{{ route('images.edit', $image->id) }}" class="btn btn-success">編集</a>
                    <a href="{{ route('images.download', $image->uuid) }}" class="btn btn-secondary">Download</a>
                    <form action="{{ route('images.destroy', $image->uuid) }}" method="POST" onsubmit="return confirm('本当に削除しますか?');">
                        @csrf
                        @method('DELETE')
                        <button type="submit" class="btn btn-error">削除</button>
                    </form>
                </div>
            </div>
        </div>
    </div>
@endsection

DeleteやDownloadに関しては、idでは無くて、uuidを使用しています。idだとブラウザから直接入力で、削除やダウンロードが出来るので、セキュリティの観点から、uuidにしています。

STEP

editメソッドの修正

editメソッドを下記の様に作成します。

public function edit(String $id)
{
    // IDに基づいて画像を検索
    $image = Image::findOrFail($id);

    // 編集ビューを表示し、画像オブジェクトを渡す
    return view('images.edit', compact('image'));
}
STEP

editビューの作成

editビューを下記の様に作成します。単純に画像タイトルのみの編集です。

@extends('layouts.base')

@section('title', 'タイトル編集')

@section('content')
<div class="container mx-auto p-4">
    <h1 class="text-lg font-bold mb-4">画像タイトル編集</h1>

    <form action="{{ route('images.update', $image->id) }}" method="POST" enctype="multipart/form-data">
        @csrf
        @method('PUT')

        <div class="mb-4">
            <label for="name" class="block text-sm font-medium text-gray-700">画像名</label>
            <input type="text" id="name" name="name" value="{{ $image->name }}" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm">
        </div>

        <!-- その他の編集可能なフィールド -->

        <div class="flex justify-start gap-4">
            <button type="button" onclick="window.history.back();" class="btn btn-secondary">戻る</button>
            <button type="submit" class="btn btn-primary">更新</button>
        </div>
    </form>
</div>
@endsection
STEP

updateメソッドの修正

updateメソッドを下記の様に修正します。

public function update(Request $request, String $id)
{
    // バリデーション
    $validated = $request->validate([
        'name' => 'required|string|max:255',
        // その他の必要なバリデーションルール
    ]);

    // IDに基づいて画像を検索
    $image = Image::findOrFail($id);

    // データの更新
    $image->name = $request->name;
    // その他のフィールドも同様に更新
    $image->save();

    // 更新後、適切なリダイレクト先へ
    return redirect()->route('images.index')
                     ->with('success', '画像情報が更新されました');
}
STEP

destoryメソッドの作成

下記の様にdestroyメソッドを作成します。

use Illuminate\Support\Facades\File;  //追加
...

// 画像削除処理
public function destroy($uuid) {
    // UUIDに基づいて画像を検索
    $image = Image::where('uuid', $uuid)->firstOrFail();

    // 画像ファイルの削除
    $paths = [$image->path, $image->m_image_path, $image->thumbnail_path];
    foreach($paths as $path) {
        if($path && File::exists(public_path($path))) {
            File::delete(public_path($path));
        }
    }

    // データベースから画像レコードの削除
    $image->forceDelete();

    // 削除後、適切なリダイレクト先へ
    return redirect()->route('images.index')
        ->with('success', '画像が削除されました');
}
STEP

filesystems.phpの修正

filesystems.phpに下記を追加してください。

disks' => [
...

  'custom_images' => [
            'driver' => 'local',
            'root' => '/var/www/storage/images',
            'url' => env('APP_URL').'/images',
            'visibility' => 'public',
        ],
],

追加機能

STEP

ダウンロード機能

リソースコントローラーの7つの機能以外に追加機能としてダウンロード機能を追加したくなったので、追加方法をご紹介します。

ダウンロードメソッドの追加

メソッドは、そのままImageControllerの中に追加記述します。

// 画像ダウンロード処理
public function download(String $uuid)
{
    // UUIDに基づいて画像を検索
    $image = Image::where('uuid', $uuid)->firstOrFail();

    // 実際のファイルパスを取得
    $path = '/var/www/storage/images/' . basename($image->path);

    // ファイルが存在するか確認
    if (!File::exists($path)) {
        abort(404);
    }

    // ファイルの内容を取得
    $fileContent = File::get($path);

    // ファイルタイプを取得
    $fileType = File::mimeType($path);

    // 画像をダウンロードするためのレスポンスを作成
    return Response::make($fileContent, 200, [
        'Content-Type' => $fileType,
        'Content-Disposition' => 'attachment; filename="' . basename($image->path) . '"'
    ]);
}

ルーターへの追加記述

リソースControllerに追加のメソッドを記述する場合は、リソースルーターの前に記述して下さい。

// 画像ダウンロード(リソースルートより前に記述する必要がある)
Route::get('/images/download/{id}', [ImageController::class, 'download'])->name('images.download');
// 画像に関するリソースルート CRUD(作成、読み取り、更新、削除)
Route::resource('images', ImageController::class);
STEP

カスタムヘルパー関数の作成

画像の詳細ページに、画像サイズも表示したいと思ったので、画像のサイズを $image->size (バイト単位)からよりわかりやすい単位(例えばKBやMB)に変更して表示しようと思います。そのためには、バイトを適切な単位に変換するカスタムヘルパー関数を作成し、それをビューで使用します。

ヘルパーファイルの作成

app/Helpers ディレクトリを作成し(存在しない場合)、その中に helpers.phpという名前のファイルを作成します。

mkdir -p app/Helpers
sudo vim app/Helspers/helpers.php

下記の様な、formatBytes()関数を作成します。

<?php

if (!function_exists('formatBytes')) {
    function formatBytes($bytes, $precision = 2) {
        $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
        $bytes = max($bytes, 0);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);
        $bytes /= pow(1024, $pow);

        return round($bytes, $precision) . ' ' . $units[$pow];
    }
}

ここで、function_exists チェックを使用して、同じ関数が既に定義されていないかを確認します。これは、関数の重複定義を避けるための一般的な方法です。

パーミッションエラーにならないように下記の様に設定します。

sudo chown www-data:www-data -R app/Helpers
sudo chmod 775 -R app/Helpers

Composer.jsonにファイルをオートロードに追加

composer.json ファイルに files オートロードオプションを追加します。

"autoload": {
    "psr-4": {
        "App\\": "app/"
    },
    "files": [
        "app/Helpers/helpers.php"
    ]
}

Composerのオートロードを更新

以下のコマンドを実行して、Composerのオートロード設定を更新します。

composer dump-autoload

カスタムヘルパー関数の使用

これで、カスタムヘルパー関数のformatBytes()はどこでも使えるようになりました。

試しに、views/images/show.blade.phpの中で使用してみたいと思います。

@extends('layouts.base')

@section('title', '画像詳細')

@section('content')
    <div class="container mx-auto p-4">
        <div class="card card-bordered">
            <figure>
                <img src="{{ asset($image->path) }}" alt="{{ $image->name }}" class="rounded-t-lg">
            </figure> 
            <div class="card-body">
                <h2 class="card-title">{{ $image->name }}</h2>
                <p>サイズ: {{ formatBytes($image->size) }}</p> <!-- ここにサイズを表示 -->
                <!-- ここにその他の画像詳細情報を表示 -->
                <div class="justify-end card-actions">
                    <a href="{{ route('images.index') }}" class="btn btn-primary">一覧に戻る</a>
                    <a href="{{ route('images.edit', $image->id) }}" class="btn btn-success">編集</a>
                    <a href="{{ route('images.download', $image->uuid) }}" class="btn btn-secondary">Download</a>
                    <form action="{{ route('images.destroy', $image->id) }}" method="POST" onsubmit="return confirm('本当に削除しますか?');">
                        @csrf
                        @method('DELETE')
                        <button type="submit" class="btn btn-error">削除</button>
                    </form>
                </div>
            </div>
        </div>
    </div>
@endsection

下記の様にわかりやすい単位で表示できました。

今回は、画像をアップロードして、一覧表示、詳細表示、タイトル名編集、ダウンロード、削除の出来る画像管理アプリを作成しました。

これに、JetStreamのTeam機能で、Team内だけの閲覧や、共有、投稿者情報、コメント機能等を追加すれば、本格的なアプリとして使えると思いますので、よろしければ工夫して作成してみてください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次