この連載記事では、Laravel を使用した Web アプリケーションの開発方法を紹介します。実際に(お決まりの?)ToDo アプリを開発する手順を通して Web 開発のエッセンスを学んでいただけるように書いていきます。取り扱う Laravel のバージョンは現時点で最新の 5.7 です。
第3章では、フォルダの一覧表示機能を作っていきます。
まずは環境構築ができていて Laravel の初期画面が表示されている前提で進めます。環境構築については下記の記事を参照してください。
ルーティング
ルーティングの設定
さて、まずはルーティングの設定から始めます。ここで作成するページは、タスク一覧ページです。フォルダの一覧表示と言ってもタスク一覧の左側のパネルがフォルダ一覧なのでしたね。この章ではまずこの左側のパネルだけを作ります。
URL 設計を見返していただくと、タスク一覧ページの設計は以下の通りです。
URL | メソッド | 処理 |
---|---|---|
/folders/{フォルダID}/tasks | GET | タスク一覧ページを表示する。 |
この設計を踏まえ、routes/web.php
に下記の記述をしてください。あらかじめ記載されているサンプルのルーティングは削除して構いません。
<?php
Route::get('/folders/{id}/tasks', 'TaskController@index')->name('tasks.index');
Route
クラスがルーティングの設定をしています。コードの意味はほとんど左から読んだままで、get
で /folders/{id}/tasks
にリクエストが来たら TaskController
コントローラーの index
メソッドを呼びだす、という記述です。また、最後にこのルートに名前をつけています。アプリケーションの中で URL を参照する際にはこの名前を使います。
ポイントは URL 中の {id}
でしょう。タスク一覧ページはフォルダごとのタスクを表示するので、/folders/123/tasks
や /folders/999/tasks
など、どのフォルダのタスクを表示したいかによって URL が変わります。その変わる部分を波括弧の箇所で表現します。波括弧の間の名前(今回は「id」)はどんな値でも構いません。
次にその TaskController
クラスおよび index
メソッドを作成しましょう。
コントローラークラス
コントローラークラスはコマンドラインから雛形を作成します。
$ php artisan make:controller TaskController
これで app/Http/Controllers
ディレクトリに TaskController.php
が作成されたはずです。以下の通り index
メソッドを追加してください。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class TaskController extends Controller
{
public function index()
{
return "Hello world";
}
}
とりあえず "Hello world"
と文字列を返却しています。試しに http://todos.test/folders/1/tasks にアクセスしてみましょう。
ちなみに URL のドメインは各自の設定値に読み替えてください。私は Valet を使って todos というディレクトリで開発しているので todos.test というドメインになっています。
さていかがでしょうか。ブラウザに「Hello world」と表示されていれば OK です。ひとまずリクエストとコントローラーメソッドを紐づけられたことが確認できましたね。
メソッドのロジックはあとで書くことにして、データベース周りを先に作っていきます。
マイグレーションとモデルクラス
データベースの接続設定
まずは接続設定を行います。todo
というデータベースを作成している前提です。環境構築に Homestead を使ったか Valet を使ったかで DB_USERNAME
と DB_PASSWORD
が異なります。
Homestead
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=todo
DB_USERNAME=homestead
DB_PASSWORD=secret
Valet
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=todo
DB_USERNAME=postgres
DB_PASSWORD=postgres
マイグレーションファイルの作成
ではマイグレーションファイルを作成します。マイグレーションとは何かについてはこちらの記事にまとめていますので先に進む前に読んでおいてください。
マイグレーションファイルもコマンドラインから作成できます。
$ php artisan make:migration create_folders_table --create=folders
database/migrations
ディレクトリに 2018_11_25_121216_create_folders_table.php
というような名前のマイグレーションファイルの雛形が作成されたはずです。2018_11_25_121216
の部分はファイルが作成された年月日時分秒ですので、各々で異なります。
フォルダーテーブルのテーブル定義をおさらいしましょう。
カラム論理名 | カラム物理名 | 型 | 型の意味 |
---|---|---|---|
ID | id | SERIAL | 連番(自動採番) |
タイトル | title | VARCHAR(20) | 20文字までの文字列 |
作成日 | created_at | TIMESTAMP | 日付と時刻 |
更新日 | updated_at | TIMESTAMP | 日付と時刻 |
このテーブル定義を Laravel マイグレーションの PHP コードで表現すると以下のコードになります。
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateFoldersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('folders', function (Blueprint $table) {
$table->increments('id');
$table->string('title', 20);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('folders');
}
}
編集しているのは up
メソッドですね。テーブル名は folders としました。格納したい物の名前の複数形にするのが一般的です。
$table->increments('id');
$table->string('title', 20);
$table->timestamps();
この部分でカラムの作成を指示しています。自動採番のカラムは increments
メソッドで、作成日と更新日はまとめて timestamps
メソッドで作成されます。
マイグレーションの実行
マイグレーションの実行もコマンドラインから行います。
$ php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_000000_create_users_table
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated: 2014_10_12_100000_create_password_resets_table
Migrating: 2018_11_25_121216_create_folders_table
Migrated: 2018_11_25_121216_create_folders_table
create_folders_table
マイグレーションが実行されました。create_users_table
と create_password_resets_table
マイグレーションも一緒に実行されていますが、こちらは Laravel で最初から用意されている認証機能のためのマイグレーションです。あとで使うので今は気にしなくて OK です。
データベースクライアントツールからデータベースの中を確認してみましょう。folders テーブルが作成されているでしょうか。
Object-Relational マッピングとは
次にモデルクラスを作成しますが、Object-Relational マッピング
Object-Relational マッピング(ORM)とは、アプリケーションからデータベースの操作をしやすくするためのプログラミング手法です。
オブジェクト指向モデルであるアプリケーション(例:PHP)とリレーショナルデータベース(例:PostgreSQL)では、そもそもデータの持ち方が違います。
オブジェクト指向では、クラスがあってプロパティにデータを持ちます。例えば以下のように。
class User
{
private $email;
private $password;
// 中略
}
一方リレーショナルモデルでは行と列、つまりテーブルでデータを持ちます。
users テーブル
| email | password |
|-----------------|----------|
| sample@mail.com | test1234 |
例えば users
テーブルから取得したデータを、毎回 User
クラスに詰めなおすのは面倒ですね。User
クラスのデータを user
テーブルに挿入したい場合も、いちいちプロパティからデータを引っ張ってきて INSERT 文を作るのは面倒です。そこであらかじめアプリ側のデータとテーブル側のデータの紐づけを定義しておこうというのが ORM です。
言葉だけでは理解が難しいと思いますので、習うより慣れよ、コードを書きながら ORM の働きを体得していってください。
モデルクラス
モデルクラスは PHP 側でデータの入れ物になるクラスです。基本的にはモデルクラスひとつがテーブルひとつに対応するように作ります。
$ php artisan make:model Folder
app
ディレクトリに Folder
モデルが作成されているでしょう。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Folder extends Model
{
//
}
Folder モデルの記述はこれだけで OK です。クラスの中身は何も書いていませんが、継承元である Model
クラスで様々な設定を読み取ってくれます。
例えばこのモデルクラスがどのテーブルに対応しているかはクラス名から自動的に推定されます。つまりモデルクラスのクラス名の複数形のテーブルが対応していると解釈されるのです。今回であれば folders テーブルですね。もちろんこのデフォルトの推定に当てはまらない場合は追加で設定を書けばいいのですが、今回はこの仕組みに合わせてあるのでその必要はありません。
テストデータを挿入する
ここまででデータを扱う準備ができたのですが、テストデータが入っていた方がコントローラーを書きやすいので、データを挿入します。データベースクライアントから直接 SQL を実行してデータを挿入してもよいですが、シーダー(Seeder)を用いた方法を紹介しておきます。
シーダーはテストデータを入れるための仕組みです。以下のコマンドで作成します。
$ php artisan make:seeder FoldersTableSeeder
database/seeds
ディレクトリに FoldersTableSeeder.php
が出来ているので、以下の通り編集してください。
<?php
use Carbon\Carbon;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class FoldersTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$titles = ['プライベート', '仕事', '旅行'];
foreach ($titles as $title) {
DB::table('folders')->insert([
'title' => $title,
'created_at' => Carbon::now(),
'updated_at' => Carbon::now(),
]);
}
}
}
run
メソッドの中にデータを挿入するコードを記述します。ここでは「プライベート」「仕事」「旅行」という三つのフォルダを作ります。作成日と更新日には Carbon というライブラリを使って現在日時を入れています。
コマンドラインからシーダーを実行します。
$ composer dump-autoload
$ php artisan db:seed --class=FoldersTableSeeder
最初の composer
コマンドは、作成したシーダークラスをアプリケーションに認識させるためのものだと思ってください。db:seed
コマンドで「Database seeding completed successfully.」と返ってきたら成功です。データベースクライアントでテーブルの中身を確認してみましょう。
データが挿入されています。
コントローラー
データまで揃ったので、コントローラーにロジックを記述していきます。
<?php
namespace App\Http\Controllers;
use App\Folder;
use Illuminate\Http\Request;
class TaskController extends Controller
{
public function index()
{
$folders = Folder::all();
return view('tasks/index', [
'folders' => $folders,
]);
}
}
まず Folder
モデルの all
クラスメソッドですべてのフォルダデータをデータベースから取得しています。Laravel の ORM は強力なので、SQL をまったく書かずにデータを取得できています。
次に view
関数でテンプレートに取得したデータを渡した結果を返却しています。view
関数の第一引数がテンプレートファイル名(後ほど作成します)で第二引数がテンプレートに渡すデータです。第二引数には配列を渡しますが、キーがテンプレート側で参照する際の変数名となります。
このように view
関数の結果をコントローラーメソッドから返却すると、テンプレートをレンダリングした結果の HTML がフレームワークによってブラウザにレスポンスされます。
続いてテンプレートを書いていきましょう。
テンプレートの作成
テンプレートとは
テンプレートとはアプリケーションがレスポンスする HTML の雛形で、制御構文(if や foreach など)や変数の展開を記述することができます。ページの枠組みだけ用意して、URL によって変わる箇所だけが穴埋めになっているイメージです。雛形が同じでもデータを変えることで別のページを作り出せる仕組みですね。
テンプレートエンジンとはテンプレートを HTML に変換するライブラリです。テンプレートエンジンが変わればテンプレートの書き方も変わってきます。Laravel のテンプレートエンジンは Blade という名前です。
Blade テンプレートエンジン
テンプレートに関しては作成するための artisan コマンドは用意されていないので、手動でファイルを作成します。resources/views
ディレクトリに tasks
ディレクトリを作成してください。このディレクトリをタスク関連のテンプレート置き場にします。
$ mkdir resources/views/tasks
tasks
ディレクトリに index.blade.php
というファイルを作成しましょう。.blade.php
という拡張子はテンプレートファイルとして認識されるために決まっています。
$ touch resources/views/tasks/index.blade.php
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 App</title>
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<header>
<nav class="my-navbar">
<a class="my-navbar-brand" href="/">ToDo App</a>
</nav>
</header>
<main>
<div class="container">
<div class="row">
<div class="col col-md-4">
<nav class="panel panel-default">
<div class="panel-heading">フォルダ</div>
<div class="panel-body">
<a href="#" class="btn btn-default btn-block">
フォルダを追加する
</a>
</div>
<div class="list-group">
@foreach($folders as $folder)
<a href="{{ route('tasks.index', ['id' => $folder->id]) }}" class="list-group-item">
{{ $folder->title }}
</a>
@endforeach
</div>
</nav>
</div>
<div class="column col-md-8">
<!-- ここにタスクが表示される -->
</div>
</div>
</div>
</main>
</body>
</html>
28行目からがポイントですね。
@foreach($folders as $folder)
<a href="{{ route('tasks.index', ['id' => $folder->id]) }}" class="list-group-item">
{{ $folder->title }}
</a>
@endforeach
テンプレートの中では PHP のように foreach が使えています。ただしテンプレートではアットマーク @
を付けるので注意しましょう。
そして foreach の中でコントローラーから渡されたデータ $folders
を参照しています。
[
'folders' => $folders,
]
キーと値が同じなのでややこしいですが、あくまでテンプレート側ではキー名が変数名になることを覚えておいてください。
変数の値の展開は、{{ $data }}
のように波括弧二つで実現します。ここでは二箇所で使われていますね。
まずはタイトルの表示 {{ $folder->title }}
です。$folders
にすべてのフォルダのデータが入っているので、foreach でループした一つのアイテムである $folder
はフォルダテーブルの一行に相当すると考えられます。カラムの値は ->title
と、プロパティのように参照することができます。
もう一つはアンカーリンクの href 属性です。
route('tasks.index', ['id' => $folder->id])
Laravel が提供している route
関数の結果を href の値として展開しています。route
関数はルーティングの設定から URL を作り出す関数です。
Route::get('/folders/{id}/tasks', 'TaskController@index')->name('tasks.index');
route
関数の第一引数はルート名です。上記の通り、ルーティングの際に get
メソッドに続けて呼び出した name
メソッドの引数がそのルートの名前です。route
関数の第二引数として渡している配列は、ルート URL のうち変数になっている部分(ここでは {id}
)に実際の値を埋める役割です。
なおテンプレートについて詳しくはマニュアル 公式 / 日本語を参照してください。
スタイルシート
画面から確認する前に以下のスタイルシートを public/css
ディレクトリに作成してください。今回は CSS フレームワーク Bootstrap と Bootstrap にさらにスタイルを足した Bootflat を使って見た目を整えています。
@import url('https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css');
@import url('https://cdnjs.cloudflare.com/ajax/libs/bootflat/2.0.4/css/bootflat.min.css');
body {
background-color: #f4f7f8;
}
.navbar {
margin: 2rem 0 2.5rem 0;
}
.my-navbar {
align-items: center;
background: #333;
display: flex;
height: 6rem;
justify-content: space-between;
padding: 0 2%;
margin-bottom: 3rem;
}
.my-navbar-brand {
font-size: 18px;
}
.my-navbar-brand,
.my-navbar-item {
color: #8c8c8c;
}
.my-navbar-brand:hover,
a.my-navbar-item:hover {
color: #ffffff;
}
.table td:nth-child(2),
.table td:nth-child(3),
.table td:nth-child(4) {
white-space: nowrap;
width: 1px;
}
.form-control[disabled],
.form-control[readonly] {
background-color: #fff;
}
ここまでできたら一度ブラウザで確認してみましょう。http://todos.test/folders/1/tasks にアクセスしてください(ドメインは各自の設定によって読み替えてください)。
いかがでしょう?左側のパネルにシーダーで挿入した三つのフォルダが表示されているはずです。またフォルダ名をクリックしてみてください。それぞれの ID に合わせて /folders/1/tasks
/folders/2/tasks
/folders/3/tasks
というリンクが出来ているでしょう。
フォルダ名を選択表示にする
最後にもう一つ機能を追加します。フォルダ名をクリックするとそれぞれのフォルダの URL に遷移することができますが、どのフォルダが選ばれているのかは画面上では分かりません。
そこで、上記のようにアクセスされているフォルダ名だけ選択表示(水色背景)にします。
実装方法としては、URL の変数部分、つまり '/folders/{id}/tasks'
の {id}
の値をコントローラーで受け取ってテンプレートに渡します。テンプレートではループの中でコントローラーから渡された {id}
の値に合致する場合だけ CSS で水色背景を表現する HTML クラスを出力します。
コントローラー
ではコントローラーから見ていきましょう。index
メソッドを以下のように書き換えてください。
public function index(int $id)
{
$folders = Folder::all();
return view('tasks/index', [
'folders' => $folders,
'current_folder_id' => $id,
]);
}
まずは URL の変数部分をコントローラーで受け取る方法ですが、以下のようにコントローラーメソッドの引数として受け取ります。この時の引数名はルーティングで定義した波括弧内の値と合致していなければいけません。
public function index(int $id)
今回は {id}
と定義したので $id
で受け取っています。仮に {sample_value}
であれば $sample_value
で受け取る必要があるということです。
次に受け取った値をテンプレートに渡しています。
'current_folder_id' => $id,
ただ id では分かりにくいので current_folder_id
という名前で参照するように記述しました。
テンプレート
テンプレートは resources/views/tasks/index.blade.php
の29行目のリンクを編集します。
<a
href="{{ route('tasks.index', ['id' => $folder->id]) }}"
class="list-group-item {{ $current_folder_id === $folder->id ? 'active' : '' }}"
>
見やすいように折り返していますがコードを足したのは class 属性の部分です。三項演算子を用い、ループしているフォルダデータのうち $current_folder_id
つまり閲覧されているフォルダの ID と ID 値が合致する場合のみ 'active' という HTML クラスを出力しています。
ここまでできたら再度ブラウザで確認してみましょう。
フォルダ名をクリックすると選択表示になっていると思います。
最後のおまけ(ログの出力先)
アプリケーションログの出力先を紹介しておきます。
storage/logs/laravel.log
開発中のエラーは画面にも内容が出力されますが、こちらのファイルにも出力されます。
第3章はこれでおしまいです。
ここまでのソースコードはリポジトリ(chapter03 ブランチ)を参照してください。
次の章では同じタスク一覧画面で、タスクの一覧を表示させます。この章と要領は同じですが、ORM でのリレーションの表現が登場します。
連載記事
- 入門Laravelチュートリアル (1) イントロダクション
- 入門Laravelチュートリアル (2) ToDoアプリケーションの設計
- 入門Laravelチュートリアル (3) ToDoアプリのフォルダ一覧表示機能を作る
- 入門Laravelチュートリアル (4) ToDoアプリのタスク一覧表示機能を作る
- 入門Laravelチュートリアル (5) ToDoアプリのフォルダ作成機能を作る
- 入門Laravelチュートリアル (6) ToDoアプリのタスク作成機能を作る
- 入門Laravelチュートリアル (7) ToDoアプリのタスク編集機能を作る
- 入門Laravelチュートリアル (8) ToDoアプリの認証機能を作る
- 入門Laravelチュートリアル (9) ToDoアプリの認証機能を作る パート2
- 入門Laravelチュートリアル (10) エラーハンドリング
- 入門Laravelチュートリアル (11) ToDoアプリをHerokuにデプロイする