# 勉強会 # 今回作るプロジェクトの概要 ## 使用技術 Docker Laravel MySQL Blade Livewire Jetstream Tailwind # 環境構築 以下サイトからDocker-Desctopをインストール https://www.docker.com/get-started/ gitからコードを取得 ``` $ git clone https://github.com/murata0531/Laravel-CI.git ``` Dockerを使用してアプリケーションを立ち上げる ``` $ docker-compose up -d --build ``` Dockerコンテナが立ち上がっているか確認する ``` $ docker ps ``` Apacheコンテナに入る ``` $ docker-compose run --rm app bash ``` appディレクトリに入り、Laravelのファイルがあることを確認する ``` $ cd app && ls ``` Laravelを動かすための初期設定 ``` $ cp .env.example .env $ composer install $ php artisan key:generate ``` # アプリケーションを作成する ## 機能 タスクをCRUDするだけ ## テーブル構成 Tasksテーブル | 列名 | 型 | 概要 | | -------- | -------- | -------- | | id | int | タスクを特定する主キー | | name | varchar | タスクの名前 | | description | text | タスクの詳細 | | status| int | statusテーブルへの外部キー | | created_at | timestamp | 作成日時 | | updated_at | timestamp | 更新日時 | statusesテーブル | 列名 | 型 | 概要 | | -------- | -------- | -------- | | id | int | 状態を特定する主キー | | name | varchar | 状態の名前 | statusesテーブルに投入するパラメータ | id | name | |----|------| | 1 | 未処理 | | 2 | 処理中 | | 3 | 完了 | statusesテーブルとtasksテーブルはstatus_idを基準に1対多のリレーションシップを張る ## 今回利用するLaravelのテーブルでのデータ型の扱い | MySQL | Laravel | 役割 | |-------|---------|-----| | unsigned bigInt Auto Increment primary | unsignedBigInteger primary、id | 主キー | | int | integer | 整数 | | varchar | string | 文字列 | | timestamp | timestamp | 日付時刻 | 今回のgitブランチモデル | ブランチ名 | 役割 | | -------- | -------- | | mein | リリース用 | | develop | 開発時のマスター | | feature | 機能ごとに分けたブランチ | ## ここから作業 作業用のブランチを作成し、現在のブランチを作成したブランチに変更 ``` $ git checkout -b feature/create_tables ``` ## テーブルを作成 Apacheコンテナに入る ``` $ docker-compose run --rm app bash $ cd app ``` テーブル作成用のマイグレーションファイルとモデルを作成する ``` $ php artisan make:model -m Status ``` 上記のコマンドを入力すると、 ` database/migrations`フォルダに`2022_08_11S_145315_create_statuses_table.php`ファイル、 `app/Models`フォルダに`Status.php`ファイルが作成されている ``` $ php artisan make:model -m Task ``` 上記のコマンドを入力すると、 ` database/migrations`フォルダに`2022_08_10_145315_create_tasks_table.php`ファイル、 `app/Models`フォルダに`Task.php`ファイルが作成されている 作成された`create_statuses.php`ファイルに以下を記述 ```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. * * @return void */ public function up() { Schema::create('statuses', function (Blueprint $table) { $table->id(); $table->string('name')->comment('状態'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('statuses'); } }; ``` 作成された`create_tasks.php`ファイルに以下を記述 ```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. * * @return void */ public function up() { Schema::create('tasks', function (Blueprint $table) { $table->id(); $table->string('name')->comment('タスク名'); $table->text('description')->comment('詳細'); $table->unsignedBigInteger('status_id')->default(1)->comment('タスクの状態'); $table->timestamps(); $table->foreign('status_id')->references('id')->on('statuses'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('tasks'); } }; ``` 作成された`Status.php`ファイルに以下を記述 ```php <?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Status extends Model { use HasFactory; protected $guarded = []; public function tasks() { return $this->hasOne('App\Models\Task'); } } ``` 作成された`Task.php`ファイルに以下を記述 ```php <?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Task extends Model { use HasFactory; protected $guarded = []; public function statuses() { return $this->belongsTo('App\Models\Status'); } } ``` 以上の作業が終わったらテーブルを作成する ``` $ php artisan migrate ``` データベースを確認する 新しくターミナルを立ち上げて以下のコマンドでdbコンテナに入る ``` $ docker-compose exec db bash ``` コンテナ内のMySQLに入る ``` $ mysql -u test -p ``` パスワードを聞かれるので「test」と入力 MySQLに入ったら 以下のコマンドで`statusesテーブル`と`tasksテーブル`の構造を確認 ``` $ use test $ desc statuses; $ desc tasks; ``` gitにコミットしておく ファイル名の「2022_08_11_112051」は人によって違うので読み変えること ``` $ git add src/app/database/migrations/2022_08_11_112051_create_statuses_table.php $ git commit -m "statusesテーブルを追加" $ git add src/app/database/migrations/2022_08_11_112051_create_tasks_table.php $ git commit -m "tasksテーブルを追加" $ git add src/app/app/Models/Status.php $ git commit -m "Statusモデルを追加" git add src/app/app/Models/Task.php $ git commit -m "Taskモデルを追加" $ git push origin feature/create_tables ``` git hubを覗くと緑色のボタンが出てるのでプルリクエストを送り、レビューする レビューが終わったらマージする gitのローカルブランチを掃除 ``` $ git checkout develop $ git branch -D feature/create_tables ``` リモートリポジトリから変更したコードを取得 ``` $ git pull origin develop ``` ## statusesテーブルに初期データを投入 `feature/seeders`ブランチを作成し、入っておく ``` $ git checkout -b feature/seeders ``` StatusSeederを作成 ``` $ php artisan make:seeder StatusSeeder ``` `database/seeders/StatusSeeder.php`が作成されているので以下を記述 ```php <?php namespace Database\Seeders; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\DB; class StatusSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { DB::table('statuses')->insert([ [ 'id' => 1, 'name' => '未処理' ], [ 'id' => 2, 'name' => '処理中' ], [ 'id' => 3, 'name' => '完了' ], ]); } } ``` 同じ階層にある`DatabaseSeeder.php`に以下を記述 ```php <?php namespace Database\Seeders; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { /** * Seed the application's database. * * @return void */ public function run() { // \App\Models\User::factory(10)->create(); // \App\Models\User::factory()->create([ // 'name' => 'Test User', // 'email' => 'test@example.com', // ]); $this->call(StatusSeeder::class); } } ``` データベースにデータを投入 ``` $ php artisan db:seed ``` データベースを確認したら3つデータが入ってるはず 追加したファイルをgitにコミットする ``` $ git add src/app/database/seeders/StatusSeeder.php $ git commit -m "statusesテーブルにデータを追加する" $ git add src/app/database/seeders/DatabaseSeeder.php $ git commit -m "StatusSeederを追加" $ git push origin feature/seeders ``` git hubを覗くと緑色のボタンが出てるのでプルリクエストを送り、レビューする レビューが終わったらマージする gitのローカルブランチを掃除 ``` $ git checkout develop $ git branch -D feature/seeders ``` リモートリポジトリから変更したコードを取得 ``` $ git pull origin develop ``` ## モデルを修正する `fix/models`ブランチを作成し入る ``` $ git checkout -b fix/models ``` `app/Models/Status.php`を以下に修正 ```php <?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Status extends Model { use HasFactory; protected $guarded = []; public function tasks() { return $this->hasMany('App\Models\Task'); } } ``` `app/Models/Task.php`を以下に修正 ```php <?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Task extends Model { use HasFactory; protected $guarded = []; public function statuses() { return $this->belongsTo('App\Models\Status', 'status_id'); } } ``` 追加したファイルをgitにコミットする ``` $ git add src/app/app/Models/Status.php $ git commit -m "hasManyに修正" $ git add src/app/app/Models/Task.php $ git commit -m "belongsToに修正、外部キーを指定" $ git push origin fix/models ``` git hubを覗くと緑色のボタンが出てるのでプルリクエストを送り、レビューする レビューが終わったらマージする gitのローカルブランチを掃除 ``` $ git checkout develop $ git branch -D fix/models ``` リモートリポジトリから変更したコードを取得 ``` $ git pull origin develop ``` ## ページを作成する `feature/tasks`ブランチを作成し入る ``` $ git checkout -b feature/tasks ``` Livewireコンポーネントを作成 ``` $ php artisan make:livewire tasks ``` コマンドを入力すると以下のファイルが作成されている `app/Http/Livewire/Tasks.php` `resources/views/livewire/Tasks.blade.php` `app/Http/Livewire/Tasks.php`に以下を記述 ```php <?php namespace App\Http\Livewire; use Livewire\Component; use App\Models\Status; use App\Models\Task; class Tasks extends Component { public $tasks, $name, $description, $task_id,$statuses; public $status_id = 1; public $isOpen=false; public function render() { $this->statuses = Status::with('tasks')->get(); $this->tasks = Task::with('statuses')->get(); return view('livewire.tasks'); } public function mount($status_id = 1) { $this->status_id = $status_id; $this->onChange(); } public function onChange() { $status_id= intval($this->status_id); } public function create() { $this->resetInputFields(); $this->openModal(); } public function openModal() { $this->isOpen = true; } public function closeModal() { $this->isOpen = false; } public function resetInputFields() { $this->name = ''; $this->description = ''; $this->task_id = null; } public function store() { $this->validate([ 'name' => 'required', 'description' => 'required' ]); Task::updateOrCreate(['id' => $this->task_id], [ 'name' => $this->name, 'description' => $this->description, 'status_id' => $this->status_id ]); session()->flash('message', $this->task_id ? 'Task Updated Successfully.' : 'Task Created Successfully.'); $this->closeModal(); $this->resetInputFields(); } public function edit($id) { $task = Task::findOrFail($id); $this->task_id = $task->id; $this->name = $task->name; $this->description = $task->description; $this->stattus_id = $task->status_id; $this->openModal(); } public function delete($id) { Task::find($id)->delete(); session()->flash('message', 'Task Deleted Successfully.'); } } ``` `resources/views/livewire/tasks.blade.php`に以下を記述 ```php <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> ToDo App </h2> </x-slot> <div class="py-12"> <div class="max-w-7x1 mx-auto sm:px-6 lg:px-8"> <div class="bg-white overflow-hidden shadow-x1 sm rounded-lg px-4 py-4"> @if(session()->has('message')) <div class="bg-teal-100 border-lt-4 border teal-500 rounded-b text-teal-900 px-4 py-3 shadow-md my-3" role="alert"> <div class="flex"> <div> <p class="text-sm">{{ session('message') }}</p> </div> </div> </div> @endif <button wire:click="create()" class="bg-blue-500 hover bg-blue-700 text-white font-bold py-2 px-4 rounded my-3">Create Task</button> @if($isOpen) @include('livewire.create') @endif <table class="table-fixed w-full"> <thead> <tr class="bg-gray-1000"> <th class="px-4 py2 w-20">No.</th> <th class="px-4 py-2">Name</th> <th class="px-4 py-2">Desc</th> <th lass="px-4 py-2">Status</th> <th class="px-4 py-4">Action</th> </tr> </thead> <tbody> @foreach($tasks as $task) <tr> <td class="border px-4 py-2">{{ $task->id }}</td> <td class="border px-4 py-2">{{ $task->name }}</td> <td class="border px-4 py-2">{{ $task->description }}</td> <td class="border px-4 py-2">{{ $task->statuses->name }}</td> <td class="border px-4 py2"> <button wire:click="edit({{ $task->id }})" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Edit</button> <button wire:click="delete({{ $task->id }})" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Delete</button> </td> </tr> @endforeach </tbody> </table> </div> </div> </div> ``` 新しく`resources/views/livewire/`フォルダに `create.blade.php`ファイルを作成し、以下を記述 ```php <div class="fixed z-10 inset-0 overflow-y-auto ease-out duration-400"> <div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> <div class="fixed inset-0 tansition-opacity"> <div class="absolute inset-0 bg-gray-500 opacity-75"></div> </div> <span class="hidden sm:inline-block sm:aligh-middle sm:h-screen"></span> <div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" role="dialog" aria-modal="true" aria-labelledby="modal-headline"> <form> <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> <div class=""> <div class="mb-4"> <label for="name" class="block text-gray-700 text-sm font-bold mb-2">name</label> <input type="text" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="name" placeholder="Enter name" wire:model="name"> @error('name') <span class="text-red-500">{{ $message }}</span>@enderror </div> <div class="mb-4"> <label for="description" class="block text-gray-700 text-sm font-bold mb-2">Description</label> <textarea id="description" cols="30" rows="10" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" placeholder="Enter Description" wire:model="description"></textarea> @error('description') <span class="text-red-500">{{ $message }}</span>@enderror </div> <div class="mb-4"> <label for="status_id" class="block text-gray-700 text-sm font-bold mb-2">状態</label> <select id="status_id" cols="30" rows="10" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" name="status_id" wire:model="status_id" wire:change="onChange"> @foreach ($statuses as $status) <option value="{{ $status->id }}">{{ $status->name }} @endforeach </select> </div> </div> </div> <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> <span class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"> <button wire:click.prevent="store()" type="button" class="inline-flex justify-center w-full rounded-md border border-transparent px-4 py-2 bg-green-600 text-base leading-6 font-medium text-white shadow-sm hover:bg-green-500 focus:outline-none focus:border-green-700 focus:shadow-outline-green transition ease-in-out duration-150 sm:text-sm sm:leading-5"> Save </button> </span> <span class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"> <button wire:click="closeModal()" type="button" class="inline-flex justify-center w-full rounded-md border border-transparent px-4 py-2 bg-green-600 text-base leading-6 font-medium text-white shadow-sm hover:bg-green-500 focus:outline-none focus:border-green-700 focus:shadow-outline-green transition ease-in-out duration-150 sm:text-sm sm:leading-5"> Cancel </button> </span> </div> </form> </div> </div> </div> ``` 最後にルートを追加する `routes/web.php`に以下を記述 ```php <?php use Illuminate\Support\Facades\Route; use App\Http\Livewire\Tasks; /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ Route::get('/', function () { return view('welcome'); }); Route::middleware([ 'auth:sanctum', config('jetstream.auth_session'), 'verified' ])->group(function () { Route::get('/dashboard', function () { return view('dashboard'); })->name('dashboard'); }); Route::get('tasks', Tasks::class); ``` 新しくターミナルを立ち上げてLaravelのディレクトリまで移動 ``` $ cd src/app ``` vite(JavaScriptの実行環境)を動かす ``` $ npm install && npm run dev ``` 以上の作業が終わったらブラウザから`localhost`にアクセス 画面右上のregisterからユーザ登録後、`localhost/tasks`を開く タスクの確認・作成・編集・削除が正しく行えることを確認する 追加したファイルをgitにコミットする ``` $ git add src/app/resources/views/livewire/tasks.blade.php $ git commit -m "タスク確認ページを追加" $ git add src/app/resources/views/livewire/create.blade.php $ git commit -m "タスク編集フォームを追加" $ git add src/app/routes/web.php $ git commit -m "ルートを追加" $ git push origin feature/tasks ``` git hubを覗くと緑色のボタンが出てるのでプルリクエストを送り、レビューする レビューが終わったらマージする gitのローカルブランチを掃除 ``` $ git checkout develop $ git branch -D fix/models ``` リモートリポジトリから変更したコードを取得 ``` $ git pull origin develop ``` # git 多く使用されているブランチモデル | ブランチ名 | 役割 | | -------- | -------- | | mein | リリース用 | | develop | 開発時のマスター | | feature | 機能ごとに分けたブランチ | リモートブランチを確認する ``` $ git remote -v ``` 全てのブランチを確認する ``` $ git branch -a ``` 新しくfeature/xxxブランチを作成して切り替える ``` $ git checkout -b feature/xxx ``` 現在のブランチを確認する ``` $ git branch ``` コードを書く ファイルをステージングさせる ``` $ git add ファイル名 ``` コミット ``` $ git commit -m "コメント" ``` 作成したブランチをリモートにプッシュする ``` $ git push origin feature/xxx ``` githubを確認するとプルリクを送る旨のメッセージが表示されているのでプルリクエストを送る 無事マージされたら以下を実行しブランチを削除する ``` $ git checkout develop $ git branch -D feature/xxx ``` もしリモートが変更されていたら、 ``` $ git pull origin develop ``` でコードをローカルに追加する コンテナ破棄 ``` $ docker-compose down --rmi all --volumes --remove-orphans ``` # 今回のリポジトリ https://github.com/murata0531/Laravel-CI