MENU

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

2021 5/17
【Laravel】Todoアプリを作ってみた

おつかれさまです!!ご無沙汰しております。

私フロントエンド志望なんですが、バックエンドとの連携も切っては切り離せないという背景を踏まえて、少しだけPHPフレームワークのLaravelを触っていました。

そこで一通り簡単なTodoアプリを作成してみたので、その感想や苦労した点をメモ書き程度に記していこうと思います。

デモサイト:https://rik9228.xsrv.jp

github:https://github.com/rik9228/laravel_todos

目次

開発環境・使用ツールのバージョン

OSDockerPHPLaravelComposerNode.js
MacOS20.10.58.0.36.20.262.0.1314.15.1

Dockerの構築や使い方はこちらの記事を参考にしました ^ ^

Qiita
最強のLaravel開発環境をDockerを使って構築する - Qiita
最強のLaravel開発環境をDockerを使って構築する - Qiitaお急ぎの方は 使い方 からお読みください。 ※Linuxはもちろんですが、Docker for Mac(M1 Mac含む), Docker for Windows で動作します。 (自分では試してないけど他の人が...

アプリ作成の流れ(準備)

①仕様の考案

ここではアプリケーション内ではどんなデータがあって、ユーザーは何をすることができるのかという要件を作成前に整理します。
(とはいえただのTodoアプリなので、そこまで踏み込んだ内容ではありませんが主に以下の情報整理をしています)

・ユーザーはタスクを作成することができる。

・タスクはID、タイトル、状態を持つ。(タスクの状態は「未着手」「着手中」「完了」の3種類を持つ。)

・ユーザーはタスクを一覧表示することができる。

・ユーザーはタスクのタイトル、状態を編集することができる。

・ユーザーはタスクを削除することができる。

②構成するページ及び要素

ここではアプリケーションを構成するページとページが有する各要素や機能を整理します。

・アプリトップページ

・タスク一覧ページ

■要素
・ID
・タイトル
・状態ラベル
・期限日
・編集ボタン
・削除ボタン
・新規作成ボタン

■アクション(機能)
・タスクの作成
・タスクの編集
・タスクの削除

・タスク作成ページ

■要素
・タイトル
・状態
・期限日
・確定ボタン
・戻るボタン

■アクション(機能)
タスクを新規作成を確定(リクエスト送信)

・タスク編集ページ

■要素
・タイトル
・状態
・期限日
・確定ボタン
・戻るボタン

■アクション(機能)
タスクの編集を確定(リクエスト送信)

③テーブルの設計

ここではアプリケーションを作成するにあたって用いるテーブルの設計をします。
また、そのテーブルがどんなデータ(カラム)を持っているのか整理します。

使用するテーブル:タスクテーブル

カラム論理名カラム物理名
IDidINTEGER
タイトルtitleVARACHAR(50)
期限日due_dateDATE
状態statusINTEGER
作成日created_atTIMESTAMP
更新日updated_atTIMESTAMP

④URL設計

ここではLaravelのルーティングを用いて、対象のURLにHTTPメソッドを行うことで任意の処理を行うこと、またはURLの命名設計を行います。

URLメソッド処理内容
/todosGETタスク一覧ページを表示
/todos/createGETタスク登録ページを表示
/todos/createPOSTタスク登録を実行
/todos/{タスクID}/editGETタスク編集ページを表示
/todos/{タスクID}/editPOSTタスク編集を実行
/todos/{タスクID}/deletePOSTタスク削除を実行

作成前の設計はこんな感じになります。

アプリ作成の流れ(コーディング)

実際に作っていきます。

作成時の流れとしてはざっくりいうと、

  1. テーブルの作成(マイグレーション)
  2. モデルの作成
  3. ルーティングの作成
  4. コントローラーの作成
  5. ビューの作成

みたいな感じでしょうか。※3,4,5あたりを頻繁に行います。

全てコードを載せるとアレなので(Githubをご参考ください)作る上でポイントとなるコードを掲載します。

テーブルにあるデータを一覧表示する

ここではDBのテーブルより実際にあるすべてのtodoデータを一覧表示していきます。

まず初めに必要となるテーブルを作成する必要があります。

ターミナルで以下のコマンドを実行しましょう。

php artisan make:migration {テーブル名}_table // 通常時

docker-compose exec app php artisan make:migration {テーブル名}_table // 今回のDocker環境でのコマンド

すると、database/migrations 配下にマイグレーションファイルが作成されます。

<?php

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

class CreateTodosTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('todos', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title', 50);
            $table->integer('status')->default(1);
            $table->date('due_date');
            $table->timestamps();
        });
    }

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

