[Laravel11+LIVEWIRE #8]File Upload(ファイルアップロード)

未分類
LIVEWIRE LOGO

前回[Laravel11+LIVEWIRE #6]ソース整理します(CRUDが一通りできるようにする)で一旦CRUDが一通りできるようにした。

公式はhttps://livewire.laravel.com/docs/uploads参照。

エラーが発生するかも?

私の環境だと、こんなエラーが発生してハマった…

stream_get_meta_data(): Argument #1 ($stream) must be of type resource, bool given

検索していると、sys_get_temp_dirに権限が無いからエラーとなるらしい。そのフォルダどこ?でデバッグしてみると/var/folders/jk/m6g6jhs51sv14n1j38yqzn240000gn/T。確かにパーミッションの設定でアクセス権限が無い。このフォルダのパーミッションを777に変えても良かったんだけど、なんかイヤ!

解決策はphp.inisys_temp_dir/tmpに変更してあげたら動くようになった。

改修開始ぃ!

DBにカラム追加

カラム追加のマイグレーション作成

php artisan make:migration --table posts alter_posts_add_path

database/migrations/yyyy_mm_dd_xxxxx_alter_posts_add_path.php

<?php

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

return new class () extends Migration {
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->text('path')
            ->nullable()
            ->after('message');  // sqliteはafterが効かない。
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->dropColumn('path');
        });
    }
};

マイグレーション実行して、カラムが追加されていることを確認する。できればロールバックもやってカラムが削除されているのを確認するのがよい。

フォーム

app/Livewire/Forms/Post/InputForm.php

まずは入力フォームの解説。入力フォームは登録(追加)と変更(編集)を兼ねている。

<?php

namespace App\Livewire\Forms\Post;

use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
use Livewire\Attributes\Validate;
use Livewire\Form;

use App\Models\Post;

class InputForm extends Form
{
    public ?Post $post;

    public $title = 'たいとる';

    public $message = '';

    public $photo;

    public $isUpdate = false;

    private $kbn = '';

    public function setPost(Post $post)
    {
        $this->isUpdate = false;
        $this->post = $post;
        $this->title = $post->title;
        $this->message = $post->message;
        $this->photo = $post->path;
    }

    public function rules()
    {
        if ($this->kbn == 'store') {
            $titleUnique = Rule::unique('posts');
        } elseif($this->kbn == 'update') {
            $titleUnique = Rule::unique('posts')->ignore($this->post);
        }
        return [
            'title' => [
                'required',
                'min:3',
                $titleUnique,
            ],
            'message' => 'required|min:3',
            'photo' => 'nullable|image|max:1024',
        ];
    }

    public function store()
    {

        $this->kbn = 'store';
        $this->validate();

        $path = $this->photo->store('photos', 'public');

        $post = new Post();
        $post->user_id = 1;
        $post->title = $this->title;
        $post->message = $this->message;
        $post->path = $path;
        $post->save();

        $this->reset();

    }

    public function update()
    {
        $this->kbn = 'update';
        $this->validate();

        $this->post->title = $this->title;
        $this->post->message = $this->message;
        if ($this->isUpdate) {
            $this->post->path = $this->photo->store('photos', 'public');
        }
        $this->post->save();

    }

}

画面でファイルが選択された時点でサーバ側のphotoプロパティにファイルオブジェクトとしてアップされる。編集時の初期はファイル名(String)が格納されている。

isUpdateプロパティは、編集時にファイルがアップされたときはファイル名(String)からファイルオブジェクトに変わるためフラグにて管理している。あ!ビュー側でStringかファイル化の判断で良かったりするのか…

登録(追加)

app/Livewire/Post/Create.php

<?php

namespace App\Livewire\Post;

use App\Livewire\Forms\Post\InputForm;

use Livewire\WithFileUploads;
use Livewire\Component;

use App\Models\Post;

class Create extends Component
{
    use WithFileUploads;

    public InputForm $form;

    public function mount()
    {
        logger()->debug('Create::mount()');
    }

    public function store()
    {
        $this->form->store();
        $this->dispatch('post-created');
    }

    public function render()
    {
        return view('livewire.post.create');
    }
}

WithFileUploadsを使うと宣言してあげる。2行追加のみ。

