Laravelに画像をアップしてみよう

目次

概要

画像アップロードはWeb開発において一般的な機能です。Laravelを使って、var/www/imagesに画像を保存し、データベースで管理する方法を解説します。さらに、画像をWebP形式に変換し、サイズ別に保存する方法も紹介します。

STEP

保存場所の作成

Laravelでは、基本的に、storage/app/imagesに画像を保存して、public/storageにシンボリックリンクを張るのが一般的ですが、他のアプリからも参照できるように、/var/www/imagesに保存するようする。

STEP

データベースの作成

imagesテーブルを作成し、id、uuid、ファイル名、ファイルパス、サイズを定義する。

STEP

モデルの作成

Eloquentモデル Image を作成します。

STEP

画像アップロードの実装

  • HTMLフォームの作成。
  • 画像アップロード処理を行うコントローラーの実装。
  • 画像を受け取り、一時的に var/www/images/oldに保存する処理。
STEP

画像をWebPに変換

  • ntervention Imageライブラリを使用して、画像をWebPに変換。
  • 元のサイズ、縦横半分のサイズ、中央を200×200pxにリサイズした画像を生成。
  • 変換した画像を var/www/images に保存。
STEP

画像情報のデータベースへの保存

変換後の画像ファイルの情報(パス、サイズ等)をデータベースに保存する処理。

STEP

画像の表示

保存した画像をWebアプリケーションで表示する方法。

保存場所の作成

STEP

ディレクトリの作成

ターミナルから、VPSサーバーにアクセスして、/var/www/imagesディレクトリと、/var/www/images/oldディレクトリを作成します。

sudo mkdir -p /var/www/images/old

Ubuntuで /var/www/images ディレクトリとそのサブディレクトリ var/www/images/old を同時に作成するには、mkdir コマンドに-p オプションを使用します。このオプションは、指定されたディレクトリと必要なすべての親ディレクトリを同時に作成します。

STEP

パーミッションの設定

Laravelからアクセスできるように、パーミッションを設定します。

// オーナーの変更
sudo chown www-data:www-data -R /var/www/images
// 権限の変更
sudo chmod 775 -R /var/www/images

データベースの作成

STEP

マイグレーションファイルの作成

以下のコマンドを実行して、新しいマイグレーションファイルを生成します。

php artisan make:migration create_images_table
STEP

マイグレーションファイルの編集

生成されたマイグレーションファイルを開き( database/migrations ディレクトリ内)、以下のように編集します。

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->timestamps();
            $table->softDeletes();

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

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

このコードにより、imaesテーブルに以下のカラムが作成されます:

  • id:ユニークな識別子(自動インクリメント)。
  • uuid:ユニークなUUID。
  • name:画像の名前。
  • path:画像の保存パス。
  • size:画像のサイズ(バイト単位)。
  • created_at とupdated_at:タイムスタンプ。
  • deleted_at:論理削除用のタイムスタンプ(ソフトデリート)。
STEP

マイグレーションの実行

編集が完了したら、以下のコマンドを実行してマイグレーションを実行します。

php artisan migrate

これで、指定されたプロパティを持つ images テーブルがデータベースに作成されます。ソフトデリートを使用する場合は、対応するEloquentモデルにも SoftDeletesトレイトを使用する必要があります。

モデルの作成

STEP

モデルの生成

LaravelのArtisanコマンドラインツールを使用して、Image モデルを生成します。ターミナルまたはコマンドプロンプトで以下のコマンドを実行します。

php artisan make:model Image
STEP

モデルの編集

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; // ソフトデリートを使用する場合

class Image extends Model
{
    use SoftDeletes; // ソフトデリートを使用する場合

    protected $fillable = [
        'uuid', 'name', 'path', 'size'
    ];

    // 日付型として扱うカラム
    protected $dates = ['deleted_at'];

    // その他のモデル関連のメソッドやビジネスロジックをここに追加
}
STEP

uuidライブラリのインストール

LaravelでEloquentモデルの uuid フィールドを自動生成するには、モデルの作成イベントにフックしてUUIDを生成・割り当てる処理を追加します。これには、モデルのブートメソッドを使用します。以下に例を示します。

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

