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