【Laravel】TODOアプリを作ってみた
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
画面設計
タスクの新規作成について
ログイン画面からメールアドレスとパスワードを入力し、ログインする。
ログイン後、タスク一覧画面が表示される。
+ボタンを押下すると、タスク新規作成画面へ遷移する。
タイトル、期限、担当者、内容を入力し、新規作成ボタンを押下する。
そうすると、タスク一覧画面へ戻り、作成したタスクが表示される。
タスクが完了した場合は、チェックボックスをタップする。
タスクの編集について
該当のタスクの行をタップすると、タスク編集画面へ遷移する。
タイトルなどの項目を編集し、編集するボタンを押下すると、タスクは編集された状態でタスク一覧画面へ戻る。
そして、削除するボタンを押下すると、タスクが削除された状態でタスク一覧画面へ戻る。
ユーザー新規作成・編集について
ヘッダーのハンバーガーメニューをタップすると、サイドバーが表示される。
そこでユーザー一覧をタップすると、ユーザー一覧画面へ遷移する。
+ボタンをタップすると、ユーザー新規作成画面へ遷移する。
ユーザー名、メールアドレス、パスワードを入力して新規作成ボタンを押下すると、ユーザーが新規作成されてユーザー一覧画面へ戻る。
編集したいユーザーの行をタップすると、ユーザー編集画面へ遷移する。
ユーザー名などを編集して編集するボタンを押下すると、ユーザーが編集された状態でユーザー編集画面へ戻る。
削除するボタンを押下すると、ユーザーが削除された状態でユーザー削除画面へ戻る。
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
テーブル設計
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
テーブル名について
・テーブル名は小文字の複数形で(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)
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
descと略さないで、descriptionと命名する。
descは降順と誤解しやすい。
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
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などある)
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
UsersとTasksのコネクターが逆になっている
1対多なので、1本線>3本線に繋ぐ
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
削除機能について
物理削除になっているが、論理削除にした方が良いのではないか?
fk機能の下記3パターン
userが削除された場合、fkの機能でtaskを削除される。
user_idがnullになる。
↓↓↓↓こちらを採用!!!
削除できないようにエラーにする。
・Laravelを介さず、手動でDBを操作した場合に備えてエラーになるように設定しておく。
↓↓↓↓論理削除を採用!!!
ユーザーテーブルを論理削除に対応させるパターン
deleted_at:datetime // laravelは自動的にselectして取得しない
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
Laravelの基礎を学習
下記の動画教材でLaravelについて学習しました。
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
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://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
MySQL データ型
データベースの型について知らなかったため、MySQLの公式ドキュメントを読みました。
知らなかったこと
BLOB型
・さまざまな容量のデータを保持できる大きなバイナリオブジェクト
商品コードなどの固定長のデータにはCHARを使う(0~255文字)
文字数がバラバラになるようなデータの場合はVARCHARを使う(0~65535文字)
それよりも長くなる文字列にはTEXTを使用する
SETはアンケートの回答などで複数選択する場合に使う
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
sessions
where id
= rYNvVZ4PDv2hG0VRssR4wlsGFCptl5kiCBAO47wY limit 1)
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'laravel.sessions' doesn't exist (Connection: mysql, SQL: select * from テーブル名が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
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
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)
SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo for mysql failed: nodename nor servname provided, or not known (Connection: mysql, SQL: select table_name as
DB_HOST=127.0.0.1 に設定すると、「php artisan migrate」を実行できた。
そもそも127.0.0.1とは、自分を意味するIPアドレス(ループバックアドレス)の一つ
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
データベース側の実装について
php artisan make:model Task -mを実行
-mによってモデルだけではなく、マイグレーションファイルも生成される。
モデルは単数系の命名にする。
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
マイグレーションとは?
あるデータベースの構造や内容を別のデータベースへ移行するプロセスのこと。
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');
}
}
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
unique()とは
unique()メソッドは、そのカラムに一意性制約(ユニーク制約)を設定することを意味します。
たとえば、上記のemailにuniqueが設定されていますが、このカラムに格納される値は全て異なるemailでなければなりません。
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
rememberTokenとは
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
ログイン画面の実装
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
@csrfとは
CSRF攻撃から保護するために使用されます。
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
バリデーションエラー表示
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
バリデーションの表示を日本語にする
・resources/lang/jaにvalidation.phpというファイルを作成
・下記の内容をファイルに追記
<?php
return [
'required' => ':attributeは必須です。',
'attributes' => [
'email' => 'メールアドレス',
'password' => 'パスワード',
]
];
・config/app.phpファイルのlocaleを'ja'に設定する
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
ログインの仕組みについて理解できていない
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
リダイレクトって何?
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
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')
]
]);
}
}
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
Bcryptとは
パスワードを安全に保存するために設計されたハッシュ関数
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
タスク一覧画面の実装
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
タスク一覧表示の際に、ユーザー名を表示する方法
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
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
タスク新規作成画面の実装
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
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(); // ここが追加されていることを確認
});
}
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
タスクの編集/削除画面
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
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://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
ユーザ削除画面
![むっく](https://res.cloudinary.com/zenn/image/fetch/s--Lb020bH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a0db435c12.jpeg)
ユーザを論理削除する場合、通常は削除フラグを設定する方法が一般的です。具体的には、ユーザーテーブルにdeleted_atというカラムを追加し、このカラムがNULLでないレコードは削除済みと見なすようにします。
以下に、Laravelで論理削除を実装する方法を示します。
- マイグレーションファイルの作成
まず、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->delete();
return redirect()->route('users.index')
->with('success', 'ユーザが削除されました');
}
このように実装すると、deleteメソッドが呼ばれたときにdeleted_atカラムに現在のタイムスタンプが設定され、そのユーザーは論理的に削除されたものとして扱われます。
- 削除されたユーザーの取得
削除されたユーザーを取得する場合は、withTrashedメソッドまたはonlyTrashedメソッドを使用します。
php
コードをコピーする
// 例: 論理削除されたユーザーを取得する
$deletedUsers = User::onlyTrashed()->get();
これにより、論理削除されたユーザーの一覧を取得できます。
以上が、Laravelにおけるユーザーの論理削除の実装方法です。物理削除ではなく、データの完全性を保ちつつ、削除済みのユーザーも管理することができます。