resources/views/livewire/post/create.blade.php

{{--<div class="ui loading form"> フォーム自体をローディング状態にする場合 --}}
<div class="ui form @if ($errors->count() > 0) error @endif">
    <h2>登録</h2>
    <form wire:submit="store">
        <div class="field">
            <label for="title">Title:</label>
            <input type="text" id="title" wire:model="form.title" placeholder="タイトル">
        </div>
        @error('form.title')
            <div class="ui error message">{{ $message }}</div>
        @enderror
        <div class="field">
            <label for="message">Message</label>
            <textarea id="message" wire:model="form.message" rows="2"></textarea>
        </div>
        @error('form.message')
            <div class="ui error message">{{ $message }}</div>
        @enderror

        <div class="field">
            <input type="file" name="file" wire:model="form.photo" />
        </div>
        @if ($form->photo)
            <img src="{{ $form->photo->temporaryUrl() }}">
        @endif
        @error('form.photo')
            <div class="ui error message">{{ $message }}</div>
        @enderror
        <div class="field">
            <button class="ui primary button" type="submit">Save</button>
            <span wire:loading>Saving...</span>
        </div>
    </form>
    {{ session('status') }}
</div>

ファイルが選択されるとファイルがアップロードされ、$form->photoに格納される。画面上に表示するので、$form->photo->temporaryUrl()でURLを取得し表示する。

変更(編集)

app/Livewire/Post/Edit.php

<?php

namespace App\Livewire\Post;

use App\Livewire\Forms\Post\InputForm;
use Livewire\WithFileUploads;
use Livewire\Attributes\On;
use Livewire\Component;

use App\Models\Post;

class Edit extends Component
{
    use WithFileUploads;

    public InputForm $form;

    #[On('edit-post')]
    public function edit(Post $post)
    {
        logger()->debug('Edit::edit()');
        $this->form->setPost($post);
    }

    public function updated($property)
    {
        logger()->debug('Edit::updated()' . $property);
        if ($property == 'form.image' && $this->form->photo) {
            $this->form->isUpdate = true;
        }
    }

    public function mount(Post $post)
    {
        logger()->debug('Edit::mount()');
        $this->form->setPost($post);
    }

    public function update()
    {
        logger()->debug('Edit::update()');
        $this->form->update();
        $this->dispatch('post-created');
    }

    public function render()
    {
        logger()->debug('Edit::render()');
        return view('livewire.post.edit');
    }
}

EditコンポーネントはWithFileUploadsを使用可能にしてあげるのと、ファイルがアップロードされされた時にisUpdateのフラグを立てないといけないので、LivewireのLifecycleで更新が行われたときに呼ばれるupdatedメソッドでフラグを立ててあげる。

resources/views/livewire/post/edit.blade.php

<div class="ui form @if ($errors->count() > 0) error @endif">
    <h2>更新</h2>
    <form wire:submit="update">
        <div class="field">
            <label for="title">Title:</label>
            <input type="text" id="title" wire:model="form.title" placeholder="タイトル">
        </div>
        @error('form.title')
            <div class="ui error message">{{ $message }}</div>
        @enderror

        <div class="field">
            <label for="message">Message</label>
            <textarea id="message" wire:model="form.message" rows="2"></textarea>
        </div>
        @error('form.message')
            <div class="ui error message">{{ $message }}</div>
        @enderror

        <div class="field">
            <input type="file" name="file" wire:model="form.photo" />
        </div>
        @if ($form->photo)
            @if ($form->isUpdate)
                <img src="{{ $form->photo->temporaryUrl() }}">
            @else
                <img src="{{ Storage::disk('public')->url($form->photo) }}">
            @endif
        @endif
        @error('form.photo')
            <div class="ui error message">{{ $message }}</div>
        @enderror
        <div class="field">
            <button class="ui primary button" type="submit">更新</button>
            <span wire:loading>Updating...</span>
        </div>
    </form>
</div>

初期表示時はファイルパスを取得して、ファイルがアップロードされたらファイルの一時URLを取得して画像を切り替えている。

感想

なかなか苦戦したけどファイルのアップロード処理もできたと思う。

画像以外がアップロードされた時の制御がちゃんとできていないけど、基本的なところはできたのかな。

コメント

タイトルとURLをコピーしました