composer require ramsey/uuid
STEP

UUID生成処理を追加

次に、モデルクラス( Image )にUUIDを自動生成するロジックを追加します。

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str; // Strクラスをインポート
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str; // UUIDを使用する場合

class Image extends Model
{
    use SoftDeletes;

    protected $fillable = [
        'uuid', 'name', 'path', 'size'
    ];

    // ブートメソッドをオーバーライドして、UUIDを自動生成します。
    protected static function boot()
    {
        parent::boot();

        static::creating(function ($model) {
            // モデルが作成される前に、UUIDを生成して割り当てます。
            if (empty($model->uuid)) {
                $model->uuid = Str::uuid();
            }
        });
    }

    // その他のコード...
}

このコードでは、Image モデルが新しく作成されるときに、uuidカラムが空の場合にのみ新しいUUIDを生成し割り当てています。これにより、モデルを保存する際に自動的にUUIDが設定されます。

STEP

使用例

$image = new Image;
$image->name = 'example.jpg';
$image->path = '/path/to/image.jpg';
$image->size = 12345;
$image->save();

echo $image->uuid; // 自動生成されたUUID

この方法により、LaravelのEloquentモデルで uuid フィールドを自動的に生成・割り当てることができます。

画像アップロードの実装

STEP

レイアウトテンプレートの作成

まず、共通のレイアウトテンプレートを作成します。これは他のBladeビューで再利用するための基本的なHTML構造を提供します。デフォルトのレイアウトテンプレートを使用する場合は、下記は不要です。デフォルトのレイアウトテンプレートは、JetStreamで使用していますので、ここは別途作成したいと思います。

resources/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') {{-- ViteによるCSSの読み込み --}}
</head>
<body class="bg-gray-100">
    <div class="container mx-auto px-4">
        @yield('content')
    </div>
    @vite('resources/js/app.js') {{-- ViteによるJavaScriptの読み込み --}}
</body>
</html>
STEP

画像アップロードビューの作成

次に、実際の画像アップロードフォームを含むビューファイルを作成します。

resources/views/image-upload.blade.php
@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="/upload-image" 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>
@endsection
STEP

ルートの設定

Laravelのルーティングで、画像アップロードページにアクセスできるように設定します。

routes/web.php
use Illuminate\Support\Facades\Route;

Route::get('/image-upload', function () {
    return view('image-upload');
});

画像をWebPに変換

STEP

Intervention Imageライブラリのインストール

ここでは、画像をWebPに変換し、異なるサイズで保存する処理を追加する必要があります。intervention/image ライブラリを使用すると、このような処理を簡単に行うことができます。

composerを利用してIntervention Imageをインストールします。

composer require intervention/image

例えば、元の画像をWebPに変換し、サイズを変更して保存するには以下のようにします

STEP

サービスプロバイダーの登録

Laravel 5.5以降ではサービスプロバイダーが自動的に登録されます。5.4以前を使用している場合、config/app.phpに手動で追加する必要があります。

'providers' => [
    // その他のサービスプロバイダ

    Intervention\Image\ImageServiceProvider::class
],

'aliases' => [
    // その他のクラスエイリアス

    'Image' => Intervention\Image\Facades\Image::class
],
STEP

基本的な使い方

画像の読み込みと変更

画像を読み込んで、リサイズ、回転などの操作を行います。

use Intervention\Image\Facades\Image;

// ファイルパスから画像を読み込む
$image = Image::make(storage_path('path/to/image.jpg'));

// 画像をリサイズ
$image->resize(300, 200);

// 画像を回転
$image->rotate(45);
画像の保存

加工した画像をファイルとして保存します。

$image->save(storage_path('path/to/new_image.jpg'));
画像の出力

ブラウザに画像を直接出力することも可能です。

return $image->response('jpg');
フォーマットの変更

画像のフォーマットを変更して保存することができます。

// 例: WebPフォーマットで保存
$image->encode('webp', 90)->save(storage_path('path/to/image.webp'));
注意点
  • Intervention ImageはGDライブラリまたはImagick PHP拡張に依存しています。サーバーにこれらのいずれか(または両方)がインストールされていることを確認してください。
  • サーバーのリソース制限(メモリなど)に注意しながら大きな画像を処理するようにしてください。必要に応じてPHPのメモリ制限を調整することが必要です。
