Laravelで画像をアップロードする機能を作ってみた

Webアプリで何をつくるにしても画像をアップロードする機能だけは外せないですよね。

今回はLaravelで画像アップロード機能の作り方をまとめてみました。

画像アップロード機能の仕様まとめ

  • auth認証でattachmnetを作成
  • 画像アップロードのフォーム作成
  • strageディレクトリに画像をアップロード
  • サムネイル用にトリミングした画像も保存

auth認証でattachmnetを作成

artisanコマンドでコントローラーを作成。

$ php artisan make:controller AttachmentController --resource

コンストラクタにはauthを書きます。

public function __construct()
{
    $this->middleware('auth');
}

routes/web.phpに/admin/attachmentでルーティングの設定。

/admin/attachmentに空白のページが表示されるようになりました。

Route::resource('/admin/attachment', 'AttachmentController');

画像アップロードのフォーム作成

viewをにフォームを作成します。

ディレクトリの一はviewsから後ろは何でもいいですが、とりあえずここに。resources/views/admin/attachemnt/create.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">画像追加</div>
                <div class="card-body">
                    <form action="/admin/attachment" method="post" enctype="multipart/form-data">
                        @csrf
                        <div class="form-group">
                            <input type="file" name="images[]" multiple="multiple">
                        </div>
                        <div class="form-group">
                            <button class="btn btn-block btn-info">決定</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

strageディレクトリに画像をアップロード

データベースにも保存するのでmodelとテーブルを作成します。

$ php artisan make:migration create_attachments_table

スキーマはファイル名とファイルのパスを保存できるようにしておく設定です。

public function up()
{
    Schema::create('attachments', function (Blueprint $table) {
        $table->bigIncrements('id');
        $table->string('name');
        $table->string('path');
        $table->timestamps();
    });
}
$ php artisan migrate

次にモデルの作成します。

$ php artisan make:model Attachment

作成されたモデルに、利用するカラムを設定します。

app/Attachment.php

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

画像は「storage/public/images/年/月/id_画像名」こんな感じでアップロードされるようにしますので、コントローラーのstoreに保存処理を記述していきます。

まず、年/月のディレクトリを設定するために現在の日付を取得して$pathとしておきます。

$d = new \DateTime();
$d->setTimeZone( new \DateTimeZone('Asia/Tokyo'));
$dir = $d->format('Y/m');
$path = sprintf('public/images/%s', $dir);

複数の画像をアップロードできるようにしていますので、postデータを取得しforeachでループさせます。

//postデータ取得
$data = $request->except('_token');
foreach ($data['images'] as $k => $v) {
    //保存処理を記述
}
unset($k, $v)

画像の名前がかぶったときに上書きされない対策として、データベースのIDをファイル名のプレフィックスとして設定すると良いでしょう。

id_画像名ってな感じです。

なので、データベースの最終行に1を足した値が必要になります。

Attachment::::take(1)->orderBy(‘id’, ‘desc’)

これでAttachmentのデータベースからIDを降順で1行だけ取得できます。

$v->getClientOriginalName()は上記のpostデータからファイル名を取得できます。

データベースに何も入っていなかった場合$filenameが空のままなので、IDは1として設定します。

最後にstoreAsで画像を指定の一にアップロードして、ファイル名と画像のパスをデータベースに保存すれば大まかな流れは完成です。

//保存処理を記述部分

$filename = '';

//ID降順の1行だけ取得
$attachments = Attachment::take(1)->orderBy('id', 'desc')->get();
foreach ($attachments as $attachment) {
    //ファイル名
    $filename = $attachment->id + 1 . '_' . $v->getClientOriginalName();
}
unset($attachment);

//データベースが空の場合ファイル名が設定されない
if ($filename == false) {
    $filename = 1 . '_' . $v->getClientOriginalName();
}

//画像の保存
$v->storeAs($path, $filename);

データベースに値を保存

データベースへの保存していきます。

パスを保存する理由は、日付によってディレクトリを変えているというと事と、直接パスの値を引っ張ってくるだけで画像表示を楽にするためです。

//データベースに保存
$attachment_data = [
    'path' => sprintf('/images/%s/', $dir),
    'name' => $filename
];
$a = new Attachment();
$a->fill($attachment_data)->save();

画像の保存場所がstrageなのでドキュメントルートの外になっていますので、publicにシンボリックリンクを作成します。

$ php artisan storage:link

これでデータベースに保存したパスが利用できるようになります。

サムネイル用にトリミングした画像も保存

サムネイルを作成するときにそのまんまの画像は利用しにくいので、トリミングされた画像も保存されるようにします。

保存形式はthumbのプレフィックスを追加してこのようにします。

public/images/年/月/thumb_id_画像名

まず、composerでトリミング用のパッケージをインストールします。

$ composer require intervention/image

config/app.phpのprovidersとaliasesに追記します。

'providers' => [
       ・
       ・ 省略
       ・
       'Intervention\Image\ImageServiceProvider'
],

'aliases' => [
       ・
       ・ 省略
       ・
       'Image' => 'Intervention\Image\Facades\Image'
],

トリミングの利用準備は整いましたので、コントローラーの保存処理にトリミングの記述を追記します。

$storage_path = storage_path(sprintf('app/%s/%s', $path, $filename));
$img = \Image::make($storage_path);
$img->resizeCanvas(150, 150);
$img->save(sprintf('%s/app/%s/thumb_%s', storage_path(), $path, $filename));

サイズは画像の中心を150×150 でトリミングする設定です。

詳しい設定については省きますが、Intervention Imageについて調べてみてください。

正社員という奴隷制度に中指を立てるWebエンジニアです。PHPが得意。繋がれた鎖を断ち切るために、自由を取り戻すために、会社から独立するために、プログラミングスキルを磨く日々です。プログラミングと個人でもできるビジネスについて、情報発信しています。

詳しくはこちら