Closed32

【Laravel】TODOアプリを作ってみた

むっくむっく

画面設計

タスクの新規作成について

ログイン画面からメールアドレスとパスワードを入力し、ログインする。
ログイン後、タスク一覧画面が表示される。
+ボタンを押下すると、タスク新規作成画面へ遷移する。
タイトル、期限、担当者、内容を入力し、新規作成ボタンを押下する。
そうすると、タスク一覧画面へ戻り、作成したタスクが表示される。
タスクが完了した場合は、チェックボックスをタップする。

タスクの編集について

該当のタスクの行をタップすると、タスク編集画面へ遷移する。
タイトルなどの項目を編集し、編集するボタンを押下すると、タスクは編集された状態でタスク一覧画面へ戻る。
そして、削除するボタンを押下すると、タスクが削除された状態でタスク一覧画面へ戻る。

ユーザー新規作成・編集について

ヘッダーのハンバーガーメニューをタップすると、サイドバーが表示される。
そこでユーザー一覧をタップすると、ユーザー一覧画面へ遷移する。
+ボタンをタップすると、ユーザー新規作成画面へ遷移する。
ユーザー名、メールアドレス、パスワードを入力して新規作成ボタンを押下すると、ユーザーが新規作成されてユーザー一覧画面へ戻る。

編集したいユーザーの行をタップすると、ユーザー編集画面へ遷移する。
ユーザー名などを編集して編集するボタンを押下すると、ユーザーが編集された状態でユーザー編集画面へ戻る。
削除するボタンを押下すると、ユーザーが削除された状態でユーザー削除画面へ戻る。

むっくむっく

テーブル設計

むっくむっく

テーブル名について
・テーブル名は小文字の複数形で(users、tasks)
・英語的な正しい複数形で(companies、water)

pkについて
userIdやtaskIdではなく、idとする

$user = User::find(1); // デフォルトでidが1のものを探す
echo $user->userId; // もうuser->でuserとわかるので、idとする

fk
userIdではなく、user_id(テーブル名の単数系_id)

むっくむっく

descと略さないで、descriptionと命名する。
descは降順と誤解しやすい。

むっくむっく

Users
name: varchar(30)
email: varchar(255) // 昔の習慣
password: varchar(255)

Tasks
user_id:Unsigned BigInt型
title:varchar(30)
date:datetime 日時
owner:これはuser_idで紐づいているから不要
description:text型 (midium や tinyなどある)

むっくむっく

削除機能について
物理削除になっているが、論理削除にした方が良いのではないか?

fk機能の下記3パターン
userが削除された場合、fkの機能でtaskを削除される。
user_idがnullになる。
↓↓↓↓こちらを採用!!!
削除できないようにエラーにする。
・Laravelを介さず、手動でDBを操作した場合に備えてエラーになるように設定しておく。

↓↓↓↓論理削除を採用!!!
ユーザーテーブルを論理削除に対応させるパターン
deleted_at:datetime // laravelは自動的にselectして取得しない

むっくむっく

laravel sailを使って、Docker環境でTODOアプリを作っていく。

// curl -s でダウンロードの意味
// bashで実行
curl -s https://laravel.build/example-app?with=mysql | bash

// コンテナを起動する
cd example-app
./vendor/bin/sail up

// データベース移行を実行する
./vendor/bin/sail artisan migrate

// テーブルを全て削除し再生成
./vendor/bin/sail artisan migrate:fresh
// データ注入
./vendor/bin/sail artisan db:seed

https://laravel.com/docs/11.x#sail-on-windows

むっくむっく

MySQL データ型

データベースの型について知らなかったため、MySQLの公式ドキュメントを読みました。
https://dev.mysql.com/doc/refman/8.0/ja/data-types.html

知らなかったこと
BLOB型
・さまざまな容量のデータを保持できる大きなバイナリオブジェクト

商品コードなどの固定長のデータにはCHARを使う(0~255文字)
文字数がバラバラになるようなデータの場合はVARCHARを使う(0~65535文字)
それよりも長くなる文字列にはTEXTを使用する

SETはアンケートの回答などで複数選択する場合に使う

むっくむっく

SQLSTATE[42S02]: Base table or view not found: 1146 Table 'laravel.sessions' doesn't exist (Connection: mysql, SQL: select * from sessions where id = rYNvVZ4PDv2hG0VRssR4wlsGFCptl5kiCBAO47wY limit 1)

