githubのcodespaces laravel11 VS CODE でtodoアプリづくり

githubのcodespaces laravel11 VS CODE でtodoアプリづくり

参考にしたサイト

https://www.seplus.jp/dokushuzemi/blog/2023/01/tutorial_github_codespaces.html

https://b-risk.jp/blog/2022/08/laravel

今回、codespaces テンプレートは、

flemzord/codespaces-laravel

を使用し、laravel11、postgresql 構成でVS CODE 上に環境作りました。

VS CODE 上のターミナルで、

php artisan serve

http://127.0.0.1:8000

Laravelの初期設定

.env

APP_NAME=LaravelTest
...
APP_TIMEZONE=Asia/Tokyo
APP_LOCALE=ja
APP_FALLBACK_LOCALE=ja
APP_FAKER_LOCALE=ja_JP

VS CODE の初期設定

接続情報の設定

ホスト:db、db・ユーザー:laravel/laravel、ポート:5432

サンプルアプリ

モデル、テーブル作成

php artisan make:model TodoList -mc


public function up()
{
  Schema::create('todo_lists', function (Blueprint $table) {
    $table->id();
    $table->string('name', 100);
    $table->timestamps();
  });
}

php artisan migrate

テストデータ作成

php artisan make:seeder TodoListSeeder


<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class TodoListSeeder extends Seeder
{
  /**
   * Run the database seeds.
   *
   * @return void
   */
  public function run()
  {
    DB::table('todo_lists')->insert(
      [
        [
          'name' => 'テスト1',
          'created_at' => now(),
          'updated_at' => now(),
        ],
        [
          'name' => 'テスト2',
          'created_at' => now(),
          'updated_at' => now(),
        ],
        [
          'name' => 'テスト3',
          'created_at' => now(),
          'updated_at' => now(),
        ],
      ]
    );
  }
}

database\seeders\DatabaseSeeder.php


public function run()
{
  $this->call([
    TodoListSeeder::class
  ]);
}

php artisan db:seed –class=TodoListSeeder

コントローラ作成

app\Http\Controllers\TodoListController.php


<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\TodoList;

class TodoListController extends Controller
{
  public function index(Request $request)
  {
    $todo_lists = TodoList::all();

    return view('todo_list.index', ['todo_lists' => $todo_lists]);
  }
}

ビュー作成

resources\views\todo_list\index.blade.php


<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>テスト</title>
</head>

<body>

    @if ($todo_lists->isNotEmpty())
        <ul>
            @foreach ($todo_lists as $item)
                <li>
                    {{ $item->name }}
                </li>
            @endforeach
        </ul>
    @endif

</body>

</html>

ルーティング設定

routes\web.php

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\TodoListController;

Route::get('/', function () {
    return view('welcome');
});

Route::get('/list', [TodoListController::class, 'index']);

TailwindCSS 導入

VS CODE ターミナルで

npm install -D tailwindcss postcss autoprefixer

npx tailwindcss init -p

ファイルが2つ作成。postcss.config.jsとtailwind.config.js

tailwind.config.jsを編集


/** @type {import('tailwindcss').Config} */
module.exports = {
    content: [
        "./resources/**/*.blade.php",
        "./resources/**/*.js",
        "./resources/**/*.vue",
    ],
    theme: {
        extend: {},
    },
    plugins: [],
}

resources\css\app.css 編集


@tailwind base;
@tailwind components;
@tailwind utilities;

npm run dev ※php artisan serveも実行中に。

http://127.0.0.1:5173/

http://127.0.0.1:8000/list

tasksテーブル作成

VS CODE ターミナルで、

php artisan make:migration create_tasks_table