STEP

画像変換と追加の処理

元の画像をWebPに変換し、サイズを変更して保存するには以下のようにします。

use Intervention\Image\Facades\Image as ImageIntervention;

// ...

$image = ImageIntervention::make($file)->encode('webp', 90); // 90は品質

// 元のサイズで保存
$image->save(storage_path('app/public/images/' . $filename . '.webp'));

// 縦横半分のサイズで保存
$image->resize($image->width() / 2, $image->height() / 2)->save(storage_path('app/public/images/' . $filename . '_half.webp'));

// 200x200で中央をクロップして保存
$image->fit(200, 200)->save(storage_path('app/public/images/' . $filename . '_thumb.webp'));

画像情報のデータベースへの保存

STEP

コントローラの生成

まず、画像アップロード用のコントローラを生成します。以下のコマンドをターミナルで実行してください。

php artisan make:controller ImageUploadController

このコマンドは、app/Http/Controllers ディレクトリに ImageUploadController.php という新しいコントローラファイルを生成します。

STEP

コントローラの編集

ImageUploadController.php を開き、画像アップロードのロジックを実装します。

app/Http/Controllers/ImageUploadController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Image; // 事前に作成したImageモデル
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Intervention\Image\Facades\Image as ImageIntervention;

class ImageUploadController extends Controller
{
    public function upload(Request $request)
    {
        $request->validate([
            'image' => 'required|image|max:2048', // 画像ファイルのバリデーション
        ]);

        $file = $request->file('image');
        $originalName = $file->getClientOriginalName();
        $filename = time() . '_' . $originalName;
        // $size = $file->getSize();

        // 画像の保存処理
        $oldPath = $file->storeAs('images/old', $filename, 'public');

        // ここで画像をWebPに変換し、サイズに合わせて保存
        $image = ImageIntervention::make($file)->encode('webp', 80); // 80は品質

        // 元のサイズで保存
        // $originalNameの.pngを削除する
        $filename = str_replace('.png', '', $originalName);
        $image->save(storage_path('app/public/images/' . $filename . '.webp'));
        $newName = $filename . '.webp';
        $newPath = 'images/' . $newName;
        $size = $image->filesize();
        
        // 縦横半分のサイズで保存
        $image->resize($image->width() / 2, $image->height() / 2)->save(storage_path('app/public/images/' . $filename . '_half.webp'));
        
        // 200x200で中央をクロップして保存
        $image->fit(200, 200)->save(storage_path('app/public/images/' . $filename . '_thumb.webp'));

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

        return $this->show($image->id)->with('success', '画像がアップロードされました。');
    }

    // 画像の表示
    public function show($id)
    {
        $image = Image::findOrFail($id);
        return view('image-show', ['image' => $image]);
    }
}
STEP

ルーターの作成

ImageUploadController の upload メソッドに対するルーティングを作成します。

routes/web.php
use App\Http\Controllers\ImageUploadController;

// 画像アップロード用のルート
Route::post('/upload-image', [ImageUploadController::class, 'upload']);

画像の表示

STEP

画像のパスの取得

Bladeテンプレートで画像を表示するには、<img>タグの src 属性に保存した画像のパスをデータベースから取得します。

STEP

画像の表示

例えば、Image モデルから取得した画像データを表示するには、以下のようにします。

<img src="{{ asset($image->path) }}" alt="画像の説明">
STEP

showメソッドの作成

まず、ImageUploadController に画像を表示するための show メソッドを追加します。このメソッドでは、特定の画像のデータをデータベースから取得し、それをビューに渡します。

app/Http/Controllers/ImageUploadController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Image;

class ImageUploadController extends Controller
{
    // その他のメソッド...

    public function show($id)
    {
        $image = Image::findOrFail($id);
        return view('image-show', ['image' => $image]);
    }
}

このメソッドでは、指定されたIDを持つ画像を Image モデルを使用して検索し、見つかった画像オブジェクトを imge-show ビューに渡しています。findOrFail メソッドは、指定されたIDの画像が存在しない場合に404エラーを返します。