テーブル名がlaravel.sessionsであるテーブルが見つからないと言っています。
なので、そもそもマイグレーションでテーブルを作る必要があるのではと思っています。

php artisan migrateを実行します。

下記エラーが発生
PHP Fatal error: Composer detected issues in your platform: Your Composer dependencies require a PHP version ">= 8.2.0". You are running 8.1.27. in /Users/naoki/development/test-project/vendor/composer/platform_check.php on line 24

要は、「PHP バージョン ">= 8.2.0" が必要です」と言っています。
なので、phpのバージョンを8.2.0以上に上げてみましょう。

既存のPHPをアンインストール

brew uninstall php@8.1

PHP 8.2をインストール

brew install php@8.2

HomebrewのPHP 8.2をリンク:

brew link --force --overwrite php@8.2
むっくむっく

SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo for mysql failed: nodename nor servname provided, or not known (Connection: mysql, SQL: select table_name as name, (data_length + index_length) as size, table_comment as comment, engine as engine, table_collation as collation from information_schema.tables where table_schema = 'laravel' and table_type in ('BASE TABLE', 'SYSTEM VERSIONED') order by table_name)

https://zenn.dev/mukkun69n/articles/1fd320f1fcc09c

DB_HOST=127.0.0.1 に設定すると、「php artisan migrate」を実行できた。

そもそも127.0.0.1とは、自分を意味するIPアドレス(ループバックアドレス)の一つ
https://wa3.i-3-i.info/word17065.html

むっくむっく

データベース側の実装について

php artisan make:model Task -mを実行
-mによってモデルだけではなく、マイグレーションファイルも生成される。
モデルは単数系の命名にする。

むっくむっく

マイグレーションとは?

あるデータベースの構造や内容を別のデータベースへ移行するプロセスのこと。

Laravelのマイグレーションファイルは、データベーススキーマのバージョン管理を行うための仕組みです。
マイグレーションファイルを使うことで、データベースの構造(テーブルやカラムなど)をプログラムコードとして定義し、変更履歴を管理できる。

スキーマとは、データベースの構造のこと。

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}
むっくむっく

unique()とは

unique()メソッドは、そのカラムに一意性制約(ユニーク制約)を設定することを意味します。
たとえば、上記のemailにuniqueが設定されていますが、このカラムに格納される値は全て異なるemailでなければなりません。

むっくむっく

ログイン画面の実装

むっくむっく

バリデーションの表示を日本語にする

・resources/lang/jaにvalidation.phpというファイルを作成
・下記の内容をファイルに追記

<?php
return [
    'required' => ':attributeは必須です。',
    'attributes' => [
        'email' => 'メールアドレス',
        'password' => 'パスワード',
    ]
];

・config/app.phpファイルのlocaleを'ja'に設定する

むっくむっく

This password does not use the Bcrypt algorithm.

「このパスワードはBcrypt algorithmを使用していません」というエラーです。

データベースに保存しているパスワードがBcryptでハッシュ化されていないために発生しています。
パスワードを平文で保存するのはセキュリティ上良くないため、パスワードは常にハッシュ化して保存する必要があります。

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;

class UserSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        DB::table('users')->insert([
            [
                'name' => 'なおき',
                'email' => 'test@test.com',
                'password' => Hash::make('test1234') 👈 ココでパスワードを暗号化
            ],
            [
                'name' => 'おおたけ',
                'email' => 'test1@test.com',
                'password' => Hash::make('test1234')
            ]
        ]);
    }
}
むっくむっく

Bcryptとは

パスワードを安全に保存するために設計されたハッシュ関数

むっくむっく
むっくむっく

タスク一覧表示の際に、ユーザー名を表示する方法

https://qiita.com/yukachin0414/items/726f3cbf4270f50f6028

use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    // Userモデルとのリレーションを定義
    public function user()
    {
        // TaskテーブルはUserテーブルに従属する
        return $this->belongsTo(User::class);
    }
}
<?php

namespace App\Http\Controllers;

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

class TaskController extends Controller
{
    public function showTasks()
    {
        // ログインしたユーザーのID
        $userId = Auth::user()->id;
        /// Taskと関連するuser情報を取得
        /// ログインしたユーザーのタスクのみ取得
        $tasks = Task::with('user')->where('user_id', $userId)->get();
        // 画面の表示
        return view('home', ['tasks' => $tasks]);
    }
}

