LoginSignup
1
0

More than 1 year has passed since last update.

【Laravel】 MySQLにファイルをバイナリで保存・表示する

Last updated at Posted at 2022-12-27

バイナリをMySQLに保存することの是非については今回扱いません
また既存のHomeController,home.blade.phpを編集する形で進めていきます

環境

  • Laravel: 8.6.2
  • Vue.js: 2.6.12
  • PHP: 8.0.8
  • Composer: 2.5.1
  • Node.js: 19.3.0
$ composer create-project --prefer-dist laravel/laravel:^8.0 project-name
$ cd project-name
$ php artisan migrate:fresh
$ composer require laravel/ui
$ php artisan ui bootstrap
$ npm install && npm run dev
$ php artisan serve

Tips.Laravel Pintの導入

せっかくなのでLaravel専用のFormatterであるLaravel Pintを導入します
プロジェクトルートに以下のコマンドを入力します

$ composer require laravel/pint --dev

フォーマットをかけたい時は、適宜以下のコマンドを入力しましょう
使い心地が良ければ、aliasを設定してみてください

$ vendor/bin/pint -v

1. テーブルを追加する(Migration, Model)

ファイルを保存するためのテーブル(とついでにModel)を作成します

$ php artisan make:model File --migration

まずmigrationファイルを以下のように編集します

create_files_table
<?php

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

class CreateFilesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('files', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->binary('content');
            $table->timestamps();
        });
        DB::statement('ALTER TABLE files MODIFY content LONGBLOB not null');
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('files');
    }
}

BlueprintのbinaryメソッドによってBLOBのカラムを作成することができますが、カラムサイズが小さいため満足に機能しない可能性があります
悲しいことにスキーマビルダのBlueprintにはLONGBLOBカラムを作成するメソッドが用意されていないので、素のSQLを流し込みます

次にModelを編集します

File.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class File extends Model
{
    protected $fillable = ['name', 'content']; // <-- 追加
}

2. 画面を用意する(View, Routing)

まずhome.blade.phpを編集します

home.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 method="POST" action="{{ route('store') }}" enctype="multipart/form-data">
                            @csrf
                            <input type="file" name="file">
                            <button type="submit">登録</button>
                        </form>
                        @if(is_null($files) === false)
                            @foreach ($files as $row)
                                <a href="{{ route('streamFile', ['file' => $row]) }}">{{ $row->name }}</a>
                            @endforeach
                        @endif
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

保存部分だけでなく表示部分も一緒に書いておきます
次にRoutingを編集します

web.php
// ...
Route::post('/store', [App\Http\Controllers\HomeController::class, 'store'])->name('store');
Route::get('/file/{file}', [App\Http\Controllers\HomeController::class, 'streamFile'])->name('streamFile');

便利なのでルートモデルバインディングを使用しています
処理の階層構造の考え方によっては、この仕組みはControllerでDBを参照することになるので許されませんが、今回はデモということで使います

3. 保存処理を追加する

Routingで定義したstoreメソッドを書いていきます

HomeController.php
// ...
    public function store(Request $request, File $file)
    {
        if (empty($request->file) === false) {
            $file->name = $request->file('file')->getClientOriginalName();
            $file->content = $request->file('file')->get();
            $file->save();
        }

        return redirect()->route('home');
    }

DIもじゃんじゃん使っていきます

4. 表示処理を追加する

Routingで定義したstreamFileメソッドを書いていきます

HomeController.php
// ...
    public function streamFile(File $file)
    {
        $callback = function () use ($file) {
            // 出力バッファをopen
            $stream = fopen('php://output', 'w');
            fwrite($stream, $file->content);
            fclose($stream);
        };

        $header = [
            'Content-Type' => 'application/octet-stream',
        ];

        return response()->streamDownload($callback, $file->name, $header);
    }

表示画面作成時に$filesを追記したので、indexメソッドにも追記します

HomeController.php
// ...
   public function index()
    {
        $files = File::all();
        return view('home', compact('files'));
    }

end.Githubのリポジトリ

記事内で作成したもののソースコードは以下のリポジトリに公開しています

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0