STEP

ビューファイルの作成

次に、画像を表示するためのBladeビューファイル image-show.blade.php を作成します。

@extends('layouts.base')

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

@section('content')
    <div class="max-w-md mx-auto bg-white p-5 rounded-md shadow-sm">
        <div class="text-center mb-4">
            <h1 class="text-xl font-semibold text-gray-700">画像詳細</h1>
        </div>
        <img src="{{ asset('storage/' . $image->path) }}" alt="{{ $image->name }}" class="w-full h-auto">

        <p class="text-center text-gray-600 mt-4">{{ $image->name }}</p>
    </div>
@endsection

このビューファイルでは、@extends ディレクティブを使って base.blade.php のレイアウトを継承し、@section(‘content’) で画像の詳細を表示しています。@section(‘title’,’画像詳細’) を使用してページのタイトルも指定しています。

STEP

ルートの設定

最後に、この show メソッドにアクセスするためのルートを設定します。

routes/web.php
use App\Http\Controllers\ImageUploadController;

Route::get('/images/{id}', [ImageUploadController::class, 'show']);

GDライブラリ

STEP

GD Library extension not available with this PHP installation.

このエラーメッセージは、PHPのGDライブラリがインストールまたは有効化されていないことを示しています。GDライブラリは、PHPで画像処理を行う際に必要な拡張機能です。このエラーを解決するには、GDライブラリをインストールし、PHP設定で有効にする必要があります。

GDライブラリのインストール

Linux (Ubuntu/Debian) の場合

以下のコマンドを実行して、GDライブラリをインストールします

sudo apt update
sudo apt -y upgrade
sudo apt -y install php-gd
Windowsの場合
  • php.ini ファイルを開きます(通常はPHPのインストールディレクトリ内にあります)。
  • ;extension=gd という行を探し、コメントアウト(先頭のセミコロン ; を削除)して有効化します。
  • 変更を保存して、Webサーバー(Apache, Nginx, など)を再起動します。
macOS の場合

Homebrewを使用している場合は、以下のコマンドでGDライブラリをインストールできます。

brew install php@8.2-gd

ここで、php@8.2-gd はインストールしているPHPのバージョンに応じて変更してください。

PHPの再起動

GDライブラリをインストールした後、変更を有効にするためにPHPを再起動する必要があります。Webサーバー(Apache, Nginxなど)を使用している場合は、そのサーバーを再起動してください。

例えば、Apacheを使用している場合は以下のコマンドを実行します。

sudo service apache2 restart

Nginxを利用している場合は、以下のコマンドを実行します。

sudo systemctl restart nginx

これで、GDライブラリがインストールされ有効化され、Laravelアプリケーションで画像処理が行えるようになります。エラーが解消されていることを確認するために、Laravelアプリケーションを再度実行してください。

保存先の変更

STEP

保存先の変更

現状は、Laravelのstorage/app/publicに画像データは保存されています。これを当初作成した、/var/www/storage/imagesディレクトリに保存する為に下記の様に修正します。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Image; // 事前に作成したImageモデル
use Illuminate\Support\Str;
use Intervention\Image\Facades\Image as ImageIntervention;

class ImageUploadController extends Controller
{
    public function upload(Request $request)
    {
        $request->validate([
            'image' => 'required|image|max:2048', // 画像ファイルのバリデーション
        ]);

        $file = $request->file('image');
        $originalName = $file->getClientOriginalName();
        
        // 画像の保存先パスを定義
        $image_path = '/var/www/storage/images';
        $old_path = '/var/www/storage/images/old';

        // 画像の保存処理
        $oldImagePath = $old_path . '/' . $originalName;
        $file->move($old_path, $originalName);

        // Intervention Imageを使用して画像を処理
        $image = ImageIntervention::make($oldImagePath)->encode('webp', 80);
        
        // 元のサイズで保存
        $filename = str_replace('.png', '', $originalName);
        $image->save($image_path . '/' . $filename . '.webp');
        $newName = $filename . '.webp';
        $newPath = 'images/' . $newName;
        $size = $image->filesize();

        // 縦横半分のサイズで保存
        $image->resize($image->width() / 2, $image->height() / 2)->save($image_path . '/' . $filename . '_half.webp');
        
        // 200x200で中央をクロップして保存
        $image->fit(200, 200)->save($image_path . '/' . $filename . '_thumb.webp');

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

        return $this->show($image->id)->with('success', '画像がアップロードされました。');
    }

