KoudouBlogs

元警察官 / 現ITエンジニア 宅トレ発信

『Laravel』画像アップロード機能を実装するための考え方MEMO

はじめに

 仕事で実装を始めると、どうしてもスケジュールに間に合うことを優先してしまい、動けばよいで雑なコードいわゆる糞コードを書いてしまいがちです。

 リファクタリングする前に案件終了してしまい、そのまま糞コードが納品されるということはよくあります。

 糞コードを減らすために、自分の書いたコードの振り返りの意味で記事を書きます。

 考え方がメインの記事です、コードは参考程度になります。

 

 定番中の定番。記事を書くときの相棒です。

 PHPを始める方、既に業務でされている方にもおすすめです。

【新品】【本】独習PHP 山田祥寛/著

価格:3,520円
(2020/7/5 10:44時点)
感想(0件)


機能要件を考える

前提

 今回は、「画像の名前」と「画像」を登録するだけのシンプルな機能なので、機能も必要最小限にします。

 業務なので、個人開発と違い、ただ良いものを作るのではなく、予算と期間の中できる範囲のものを作る必要があります。

必要

  • 一覧 
  • 登録
  • バリデーション(サーバー)
  • 削除

不要

  • 検索
  • ページネーション
  • 確認画面
  • 編集
  • バリデーション(フロント)

具体案

 登録機能を実装するうえで、まず考えるのが画面遷移の有り無し。

 一般的な登録機能は

  • 一覧機能から新規登録画面へのリンク
  • 新規登録画面
  • 確認画面
  • 登録完了画面

で構成されているが、今回は

  • 一覧画面と新規登録画面を同居させる。
  • 確認画面、登録完了画面は無し。
  • 間違って削除してはいけないので確認をモーダル・ajaxで実装
  • バリデーションはvalidatorファサードを使用する。

とします。

 画像の保存方法は、大きく分けて

  • DBに直接画像を登録する方法
  • DBには画像ファイルのパスを登録し、画像はファイルとして保存する方法

がありますが、今回は後者を選択します。

 ちなみに前者ですと、確認画面で画像を確認するために、画像ファイルを一時的に/tmpに保存しておき、登録の際に正規の保存先に移動してやる必要があります。

 それがないだけでも工数削減できますし、そもそもユーザー的にも必要ない機能なのでいらないです。

テーブル定義を考える

  • 管理方法
    マイグレーションを利用
  • テーブル名
    image_files
  • 画像登録特有のフィールド
    ○○_id:画像を何か一意になるキー単位で保存したい場合は必要
    file_name:ファイル名
    file_path:ファイルを保存するパス
  • laravelの規約で必要なフィールド
    id
    timestamps
    softDeletes

コントローラのメソッド構成を考える

  • 一覧表示用(public)
    index()
  • 登録処理用(public)
    store()
  • 削除時の確認表示用(public)
    edit()
  • 削除処理用(public)
    delete()
  • バリデーション用(private)
    validateFileToUpload()

コントローラのメソッド処理を考える

  • 一覧表示用 index()
    ・対象のモデルからデータを取得する
    ・bladeに取得した値を渡して、ブラウザに表示させる
<?php
$ImgFiles = ImgFile::where('○○_id', $○○_id)->get();
return view('routeパス名', ['パラメータ名' => $○○])->with(○○);
?>
  • 登録処理用 store()

 1.【バリデーション】不正なPOST値がないかチェックする。
  不正な値があった場合:「blade」に「エラーメッセージ」と「POST値」を渡してブラウザに表示させる。

<?php
// 入力エラーの場合はエラー表示
if($validator->fails())
{
 return view('route名', ['[パラメータ名]' => $○○_id])
 ->with('○○_id',$○○_id)
 ->withErrors($validator);
}
?>


 2.【DB】ファイル名(file_name)をinsertします。

<?php
$ImageFile = new ImageFile;
$ImageFile->file_name = $request->fileName;
$ImageFile->save();
?>

       
 3.【ディレクトリ】ファイルを保存するディレクトリが無ければ作成します。

<?php
$storagePath  = Storage::disk('local')->getDriver()->getAdapter()->getPathPrefix();
$fullStoragePath = $storagePath . 'public/[必要があればディレクトリを分ける]/' . $insertImageFile->○○_id;

if (!file_exists($fullStoragePath)) {
    mkdir($fullStoragePath, 0777);
}
?>

 

 4.【DB】ファイルを参照するためのfile_pathカラムをupdateします。

<?php
/**以下の2つを保存用のファイル名にします。
/*・最後に登録したPK
/*・ファイルの拡張子
/*・例:「1.jpeg」
*/
$lastInsertedId = $InsertImageFile->id;
$ext = $request->file('fileToUpload')->guessExtension();

/**
/*参照用(シンボリックリンク)のパス作成します
*/
$publicPath = '/storage/[必要があればディレクトリを分ける]/' . ImageFile->○○_id . '/' .$lastInsertedId.'.'.$ext;

/**
/*作成したパスをDBにupdateします。
*/
$UpdateImageFile = ImageFile::find($lastInsertedId);
$UpdateImageFile->path = $publicPath;
$UpdateImageFile->save();
?>

 ※ちなみにLaravelでは以下のコマンドでpublic/storageからstorage/app/publicへのシンボリックリンクの設定ができます。

$ php artisan storage:link

 5.【ファイル】アップロードしたファイルを保存用のディレクトに保存します。

<?php
$request->file('fileToUpload')->move($fullStoragePath, $lastInsertedId.'.'.$ext);
?>

 6.【リダイレクト】登録が終了したら一覧画面に戻る処理を行います。

 リダイレクトはブラウザにリクエストを再送させます。 

<?php
return redirect(route('[route名]', ['パラメータ名' => $○○_id]));
?>

 

 

 7.【例外処理】try~catch命令

  try,catchでそのエラーをキャッチすれば、DB接続情報の漏洩を防ぐことができます。


 8.【トランザクション処理】

  DBでエラーが発生した場合を考慮してトランザクションは必須です。

<?php
try {
       \DB::beginTransaction();
       //※処理
       \DB::commit();
        } catch (\Exception $e) {
            \DB::rollback();
            throw $e;
        }
?>
  • 削除時の確認表示用(public)
    edit()
  • 削除処理用(public)
    delete()
  • バリデーション用(private)
    validateFileToUpload()

bladeはざっくりこんな感じ

<?php
<form action="{{ url('○○') }}" method="POST" enctype="multipart/form-data">
    @csrf
 
    <p>ファイル名:<input type="text" name="fileName" value="{{old('fileName')}}"></p>
    @if ($errors->has('fileName'))
    <p>{{$errors->first('fileName')}}</p>
    @endif
 
    <p>画像:<input type="file" name="fileToUpload" value="{{old('fileToUpload')}}"></p>
    @if ($errors->has('fileToUpload'))
    <p>{{$errors->first('fileToUpload')}}</p>
    @endif
 
    <input type="submit" value="確認する">
</form>
?>

終わり

 仕事では使用する言語フレームワークはコロコロ変わるので、実装時の考え方を記録しておくことはとても有用です。

 個人的には時間は資格試験の勉強に割くより、実務の振り返り予習に時間を割く方が確実に成長すると思っています。