上で作成されたマイグレーションファイルを編集します。

DBのテーブル作成や編集などを管理する方法やその仕組みのことをマイグレーションと呼びます。

このファイルを用いることで実際にテーブル設計で整理した内容をもとにテーブルを作成したり削除することができます。

マイグレーションファイルの作成が済んだら、ターミナルで以下のコマンドを実行します。

php artisan migrate // マイグレーションの実行(通常時)

docker-compose exec app php artisan make migrate {テーブル名}_table // 今回のDocker環境でのコマンド

これでテーブルができます。

続いてルーティングを設定するファイルとコントローラーファイルに移ります。

Web.php(3.ルーティングの作成)

<?php

use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Auth;

/*
|--------------------------------------------------------------------------
| 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::get('/todos', 'TodosController@index')->name('todos_index');

Route::get('/todos/create', 'TodosController@create')->name('todos_create');
Route::post('/todos/create', 'TodosController@store')->name('todos_store');

Route::get('/todos/{id}/edit', 'TodosController@edit')->name('todos_edit');
Route::post('/todos/{id}/edit', 'TodosController@update')->name('todos_update');

Route::post('/todos/{id}/delete', 'TodosController@destroy')->name('todos_delete');

TodosController.php(4.コントローラーの作成)

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Requests\TodoRequest;
use App\Models\Todo;

class TodosController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $todos = Todo::paginate(4);
        return view('todos.index', ['todos' => $todos]);
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('todos.create');
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(TodoRequest $request)
    {
        $todo = new Todo;
        $form = $request->all();
        $todo->fill($form)->save();

        return redirect()->route('todos_index');
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit(Request $request)
    {
        $todo = Todo::find($request->id);
        return view('todos.edit', ['todo' => $todo]);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(TodoRequest $request, $id)
    {
        $todo = Todo::find($id);
        $todo->title = $request->title;
        $todo->status = $request->status;
        $todo->due_date = $request->due_date;
        $todo->save();
        return redirect()->route('todos_index');
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        $todo = Todo::find($id);
        $todo->delete();
        return redirect()->route('todos_index');
    }
}

Web.phpではURLの設計時に行った情報をもとに実際にHTTPメソッドを用いてLaravelにどんな処理を行わせるかを作成していきます。

もう少し具体的に言うと、

Route::get('/todos', 'TodosController@index')->name('todos_index');

この場合だと

もし、/todos にアクセスした時(GET)、TodosControllerのindexメソッドを呼ぶ。

という内容になります。

なので、/todosにアクセスした時に

public function index()
    {
        $todos = Todo::paginate(4);
        return view('todos.index', ['todos' => $todos]);
    }

上の処理が呼ばれ、さらに対象のviewファイルを表示する流れになります。

ちなみにこちらでは

  1. Todoモデル(後で説明します)を使って、DBに格納されているtodosテーブルよりを実際に引っ張ってきて、$todosという変数に格納。
    ※Todo::paginateでは対象のレコードを4件ずつ取り出します。
    (1ページに表示するデータ数を最大4件までに設定するため)
  2. 'todos'という変数にデータを格納してそれを対象のviewファイルに渡します。

という処理になります。

そして、次にLaravelが指定のviewファイルを参照しにいきます。

return view('todos.index', ['todos' => $todos]);

図に表すとこんな感じになります。

app/
 ~ 割愛 ~
 ├ resources/
 │ └ views/
 |  ├ layouts
 │   └ todos/
 |    ├ index.blade.php ← ココ
 |    ├ create.blade.php
 |    └ edit.blade.php
 └ …/

index.blade.phpはコチラ

@extends('layouts.common')

@section('title','一覧ページ')

@include('layouts.header')

@section('content')

<h1>Todo一覧</h1>

<!-- @if (Auth::check())
<p>ログインしているユーザー名: {{$user->name . ' (' . $user->email . ')'}}</p>
@else
<p>※ログインしていません。(<a href="/todos/auth">ログイン</a>|
  <a href="/todos/register">登録</a>)</p>
@endif -->

<table class="table">
  <tr>
    <th scope="col">#</th>
    <th scope="col">タスク名</th>
    <th scope="col">状態</th>
    <!-- <th scope="col">登録日</th> -->
    <th scope="col">期限日</th>
    <th scope="col"></th>
    <th scope="col"></th>
  </tr>
  @foreach ($todos as $todo)
  <tr>
    <td>{{$todo->id}}</td>
    <td>{{$todo->title}}</td>
    <td><span class="label {{ $todo->status_class }}">{{$todo->status_label}}</span></td>
    <!-- <td>{{$todo->created_at}}</td> -->
    <td>{{$todo->due_date}}</td>
    <td><a class="btn btn-outline-secondary" href="{{ route('todos_edit', $todo->id)}}">編集</a></td>
    <td>
      <form action="{{ route('todos_delete', $todo->id)}}" method="POST">
        @csrf
        <input class="btn btn-danger" id="js-delete" type="submit" value="削除"></input>
      </form>
    </td>
  </tr>
  @endforeach
</table>

<div class="linkWrapper">
  {{ $todos->links() }}
</div>

<div class="linkWrapper">
  <a class="bg-secondary link" href="{{route('todos_create')}}">タスクを登録</a>
</div>

@endsection

ここでのポイントは

  @foreach ($todos as $todo)
  <tr>
    <td>{{$todo->id}}</td>
    <td>{{$todo->title}}</td>
    <td><span class="label {{ $todo->status_class }}">{{$todo->status_label}}</span></td>
    <!-- <td>{{$todo->created_at}}</td> -->
    <td>{{$todo->due_date}}</td>
    <td><a class="btn btn-outline-secondary" href="{{ route('todos_edit', $todo->id)}}">編集</a></td>
    <td>
      <form action="{{ route('todos_delete', $todo->id)}}" method="POST">
        @csrf
        <input class="btn btn-danger" id="js-delete" type="submit" value="削除"></input>
      </form>
    </td>
  </tr>
  @endforeach

こちらでしょうか。

先のController処理ではDBよりtodosのデータが変数に格納されて、viewファイルに渡されるという流れでした。

なので、foreach文でこちらのテンプレートファイルに実際にその内容を流し込んでいきます。
そして一覧ページが出来上がります。

※画像は実際にタスクを作成した後の画像です。

ここまでテーブルにあるデータを一覧で実際に表示する流れを説明しました。

このほかにも編集や削除処理なども同じく記載してあるので、よかったら覗いてみてください👀

補足:Modelについて

先のController処理で「モデル」という言葉が出てきましたが、モデルとは一体何なのでしょうか?

結論から言いますと、

データベースのテーブル情報をphpで読み込みやすいようにクラスのインスタンスとして持てるようしたもの

みたいな感じになります。

つまりは通常SQLを使用しないといけないところをPHPで処理ができるようにLaravel内でDBのテーブルを実体として扱えるようにするみたいなイメージになります。

先程のコントローラー処理ではモデルを使ってDBから対象のテーブルを引っ張ってきていたというような仕組みになります。

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Requests\TodoRequest;
use App\Models\Todo; // コレ

class TodosController extends Controller
{
・
・
・
public function index()
    {
        $todos = Todo::paginate(4); // ココ
        return view('todos.index', ['todos' => $todos]);
    }

ちなみに今回、使用したモデルファイルはこちらになります。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Todo extends Model
{
    protected $fillable = ['title', 'status', 'due_date'];
    // ユーザー側で自由に変更が効く項目

    /**
     * 状態定義
     */
    const STATUS = [
        1 => ['label_text' => '未着手', 'html_class' => 'btn-danger'],
        2 => ['label_text' => '着手中', 'html_class' => 'btn-primary'],
        3 => ['label_text' => '完了', 'html_class' => 'btn-success'],
    ];

    /**
     * 状態のラベル
     * @return string
     */
    public function getStatusLabelAttribute()
    {
        // 状態値
        $status = $this->attributes['status'];

        // 定義されていなければ空文字を返す
        if (empty(self::STATUS[$status])) {
            return '';
        }

        return self::STATUS[$status]['label_text'];
    }

    /**
     * 状態を表すHTMLクラス
     * @return string
     */
    public function getStatusClassAttribute()
    {
        // 状態値
        $status = $this->attributes['status'];

        // 定義されていなければ空文字を返す
        if (empty(self::STATUS[$status])) {
            return '';
        }

        return self::STATUS[$status]['html_class'];
    }
}

