Laravelプロジェクト作成
laravel new hoge
上記実行中に、JetstreamやLivewireなどを選択しました。Databaseもpostgresqlを選択しました。 しばらくすると、プロジェクトが作成されており、.envも作成済みでした。
DB作成・設定
psql -U postgres # create database hoge
上記でhogeデータベースを作成し、.envの下記を設定します。
DB_CONNECTION=pgsql DB_HOST=127.0.0.1 DB_PORT=5432 DB_DATABASE=hoge DB_USERNAME=postgres DB_PASSWORD=password
マイグレーションを実行して、テーブルを作成し、サーバを起動します。
php artisan migrate php artisan serve
Jetstreamのデフォルト画面
Jetstreamが入ってますので、ホームページの右上にlogin, registerというのがあります。
Seederでユーザを作成しログインしましょー。
php artisan db:seed
これで、下記ユーザでログインできます。
test@example.com password
ログインするとこのようなDashboardが表示されます。
プロフィール編集画面はこのような感じです。
もちろんこのまま使えますが、今回は、maryUIを入れて、管理画面をデザインを変えながら作っていきたいと思います。
maryUIを入れてみる
これです。
composer require robsontenorio/mary php artisan mary:install pnpm dev
これでインストールは基本完了です。 インストール中に、どうもJetstreamが入っていることを検知して、config/mary.phpを自動で作成し、プレフィックス設定までしてくれているっぽいです。多分。下記が自動生成されていました。
config/mary.php
<?php return [ 'prefix' => 'mary-', ...
本と著者のDBテーブルを作る
本と著者を作ります。構造は適当でよいので、Claudeでお願いします。
マイグレーションファイルを作り、中身をClaudeでもらったやつに変更します。
php artisan make:migration create_authors_table php artisan make:migration create_posts_table
そして、migrateします。
php artisan migrate
中身を確認してみます。
psql -U postgres # \c hoge # \d posts 列 | タイプ | 照合順序 | Null 値を許容 | デフォルト --------------+--------------------------------+----------+---------------+----------------------------------- id | bigint | | not null | nextval('posts_id_seq'::regclass) author_id | bigint | | not null | title | character varying(255) | | not null | content | text | | not null | slug | character varying(255) | | not null | status | character varying(255) | | not null | 'draft'::character varying published_at | timestamp(0) without time zone | | | created_at | timestamp(0) without time zone | | | updated_at | timestamp(0) without time zone | | | # \d authors 列 | タイプ | 照合順序 | Null 値を許容 | デフォルト ------------+--------------------------------+----------+---------------+------------------------------------- id | bigint | | not null | nextval('authors_id_seq'::regclass) name | character varying(255) | | not null | email | character varying(255) | | not null | password | character varying(255) | | not null | bio | text | | | created_at | timestamp(0) without time zone | | | updated_at | timestamp(0) without time zone | | |
本と著者のモデルを作る
これもClaudeにお願いします。下記で作成されたファイルをClaudeが出力した内容に置き換えます。
php artisan make:model Author php artisan make:model Post
本と著者のVoltコンポーネントを作ってみる
これはどうも、Claudeさんは良く知らないようでした。VoltとmaryUIがよく知らないようです。そこで、Claudeのプロジェクトを使って、ナレッジとして、Volt, maryUIのドキュメントを入れてみようと思います。と思いましたが、一旦、Cursorでやってみます。CursorのDocにmaryUIを追加してみます。
まずは、下記コマンドでVoltコンポーネントを作成します。
php artisan make:volt "author-manager"
変なエラーが出たけど、キャッシュクリアしたら出なくなりました。
php artisan config:cache php artisan route:cache php artisan view:cache
ただ、レイアウト関連でエラーがでました。JetstreamはLivewireを使わない前提で、resources/views/layouts/app.blade.php
にレイアウトを用意してくれます。一方でLivewire(Volt)は、resources/views/components/layouts/app.blade.php
をデフォルトでは使うようになっているのかなと思っています。多分。下記でLivewireが使うレイアウトが作成されます。
php artisan livewire:layout
上記で作成されたレイアウトに @vite(['resources/css/app.css', 'resources/js/app.js'])
を下記のように追加します。
resources/views/components/layouts/app.blade.php
<!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{{ $title ?? 'Page Title' }}</title> @vite(['resources/css/app.css', 'resources/js/app.js']) </head> <body> {{ $slot }} </body> </html>
これでエラーでなくなったので、Voltコンポーネントを作成していきます。 著者の一覧、追加、編集、削除です。もちろん、バリデーションチェック・権限管理等を加える必要があります。Voltのお試しです。
<?php use function Livewire\Volt\{state}; use App\Models\Author; state([ 'authors' => Author::all(), 'name' => '', 'email' => '', 'password' => '', 'bio' => '', 'selectedAuthor' => null, 'tableHeaders' => [ ['key' => 'id', 'label' => 'ID'], ['key' => 'name', 'label' => 'Name'], ['key' => 'email', 'label' => 'Email'], ['key' => 'actions', 'label' => ''] ] ]); $addAuthor = function () { Author::create([ 'name' => $this->name, 'email' => $this->email, 'password' => bcrypt($this->password), 'bio' => $this->bio, ]); $this->resetForm(); $this->authors = Author::all(); }; $editAuthor = function ($id) { $author = Author::find($id); $this->selectedAuthor = $author; $this->name = $author->name; $this->email = $author->email; $this->bio = $author->bio; }; $updateAuthor = function ($id) { $author = Author::find($id); $author->update([ 'name' => $this->name, 'email' => $this->email, 'bio' => $this->bio, ]); $this->resetForm(); $this->authors = Author::all(); }; $deleteAuthor = function ($id) { Author::destroy($id); $this->authors = Author::all(); }; $resetForm = function () { $this->name = ''; $this->email = ''; $this->password = ''; $this->bio = ''; $this->selectedAuthor = null; }; ?> <div class="p-5"> <h1 class="text-3xl">著者</h1> <div class="p-5"> <h2 class="text-2xl font-bold pb-5">著者一覧</h2> <x-mary-table :headers="$tableHeaders" :rows="$authors" striped> @scope('cell_name', $author) {{ $author->name }} @endscope @scope('cell_email', $author) {{ $author->email}} @endscope @scope('cell_actions', $author) <x-mary-button class="btn-primary" wire:click="editAuthor({{ $author->id }})">編集</x-mary-button> <x-mary-button class="btn-error" wire:click="deleteAuthor({{ $author->id }})">削除</x-mary-button> @endscope </x-mary-table> </div> <x-mary-card title="{{ $selectedAuthor ? '著者編集' : '著者追加' }}"> <x-mary-form wire:submit.prevent="{{ $selectedAuthor ? 'updateAuthor(' . $selectedAuthor->id . ')' : 'addAuthor' }}"> <div> <label for="name">名前</label> <x-mary-input type="text" id="name" wire:model="name"/> </div> <div> <label for="email">メール</label> <x-mary-input type="email" id="email" wire:model="email"/> </div> @if (!$selectedAuthor) <div> <label for="password">パスワード</label> <x-mary-input type="password" id="password" wire:model="password"/> </div> @endif <div> <label for="bio">バイオ</label> <x-mary-textarea id="bio" wire:model="bio"/> </div> <x-slot:actions> <x-mary-button class="btn-primary" type="submit">{{ $selectedAuthor ? '更新' : '追加' }}</x-mary-button> <x-mary-button class="btn-warning" type="button" wire:click="resetForm">キャンセル</x-mary-button> </x-slot:actions> </x-mary-form> </x-mary-card> </div>