前回[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.iniのsys_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を取得して画像を切り替えている。
感想
なかなか苦戦したけどファイルのアップロード処理もできたと思う。
画像以外がアップロードされた時の制御がちゃんとできていないけど、基本的なところはできたのかな。

  
  
  
  

コメント