database/migrations/日付_create_tasks_table.phpの編集

  /** * Run the migrations. */ public function up(): void { Schema::create(‘tasks’, function (Blueprint $table) { $table->id(); $table->string(‘name’, 100); $table->boolean(‘status’)->default(false); $table->timestamp(‘updated_at’)->useCurrent()->nullable(); $table->timestamp(‘created_at’)->useCurrent()->nullable(); // $table->timestamps(); }); }

php artisan migrate

Task モデル作成

php artisan make:model Task

コントローラー作成。リソースコントローラーを作成

php artisan make:controller TaskController –resource

web.php を編集

use App\Http\Controllers\TaskController;
Route::resource('tasks', TaskController::class);

現在のルーティングを確認

php artisan route:list

ビュー作成

resources/views/tasks/index.blade.php の作成


<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Todo</title>

    @vite('resources/css/app.css')
</head>

<body class="flex flex-col min-h-[100vh]">
    <header class="bg-slate-800">
        <div class="max-w-7xl mx-auto px-4 sm:px-6">
            <div class="py-6">
                <p class="text-white text-xl">Todoアプリ</p>
            </div>
        </div>
    </header>

    <main class="grow">
        <div class="max-w-7xl mx-auto px-4 sm:px-6">
            <div class="py-[100px]">
                <p class="text-2xl font-bold text-center">今日は何する?</p>
                <form action="/tasks" method="post" class="mt-10">
                  @csrf

                  <div class="flex flex-col items-center">
                    <label class="w-full max-w-3xl mx-auto">
                        <input
                            class="placeholder:italic placeholder:text-slate-400 block bg-white w-full border border-slate-300 rounded-md py-4 pl-4 shadow-sm focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1 sm:text-sm"
                            placeholder="洗濯物をする..." type="text" name="task_name" />
                    </label>

                    <button type="submit" class="mt-8 p-4 bg-slate-800 text-white w-full max-w-xs hover:bg-slate-900 transition-colors">
                        追加する
                    </button>
                  </div>

                </form>
            </div>
        </div>
    </main>
    <footer class="bg-slate-800">
      <div class="max-w-7xl mx-auto px-4 sm:px-6">
        <div class="py-4 text-center">
            <p class="text-white text-sm">Todoアプリ</p>
        </div>
    </div>
    </footer>
</body>

</html>

TaskController.php 編集

public function index()
{
    return view('tasks.index');
}

php artisan serve

npm run dev

http://127.0.0.1:8000/tasks

タスクを登録する

TaskController.php 編集。ddメソッドを使いデバッグテスト。


public function store(Request $request)
{
  $task_name = $request->input('task_name');
  dd($task_name);
}

テスト入力。

OK。

では、実装します。

TaskController.phpに、

...
use App\Models\Task;
...
    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        //モデルをインスタンス化
        $task = new Task;

        //モデル->カラム名 = 値 で、データを割り当てる
        $task->name = $request->input('task_name');

        //データベースに保存
        $task->save();

        //リダイレクト
        return redirect('/tasks');
    }

先ほどのテスト入力。

DB確認。

バリデーション

Validatorクラスを利用したいので、TaskController.phpに以下を追記

...
use Illuminate\Support\Facades\Validator;
...
    public function store(Request $request)
    {
        $rules = [
            'task_name' => 'required|max:100',
        ];
        $messages = ['required' => '必須項目です', 'max' => '100文字以下にしてください。'];
        Validator::make($request->all(), $rules, $messages)->validate();

        //モデルをインスタンス化
        $task = new Task;

        //モデル->カラム名 = 値 で、データを割り当てる
        $task->name = $request->input('task_name');

        //データベースに保存
        $task->save();

        //リダイレクト
        return redirect('/tasks');
    }

index.blade.phpのinputタグの下に、エラーメッセージ表示を追加する。

                        <input
                            class="placeholder:italic placeholder:text-slate-400 block bg-white w-full border border-slate-300 rounded-md py-4 pl-4 shadow-sm focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1 sm:text-sm"
                            placeholder="洗濯物をする..." type="text" name="task_name" />

                        @error('task_name')
                        <div class="mt-3">
                            <p class="text-red-500">
                                {{ $message }}
                            </p>
                        </div>
                        @enderror

                    </label>

OK。

最後にまとめて「タスクを表示する、編集する、完了する、削除する。」

TaskController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Task;
use Illuminate\Support\Facades\Validator;

class TaskController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        //
        // $tasks = Task::all();
        $tasks = Task::where('status', false)->get();

        return view('tasks.index', compact('tasks'));
    }

    /**
     * Show the form for creating a new resource.
     */
    public function create()
    {
        //
    }

    /**
     * store
     */
    public function store(Request $request)
    {
        $rules = [
            'task_name' => 'required|max:100',
        ];
        $messages = ['required' => '必須項目です', 'max' => '100文字以下にしてください。'];
        Validator::make($request->all(), $rules, $messages)->validate();

        //モデルをインスタンス化
        $task = new Task;

        //モデル->カラム名 = 値 で、データを割り当てる
        $task->name = $request->input('task_name');

        //データベースに保存
        $task->save();

        //リダイレクト
        return redirect('/tasks');
    }

    /**
     * Display the specified resource.
     */
    public function show(string $id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     */
    public function edit(string $id)
    {
        //
        $task = Task::find($id);
        return view('tasks.edit', compact('task'));
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, string $id)
    {
        // dd($request->status);//追記

        //「編集する」ボタンをおしたとき
        if ($request->status === '9') {
            $rules = [
            'task_name' => 'required|max:100',
            ];

            $messages = ['required' => '必須項目です', 'max' => '100文字以下にしてください。'];

            Validator::make($request->all(), $rules, $messages)->validate();

            //該当のタスクを検索
            $task = Task::find($id);

            //モデル->カラム名 = 値 で、データを割り当てる
            $task->name = $request->input('task_name');

            //データベースに保存
            $task->save();
        } else {
            //「完了」ボタンを押したとき

            //該当のタスクを検索
            $task = Task::find($id);

            //モデル->カラム名 = 値 で、データを割り当てる
            $task->status = true; //true:完了、false:未完了

            //データベースに保存
            $task->save();
        }

        //リダイレクト
        return redirect('/tasks');
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(string $id)
    {
        //
        Task::find($id)->delete();

        return redirect('/tasks');
    }
}

index.blade.php


<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Todo</title>

    @vite('resources/css/app.css')
</head>

<body class="flex flex-col min-h-[100vh]">
    <header class="bg-slate-800">
        <div class="max-w-7xl mx-auto px-4 sm:px-6">
            <div class="py-6">
                <p class="text-white text-xl">Todoアプリ</p>
            </div>
        </div>
    </header>

    <main class="grow">
        <div class="max-w-7xl mx-auto px-4 sm:px-6">
            <div class="py-[100px]">
                <p class="text-2xl font-bold text-center">今日は何する?</p>
                <form action="/tasks" method="post" class="mt-10">
                  @csrf

                  <div class="flex flex-col items-center">
                    <label class="w-full max-w-3xl mx-auto">
                        <input
                            class="placeholder:italic placeholder:text-slate-400 block bg-white w-full border border-slate-300 rounded-md py-4 pl-4 shadow-sm focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1 sm:text-sm"
                            placeholder="洗濯物をする..." type="text" name="task_name" />

                        @error('task_name')
                        <div class="mt-3">
                            <p class="text-red-500">
                                {{ $message }}
                            </p>
                        </div>
                        @enderror

                    </label>

                    <button type="submit" class="mt-8 p-4 bg-slate-800 text-white w-full max-w-xs hover:bg-slate-900 transition-colors">
                        追加する
                    </button>
                  </div>

                </form>

                {{-- 追記 --}}
                @if ($tasks->isNotEmpty())
                    <div class="max-w-7xl mx-auto mt-20">
                        <div class="inline-block min-w-full py-2 align-middle">
                            <div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
                                <table class="min-w-full divide-y divide-gray-300">
                                    <thead class="bg-gray-50">
                                        <tr>
                                            <th scope="col"
                                                class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900">
                                                タスク</th>
                                            <th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
                                                <span class="sr-only">Actions</span>
                                            </th>
                                        </tr>
                                    </thead>
                                    <tbody class="divide-y divide-gray-200 bg-white">
                                        @foreach ($tasks as $item)
                                            <tr>
                                                <td class="px-3 py-4 text-sm text-gray-500">
                                                    <div>
                                                        {{ $item->name }}
                                                    </div>
                                                </td>
                                                <td class="p-0 text-right text-sm font-medium">
                                                    <div class="flex justify-end">
                                                        <div>
                                                            <form action="/tasks/{{ $item->id }}"
                                                                method="post"
                                                                class="inline-block text-gray-500 font-medium"
                                                                role="menuitem" tabindex="-1">
                                                                @csrf
                                                                @method('PUT')

                                                                <input type="hidden" name="status" value="{{ $item->status }}">

                                                                <button type="submit"
                                                                    class="bg-emerald-700 py-4 w-20 text-white md:hover:bg-emerald-800 transition-colors">完了</button>
                                                            </form>
                                                        </div>
                                                        <div>
                                                            <a href="/tasks/{{ $item->id }}/edit/"
                                                                class="inline-block text-center py-4 w-20 underline underline-offset-2 text-sky-600 md:hover:bg-sky-100 transition-colors">編集</a>
                                                        </div>
                                                        <div>
                                                            <form onsubmit="return deleteTask();"
                                                                action="/tasks/{{ $item->id }}" method="post"
                                                                class="inline-block text-gray-500 font-medium"
                                                                role="menuitem" tabindex="-1">
                                                                @csrf
                                                                @method('DELETE')
                                                                <button type="submit"
                                                                    class="py-4 w-20 md:hover:bg-slate-200 transition-colors">削除</button>
                                                            </form>
                                                        </div>
                                                    </div>
                                                </td>
                                            </tr>
                                        @endforeach
                                    </tbody>
                                </table>
                            </div>
                        </div>
                    </div>
                @endif
                {{-- 追記ここまで --}}

            </div>
        </div>
    </main>
    <footer class="bg-slate-800">
      <div class="max-w-7xl mx-auto px-4 sm:px-6">
        <div class="py-4 text-center">
            <p class="text-white text-sm">Todoアプリ</p>
        </div>
    </div>
    </footer>

    <script>
    function deleteTask() {
        if (confirm('本当に削除しますか?')) {
            return true;
        } else {
            return false;
        }
    }
    </script>

</body>

</html>

edit.blade.php

<!DOCTYPE html>
  <html lang="ja">

  <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Todo</title>

      @vite('resources/css/app.css')
  </head>

  <body class="flex flex-col min-h-[100vh]">
      <header class="bg-slate-800">
          <div class="max-w-7xl mx-auto px-4 sm:px-6">
              <div class="py-6">
                  <p class="text-white text-xl">Todoアプリ-編集画面</p>
              </div>
          </div>
      </header>

      <main class="grow grid place-items-center">
          <div class="w-full mx-auto px-4 sm:px-6">
              <div class="py-[100px]">
                  <form action="/tasks/{{ $task->id }}" method="post" class="mt-10">
                      @csrf
                      @method('PUT')

                      <div class="flex flex-col items-center">
                          <label class="w-full max-w-3xl mx-auto">
                              <input
                                  class="placeholder:italic placeholder:text-slate-400 block bg-white w-full border border-slate-300 rounded-md py-4 pl-4 shadow-sm focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1 sm:text-sm"
                                  type="text" name="task_name" value="{{ $task->name }}" />
                              @error('task_name')
                                  <div class="mt-3">
                                      <p class="text-red-500">
                                          {{ $message }}
                                      </p>
                                  </div>
                              @enderror
                          </label>

                          <div class="mt-8 w-full flex items-center justify-center gap-10">
                              <a href="/tasks" class="block shrink-0 underline derline-offset-2">
                                  戻る
                              </a>
                              <input type="hidden" name="status" value="9">
                              <button type="submit" class="p-4 bg-sky-800 text-white w-full max-w-xs hover:bg-sky-900 transition-colors">
                                  編集する
                              </button>

                          </div>
                      </div>

                  </form>

              </div>
          </div>
      </main>
      <footer class="bg-slate-800">
          <div class="max-w-7xl mx-auto px-4 sm:px-6">
              <div class="py-4 text-center">
                  <p class="text-white text-sm">Todoアプリ</p>
              </div>
          </div>
      </footer>
  </body>

  </html>

web.php

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\TodoListController;
use App\Http\Controllers\TaskController;

Route::get('/', function () {
    return view('welcome');
});

Route::get('/list', [TodoListController::class, 'index']);

Route::resource('tasks', TaskController::class);

WEBサーバー起動

npm run dev

php artisan serve

http://127.0.0.1:8000/tasks

最後に、VS CODEのPostgreSQL拡張機能のSQL実行機能について

右クリックメニューより、NewQueryを選択

select/insert/update/deleteのSQL実行が可能