通常、モデルでは他モデルとの関係性を定義したり(本記事では触れていません)、テーブル内のデータに対してそれがユーザ側で変更していいものなのか否かを制約したり、「アクセサ」という仕組みを使えばDBから取り込んだデータをいい感じに整形してViewファイルで出力できるようにするなんていうこともできたりします。

アクセサ引用:https://qiita.com/hitochan/items/9cd9f2cbbbdd35916a96

以上、ざっくり解説でした。

苦労したところ

エラー祭り

言わずもがなですが、実際に自分で作ってみるとエラー祭りでした\(^o^)/

例えば、テーブル周り。

コントローラーでテーブルのデータを更新したり、編集したりするのですが、少しでもテーブルとのデータの整合性が取れないとしょっちゅうエラーを吐くことがありました。

そのほかには例えば以下のようなエラーメッセージが印象的でした。

・column cannot be null ▶︎ テーブルの値のNull制約が要因

・Add [***] to fillable property to allow mass assignment on *** ▶︎ モデル内の更新可能かどうかのデータ定義が要因

・Target class [App\Http\Controllers\***] does not exist ▶︎ namespaceの定義が要因

あとは、パラメータがうまく渡されていなかったりそういうとういうところでしたね。、(^^;)

Xserverでのデプロイ作業

