見出し画像

php5なuploaderをlaravelに移植してauroraで使うとか(3) - DBの読み書き含む一通りのapp実装

前回

storeメソッド

さて今こんな感じのダミーを作ってあった。このstoreは見ての通り何も保存せずに保存した事にして返している。

    public function store(Request $request)
    {
        // dd($request->all());
        return redirect(route('uploaders.index'))
            ->with(['status' => __('File uploaded')]);
    }

つわけで「File uploaded」とか言い張っているが全く何もしていない。何かしていこう。

validationとfileの取得

    public function store(Request $request)
    {
        $request->validate([
            'file' => 'required|file',
        ]);
        $file = $request->file('file');
        dd($file);

        exit;

validationはHttp Requestを自作してもいいけど、この程度の1フィールドで特殊なことをしないのであればこんなんで十分だろう。ただし、jpegとか画像に制限したりサイズがどうのこうのとかもっと特殊な事をしたいのであればForm Requestを作成する事をおすすめするぞい。

$file変数につめこまれた

データーを整形する

        $data = [
            'original_name' => $originalName,
            'saved_name'    => $savedName,
            'mime_type'     => $mime,
            'size'          => $size,
        ];

こういったデーターを目指していく。取得した$file変数からある程度情報をgetできる(矢印をあわせてるのは趣味の領域なので別に合わせなくてもいいよ)。

でまあこんな感じとした

    public function store(Request $request)
    {
        $request->validate([
            'file' => 'required|file',
        ]);
        $file = $request->file('file');

        $originalName = $file->getClientOriginalName();
        $mime = $file->getMimeType();
        $size = $file->getSize();
        $savedName = $savedName = \Str::random(10).md5($originalName);
        $data = [
            'original_name' => $originalName,
            'saved_name'    => $savedName,
            'mime_type'     => $mime,
            'size'          => $size,
        ];
        dd($data);

このsaved_nameとりあえず被らない文字列なら何だっていいんだけど、まあこんなのが被らないだろって感じのハッシュにしておいた。とりあえずこうなっている

array:4 [▼ // app/Http/Controllers/UploaderController.php:38
  "original_name" => "
DALL·E 2024-02-18 12.11.47 - An anime girl character personifying the AWS Aurora service, represented by the instance type t4g.medium. She has been operational 
 ▶
"
  "saved_name" => "PJPemOKSr0399d9ffe238fdb2327de5f98c0998213"
  "mime_type" => "image/webp"
  "size" => 387050
]

これを保存していく。

保存と取り出し

    public function store(Request $request)
    {
        $request->validate([
            'file' => 'required|file',
        ]);
        $file = $request->file('file');

        $originalName = $file->getClientOriginalName();
        $mime = $file->getMimeType();
        $size = $file->getSize();
        $savedName = $savedName = \Str::random(10).md5($originalName);
        $data = [
            'original_name' => $originalName,
            'saved_name'    => $savedName,
            'mime_type'     => $mime,
            'size'          => $size,
        ];

        \DB::beginTransaction();
        $uploadedFile = UploadedFile::create($data);
        dd($uploadedFile);
        exit;

このように保存しにいく。そうする

いつものModelにプロパティーが足りないぞ警告

など言われるので、モデルを変更していく。

app/Models/UploadedFile.php 

class UploadedFile extends Model
{
    use HasFactory;

    protected $fillable = [
        'original_name',
        'saved_name',
        'mime_type',
        'size',
    ];
}

みたいな$fillableを定義しておく。これでinsertできるはずだ


insert後のrowsetをdumpした

ここでは以前のphp5で作ったold style uploaderに仕様を合わせているので、これに基いて再度ファイル名を変更し、保存からのupdateを行う。

    public function store(Request $request)
    {
        $request->validate([
            'file' => 'required|file',
        ]);
        $file = $request->file('file');

        $originalName = $file->getClientOriginalName();
        $mime = $file->getMimeType();
        $size = $file->getSize();
        $savedName = $savedName = \Str::random(10).md5($originalName);
        $data = [
            'original_name' => $originalName,
            'saved_name'    => $savedName,
            'mime_type'     => $mime,
            'size'          => $size,
        ];

        \DB::beginTransaction();
        $uploadedFile = UploadedFile::create($data);
        $extension = $file->getClientOriginalExtension();
        $savedName = sprintf('%05d.%s', $uploadedFile->id, $extension);
        $path = $file->storeAs('uploaded_files', $savedName, 'public');

        // saved_nameを更新
        $uploadedFile->update(['saved_name' => $savedName]);
        \DB::commit();


        // dd($request->all());
        return redirect(route('uploaders.index'))
            ->with(['status' => __('File uploaded')]);
    }

こんな感じの奴が最終的なコードとなる

保存されたか確認

tinkerで掘っといてみよう。

artisan tinker

あるいは

% ./vendor/bin/sail tinker
Psy Shell v0.12.0 (PHP 8.3.3-1+ubuntu22.04.1+deb.sury.org+1 — cli) by Justin Hileman
>

みたいなPsy Shellを起動して確認する、なお

Psy Shell v0.12.0 (PHP 8.3.3-1+ubuntu22.04.1+deb.sury.org+1 — cli) by Justin Hileman
> UploadedFile::all()

   Error  Class "UploadedFile" not found.

とかいわれた場合はdump-autoloadする

% sudo ./vendor/bin/sail composer dump-autoload
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi

   INFO  Discovering packages.

  laravel/breeze ............................................................... DONE
  laravel/sail ................................................................. DONE
  laravel/sanctum .............................................................. DONE
  laravel/tinker ............................................................... DONE
  nesbot/carbon ................................................................ DONE
  nunomaduro/collision ......................................................... DONE
  nunomaduro/termwind .......................................................... DONE
  spatie/laravel-ignition ...................................................... DONE

Generated optimized autoload files containing 6031 classes

再実行すると

> UploadedFile::all()
[!] Aliasing 'UploadedFile' to 'App\Models\UploadedFile' for this Tinker session.
= Illuminate\Database\Eloquent\Collection {#5984
    all: [
      App\Models\UploadedFile {#5986
        id: 9,
        original_name: "DALL·E 2024-02-18 12.11.47 - An anime girl character personifying the AWS Aurora service, represented by the instance type t4g.medium. She has been operational for several years, .webp",
        saved_name: "00009.webp",
        mime_type: "image/webp",
        size: 387050,
        created_at: "2024-02-18 18:22:34",
        updated_at: "2024-02-18 18:22:34",
      },
    ],
  }

こんなんなってDBに入ってる事が確認できればok。さらにファイルシステムの確認として、

% find storage/app/public
storage/app/public
storage/app/public/.gitignore
storage/app/public/uploaded_files
storage/app/public/uploaded_files/00009.webp

こんなんが入ってればok

取り出し

indexでこのデーターを取ってくる。今回は雑に全部取る(前の奴の仕様がそうだったので)。とはいえall()を使うのもアレなのでlatest()で取る

    public function index()
    {
        $uploadedFiles = UploadedFile::latest()->get();
        return view('uploaders.index', ['uploadedFiles' => $uploadedFiles]);
    }

viewの更新

<x-uploader-layout>
  <div class="max-w-2xl mx-auto py-10 px-6 bg-white rounded-lg shadow-md">
    <h1 class="text-2xl font-semibold text-gray-700 mb-5">
      <a href="{{ route('uploaders.index') }}" class="hover:underline">Simple Uploader</a>
    </h1>
    <x-auth-session-status class="mb-4" :status="session('status')" />

    <form method="POST" action="{{ route('uploaders.store') }}" enctype="multipart/form-data" class="space-y-6">
      @csrf

      <div>
        <x-input-label for="file" :value="__('File')" class="block text-sm font-medium text-gray-700" />
        <div class="mt-1 flex items-center">
          <input type="file" id="file" class="block w-full text-sm text-gray-500
          file:mr-4 file:py-2 file:px-4
          file:rounded-full file:border-0
          file:text-sm file:font-semibold
          file:bg-violet-50 file:text-violet-700
          hover:file:bg-violet-100" name="file" required autofocus />
        </div>
        <x-input-error :messages="$errors->get('file')" class="mt-2" />
      </div>

      <div class="flex items-center justify-end mt-4">
        <x-primary-button>
          {{ __('Upload') }}
        </x-primary-button>
      </div>
    </form>
    <div class="mb-8">
      <h2 class="text-xl font-semibold text-gray-600 mb-3">Uploaded Files</h2>

      <div class="overflow-x-auto">
        <table class="min-w-full leading-normal">
          <thead>
            <tr>
              <th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
                {{ __('File Name') }}
              </th>
              <th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
                {{ __('Size') }}
              </th>
              <th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100"></th>
            </tr>
          </thead>
          <tbody>
            @forelse($uploadedFiles as $file)
              <tr>
                <td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
                  <div class="flex items-center">
                    <div class="ml-3">
                      <p class="text-gray-900 whitespace-no-wrap">
                      <a href="{{ Storage::url('uploaded_files/'. $file->saved_name) }}" class="text-blue-600 hover:text-blue-900">{{ $file->saved_name }}</a>

                      </p>
                    </div>
                  </div>
                </td>
                <td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
                  <p class="text-gray-900 whitespace-no-wrap">
                    {{ $file->size }}
                  </p>
                </td>
                <td class="px-5 py-5 border-b border-gray-200 bg-white text-sm text-right">
                  <a>{{ __('Delete') }}</a>
                </td>
              </tr>
            @empty
              <tr>
                <td colspan="3" class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
                  <p class="text-gray-900 whitespace-no-wrap text-center">
                    {{ __('No files uploaded yet.') }}
                  </p>
                </td>
              </tr>
            @endforelse
          </tbody>
        </table>
      </div>
    </div>
  </div>
</x-uploader-layout>


まあまあ出来てきたね

とまあこんな感じとなるわけであるがDeleteは未実装であーる、し、リンクも機能してはいない。

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

まあ今回の件において、これはもうこれでいいだろ

php artisan storage:link

以下はsailでの実行例

% ./vendor/bin/sail artisan storage:link

   INFO  The [public/storage] link has been connected to [storage/app/public].

これでファイルにアクセスできるはず。

削除ボタン

既存のdanger buttonコンポを使った方が早いやな

<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm text-right">
  <form method="POST" action="{{ route('uploaders.destroy', $file->id) }}" onsubmit="return confirm('{{ __('Are you sure you want to delete this file?') }}')">
    @csrf
    @method('DELETE')
    <x-danger-button>{{ __('Delete') }}</x-danger-button>
  </form>
</td>
DELETEボタンを速攻で作った

削除の実装

    public function destroy(UploadedFile $uploader)
    {
        \Storage::delete('uploaded_files/' . $uploader->saved_name);
        $uploader->delete();
        return redirect()->route('uploaders.index')
                         ->with('status', __('File deleted successfully.'));
    }

とまあこんな感じで一通りの機能が移植出来たんじゃないかな。ところで最近laravelでテストの記事をよく目にするんで、その辺についても今回は書いてみようかな。






この記事が気に入ったらサポートをしてみませんか?