@foreach($tasks as $task)
                        <tr>
                            <td>{{ $task->title }}</td>
                            <td>{{ $task->date }}</td>
                            <td>{{ $task->user->name }}</td>
                            <td>{{ $task->description }}</td>
                        </tr>
                    @endforeach
むっくむっく

タスク新規作成画面の実装

むっくむっく

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'updated_at' in 'field list' (Connection: mysql, SQL: insert into tasks (user_id, title, date, description, updated_at, created_at) values (1, laravelの勉強, 2024-06-29T20:10, todoアプリの実装, 2024-06-28 11:12:42, 2024-06-28 11:12:42))

tasksテーブルにupdated_atカラムが存在しないため、データの挿入に失敗しています。
updated_atカラムは、Eloquantのタイムスタンプ機能によって自動的に挿入されますが、このカラムがテーブルに存在しない場合、エラーが発生します。

解決方法としてマイグレーションファイルにtimestampsメソッドを追加します。

public function up()
{
    Schema::create('tasks', function (Blueprint $table) {
        $table->id();
        $table->unsignedBigInteger('user_id');
        $table->string('title', 30);
        $table->dateTime('date');
        $table->text('description');
        $table->timestamps(); // ここが追加されていることを確認
    });
}
むっくむっく

タスクの編集/削除画面

むっくむっく

Add [user_id] to fillable property to allow mass assignment on [App\Models\Task].

mass assignment を許可するためにfillableプロパティにuser_idを追加してください。と記載されている。
Mass Assignmentとはユーザーが通常はアクセスを許可すべきでないデータ項目を変更するコンピューたの脆弱性のこと。
user_idはユーザー側が変更するべきものでないため、Mass Assignmentでエラーは発生したということになる。

解決方法として、Taskモデルに保存したいカラム名を記載して、fillable変数に格納しよう。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    use HasFactory;

    protected $fillable = ['user_id', 'title', 'date', 'description'];

    // 「User:Task = 1:多」を設定
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

https://qiita.com/yyy752/items/820260163d4883efb132

むっくむっく

ユーザ削除画面

むっくむっく

ユーザを論理削除する場合、通常は削除フラグを設定する方法が一般的です。具体的には、ユーザーテーブルにdeleted_atというカラムを追加し、このカラムがNULLでないレコードは削除済みと見なすようにします。

以下に、Laravelで論理削除を実装する方法を示します。

  1. マイグレーションファイルの作成
    まず、deleted_atカラムを追加するマイグレーションファイルを作成します。

bash
コードをコピーする
php artisan make:migration add_deleted_at_to_users_table --table=users
生成されたマイグレーションファイルに、deleted_atカラムを追加するコードを記述します。

php
コードをコピーする
// database/migrations/xxxx_xx_xx_xxxxxx_add_deleted_at_to_users_table.php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddDeletedAtToUsersTable extends Migration
{
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->softDeletes(); // deleted_at カラムを追加する
});
}

public function down()
{
    Schema::table('users', function (Blueprint $table) {
        $table->dropSoftDeletes(); // deleted_at カラムを削除する
    });
}

}
2. モデルでSoftDeletesトレイトを使用する
次に、UserモデルでSoftDeletesトレイトを使用するように設定します。

php
コードをコピーする
// app/Models/User.php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class User extends Model
{
use HasFactory, SoftDeletes;

protected $dates = ['deleted_at']; // deleted_at カラムを日付で扱う

}
3. コントローラで論理削除を実装する
最後に、コントローラで論理削除を実装します。削除処理はHTTPのDELETEメソッドを使用する場合が一般的ですが、POSTでも実装可能です。

php
コードをコピーする
// UserController.php

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

public function destroy($id)
{
user = User::findOrFail(id);
$user->delete();

return redirect()->route('users.index')
                 ->with('success', 'ユーザが削除されました');

}
このように実装すると、deleteメソッドが呼ばれたときにdeleted_atカラムに現在のタイムスタンプが設定され、そのユーザーは論理的に削除されたものとして扱われます。

  1. 削除されたユーザーの取得
    削除されたユーザーを取得する場合は、withTrashedメソッドまたはonlyTrashedメソッドを使用します。

php
コードをコピーする
// 例: 論理削除されたユーザーを取得する
$deletedUsers = User::onlyTrashed()->get();
これにより、論理削除されたユーザーの一覧を取得できます。

以上が、Laravelにおけるユーザーの論理削除の実装方法です。物理削除ではなく、データの完全性を保ちつつ、削除済みのユーザーも管理することができます。

このスクラップは2ヶ月前にクローズされました