最後のボスでした。

こちらの記事を参考に上から試して行ったんですが、如何せんかなり重た目の作業だったのと、慣れないコマンド作業がメインだったということもあり、かなりエラーに差し掛かりました。

その大体が見落としや勘違いだったんですが、かなり苦戦しましたね^^;

とはいえ、無事にデプロイができたのでよかったです。

シロウブログ「change life」
【完全版】LaravelアプリをGitでXserverにデプロイする手順|シロウブログ「change life」
【完全版】LaravelアプリをGitでXserverにデプロイする手順|シロウブログ「change life」こんにちは、シロウ(shiro_life0)です。 おそらくこの記事を読んでいる方は [chat face="yaruk

まとめ

ここまで読んでいただきありがとうございました。いかがだったでしょうか。

これまで何か作るにしてもYoutubeやQiitaにあるチュートリアルをそのまま写経していたので、1から作ることはありませんでした。

  • MVCモデル
  • ルーティング
  • Viewテンプレートの扱い

しかしながら、簡単なTodoアプリを作成してみることでよりこれらの3点に関してはより理解が深まった気がします。

Laraveに限った話ではありませんが、プログラミングにおいてやはりある程度インプットが済んだ段階で実際に何か作ってみることが大切だなと改めて感じました。

また、今回Laravelを触ってみてDBから対象のデータが取れたり、意図した挙動になると楽しいところもあったので、バックエンドに触れてみるのもアリだなという収穫もありました。

以上、至らない点が多く恐縮ですが、こちらの記事を通して少しでも誰かの役に立てると幸いです。

それではまた次回お会いしましょう〜〜✋


もりけん塾で学習中!

もりた先生のブログ: kenjimorita.jp (武骨日記)
もりた先生のアカウント: twitter.com/terrace_tech

この記事を書いた人

コメント

コメントする

CAPTCHA


目次
閉じる