    // 画像の表示
    public function show($id)
    {
        $image = Image::findOrFail($id);
        return view('image-show', ['image' => $image]);
    }
}
STEP

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

Laravelから/var/www/imagesへのシンボリックリンクを作成します。

ln -s /var/www/storage/images /path/to/laravel-project/public

Laravelからimagesディレクトリへアクセスできるようにオーナー変更します。

sudo chown -R www-data:www-data /var/www
STEP

ビューファイルの修正

@extends('layouts.base')

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

@section('content')
<div class="max-w-md mx-auto bg-white p-5 rounded-md shadow-sm">
    <div class="text-center mb-4">
        <h1 class="text-xl font-semibold text-gray-700">画像詳細</h1>
    </div>
    <img src="{{ asset('storage/'.$image->path) }}" alt="{{ $image->name }}" class="w-full h-auto">
    <p class="text-center text-gray-600 mt-4">{{ $image->name }}</p>
</div>
<!-- セッションメッセージの表示 -->
@if (session('success'))
<div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4 mt-4" role="alert">
    <p>{{ session('success') }}</p>
</div>
@endif
@endsection

セッションメッセージを追加しました。

Storageファサードの利用

Laravelで画像を保存する際に、ストレージパスをカスタマイズして Storage ファサードを使う方法を説明します。ここでは、/var/www/storage/images と /var/www/storage/images/old に画像を保存するように設定します。

STEP

ストレージディスクの設定

まず、config/filesystems.php にカスタムストレージディスクを設定します。

'disks' => [

    // その他のディスク設定...

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

    'old_images' => [
        'driver' => 'local',
        'root' => '/var/www/storage/images/old',
        'visibility' => 'public',
    ],
],
STEP

ImageUploadControllerの修正

次に、ImageUploasController で Storage ファサードを使用して画像を保存します。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Image; // 事前に作成したImageモデル
use Illuminate\Support\Str;
use Intervention\Image\Facades\Image as ImageIntervention;
use Illuminate\Support\Facades\Storage;

class ImageUploadController extends Controller
{
    public function upload(Request $request)
    {
        $request->validate([
            'image' => 'required|image|max:2048', // 画像ファイルのバリデーション
        ]);

        $file = $request->file('image');
        $originalName = $file->getClientOriginalName();

        // 古い画像を保存
        $oldImagePath = Storage::disk('old_images')->putFileAs('', $file, $originalName);

        // Intervention Imageを使用して画像を処理
        $image = ImageIntervention::make($file)->encode('webp', 80);

        // 元のサイズでWebP保存
        $filename = str_replace('.png', '', $originalName);
        $timestamp = time(); // タイムスタンプの取得
        $newName = $timestamp . '_' . $filename . '.webp'; // タイムスタンプをファイル名に付与
        Storage::disk('custom_images')->put($newName, (string) $image);
        $newPath = 'images/';
        $size = $image->filesize();

        // 縦横半分のサイズでWebP保存
        $image->resize($image->width() / 2, $image->height() / 2)->save($newPath . '/' . $timestamp . $filename . '_half.webp');

        // 200x200で中央をクロップしてWebP保存
        $image->fit(200, 200)->save($newPath . '/' . $timestamp . $filename . '_thumb.webp');

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

        // return $this->show($image->id);
        return back()->with('success', '画像がアップロードされました。');
    }

    // 画像の表示
    public function show($id)
    {
        $image = Image::findOrFail($id);
        return view('image-show', ['image' => $image]);
    }
}

このコードでは、Storage::disk(‘custom_images’)->put( …) と Storage::disk(‘old_images’)->putFilesAs(…) を使用して、それぞれカスタマイズしたストレージディスクに画像を保存しています。

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

コメント

コメントする

目次