前提
人の脳は2日で73%忘れると聞いたため学んだことを復習していきます。
本題
return view('posts.index', ['posts' => $posts]);
viewメソッドの第一引数には、ビューファイル名を渡す。
'posts.index'とすることで、resources/views/postsディレクトリにある、indexという名前のビューファイルが表示される。
viewメソッドの第二引数には、ビューファイルに渡す変数の名称と、その変数の値を連想配列形式で指定。
ここで、'posts'というキーを定義することで、ビューファイル側で$postsという変数が使用できるようになる。
また、Laravelでは、それ以外のコードの書き方でも変数をビューに渡すことが可能。
1、withメソッド
return view('posts.index')->with(['posts' => $posts]);
viewメソッドにwithメソッドを繋げて、withメソッドの引数にビューファイルに渡す変数の名称と、その変数の値を連想配列形式を指定。
2、compact関数
return view('posts.index', compact('posts'));
viewメソッドの第二引数に、compact関数を使った結果を渡す。
compact関数を使うと、変数を連想配列形式で記述しなくて良いため、コードの量が減る。
外部キー制約
$table->foreign('user_id')->references('id')->on('users');`
postsテーブルのuser_idカラムは、usersテーブルのidカラムを参照すること
という制約。
こうすることで、postsテーブルのuser_idカラムには、usersテーブルに存在するidと同じ値しか入れられなる。
つまり、「投稿は存在するけれど、それを投稿したユーザーが存在しない」という状態を作れないようになる。
uniqueメソッド
カラムにユニーク制約を付ける。
ユニーク制約とは、このテーブル内で他のレコードと同じ値を重複させないという制約。
nullableメソッド
カラムにnullが入ることを許容する。
Eloquent
Eloquent ORM(Eloquent Object Relational Mapping)
様々なドキュメントでEloquentという言葉が頻繁に登場する。
Eloquent=モデルのこと。
リレーション
Postモデルは、Modelクラスを継承していることで、belongsToメソッドというものが使える。
belongsToメソッドの引数には関係するモデルの名前を文字列で渡す。
そうすると、belongsToメソッドは、関係するモデルとのリレーション(belongsToメソッドの場合は、BelongsToクラス)を返す。
このようなリレーションを返すuserメソッドを作っておくと、$post->user->nameとコードを書くことで、投稿モデルから紐付くユーザーモデルのプロパティ(ここではname)にアクセスできるようになります。
記事と、記事を書いたユーザーは多対1の関係ですが、そのような関係性の場合には、belongsToメソッドを使う。
それ以外の関係性の場合は、それぞれ以下のメソッドを使う。
1対1の関係は、hasOneメソッド
1対多の関係は、hasManyメソッド
多対多の関係は、belongsToManyメソッド
戻り値の型宣言
<?php
// 略
use Illuminate\Database\Eloquent\Relations\BelongsTo;
// 略
public function user(): BelongsTo
{
// 略
}
userメソッドの定義を開始している行に、:BelongsToという記述がある。
これは、このメソッドの戻り値の「型」を宣言している。
PHP7では、こうした関数・メソッドの戻り値の型宣言が利用できる。
型とは、整数、文字列、配列、オブジェクト、クラスなど(他にも型の種類はある)。
ここでは、userメソッドの戻り値が、BelongsToクラスであることを宣言している。
その結果、もしuserメソッドがBelongsToクラスではなく、整数や文字列などの別の型を返そうとした場合、その時点でTypeErrorという例外が発生して処理が終了する。
型宣言を行うメリットとしては、安全性と可読性が挙げられる。
まず、安全性については、今回のuserメソッドの呼び出し元にBelongsToクラス以外の型を返してしまって、そのまま処理が突き進んでしまって想定外の処理結果になることが防げるので、システム全体として安全と言える。
また、可読性については、他のエンジニアがuserメソッドを見たときに、このメソッドが最終的にどのような型を返すのかが、メソッド内のコードを読まなくても一目で分かるため、userメソッドを使った別の処理を作りやすいという点が挙げられる。
トレイト
<?php
// 略
trait RegistersUsers
{
// 略
}
上記のようにtraitと宣言されているものがトレイト。
トレイトは、そのままではクラスとして使用できず、他のクラスの中でuse トレイト名
と記述する。
<?php
// 略
class RegisterController extends Controller
{
// 略
use RegistersUsers;
// 略
}
このようにすると、このクラスにおいて、トレイト内で定義している機能が使えるようになる。
汎用性の高い機能をトレイトとしてまとめておき、他の複数のクラスで共通して使う。
Laravelではユーザー登録に関する機能をRegistersUsers
トレイトとしてまとめておいてくれているため、もしRegisterController
クラス以外にもユーザー登録に関するクラスを作りたい場合にも、このトレイトを使って効率的に開発できるようになっている。
なお、似たような仕組みとして、あるクラスの機能を別のクラスでも使用できるようにする「継承」があるが、PHPではひとつのクラスが別のクラスを2つ以上継承することはできない。
(親クラスを継承した子クラスを孫クラスが継承するということは可能。2つの親クラスを子クラスが継承するということが不可。)
一方、トレイトはいくつでも同時に使用(use)できる。
三項演算子
?:
は三項演算子と呼ばれるもの。
三項演算子は、式1 ? 式2 : 式3という形式で記述し、以下の結果となる。
・式1がtrueの場合は、式2が値となる
・式1がfalseの場合は、式3が値となる
また、式2を省略して、式1 ?: 式3という形式で記述することもできる。その場合は、以下の結果となる。
・式1がtrueの場合は、式1が値となる
・式1がfalseの場合は、式3が値となる
よって、今回の処理の場合は、以下の値がregisterアクションメソッドの呼び出し元に返されることになりる。
・$this->registered($request, $user)の戻り値が何かあれば、その戻り値
・$this->registered($request, $user)の戻り値が無ければ、redirect($this->redirectPath())の戻り値
バリデーション
alpha_num
は、英数字であるか。
unique:users
は、usersテーブルの他のレコードのnameカラムに、(ユーザー登録画面から)リクエストされたnameと同じ値が無いことをチェック。
なお、カラム名がリクエストされたパラメータ名と異なる場合には、以下のようにテーブル名の後にカンマで区切ってカラム名を指定する。
例えば以下は、リクエストされたパラメータ名はnicknameであるが、チェック対象のusersテーブルのカラム名がnameである場合の例。
'nickname' => ['unique:users,name']
old関数
<input class="form-control" type="text" id="name" name="name" required value="{{ old('name') }}">
old関数は、引数にパラメータ名を渡すと、直前のリクエストのパラメータを返す。
例えば、、、
ユーザー登録処理でバリデーションエラーになると再びユーザー登録画面が表示されますが、特に何も対応していなかった場合、全ての項目が空で表示されて入力を最初からやり直さなければならない。
old関数を使うことで、入力した内容が保持された状態でユーザー登録画面が表示されるようになり、ユーザーはエラーになった箇所だけを修正すれば良くなる。
ただし、passwordとpassword_confirmationはold関数を使ってもnullが返る。
そのため、これら入力項目のinputタグに対してはold関数を使わない。
@guest、@auth
@guestから@endguestに囲まれた部分は、ユーザーがまだログインしていない状態の時のみ処理される。
@authから@endauthに囲まれた部分は、ユーザーがログイン済みの状態の時のみ処理される。
これらを使って、ログイン前、ログイン済みそれぞれで見せるべきメニュー、見せるべきでないメニューを出し分けるように設定できる。
$errors変数
Laravelでは、Bladeの中で$errors変数を使える。
tinker
Laravelに用意された機能のひとつ。
コマンドラインでPHPのコードを実行できる。
Laravel上で動いているため、Laravelの関数やクラスなども使える。
php artisan tinker
ログイン試行回数
maxAttemptsメソッドがログインを試せる回数の上限、
decayMinutesメソッドが時間間隔(単位は分)を返している。
それぞれメソッドでは、メソッドと同じ名前のプロパティが存在するかを調べ、存在すればそのプロパティの値を返す。
存在しない場合はデフォルト値(5回および1分)を返す。
もし、このデフォルト値をカスタマイズしたい場合は、LoginControllerにmaxAttemptsプロパティ、decayMinutesプロパティを追加すれば良い。オーバーライド(上書き)
キャッシュについて
ログイン試行回数はLaravelのキャッシュで管理されている。
Laravelのキャッシュの保存先は、デフォルトの設定ではファイルとなっており、storage/framework/cache/dataディレクトリに保存される。
キャッシュの保存先の設定はconfigディレクトリのcache.phpにある。
なお、本格的なWebサービスであればキャッシュの保存先はファイルではなく、より高速に読み書きできるメモリとすることが多い。
メモリを使うにはmemcachedやRedisといったソフトウェアを導入する必要がある。
remember meトークン
<input type="hidden" name="remember" id="remember" value="on">
inputタグには、次回から自動でログインする
という説明がされたチェックボックスに相当するものがある。
上記の場合は、type属性をcheckboxではなくhiddenとすることでユーザーが直接操作できない隠し項目とし、value属性をonとすることで常にチェックが入ったのと同じ状態にしている。
上記のinputタグがあることで、ユーザーがログインボタンを押した際にメールアドレスとパスワード以外にもrememberという名前のパラメータがonの状態でPOST送信される。
この結果どうなるかというと、ユーザーがログインした後はログアウト操作を行わない限り、そのブラウザではログイン状態が維持される。
どういった仕組みで実現しているかというと、最初のログイン成功後にブラウザにはremember_web_...という名前のCookieが保存され、Laravelではこれがあれば2回目からのログインを不要にしている。
ルーティングへの名前付け(name)
Route::get('/', 'postController@index')->name('Posts.index');
Routeファサードのメソッドに、nameメソッドを繋げると、そのルーティングに名前を付けルことができる。
特定のルーティングの除外(except)
Route::resource('/posts', 'PostController')->except(['index']);
resourceメソッドに、exceptメソッドを繋げると、指定したルーティングを除外できる。
ミドルウェア
Laravelにはミドルウェアという仕組みがあり、クライアントからのリクエストに対して、リクエストをコントローラーで処理する前あるいは後のタイミングで何らかの処理を行うことができる。
Route::resource('/posts', 'PostController')->except(['index'])->middleware('auth');
authミドルウェアもそうしたミドルウェアのひとつ。
authミドルウェアは、リクエストをコントローラーで処理する前にユーザーがログイン済みであるかどうかをチェックし、ログインしていなければユーザーをログイン画面へリダイレクトする。
既にログイン済みであれば、コントローラーでの処理が行われる。
rulesメソッド
バリデーションのルールを定義する。
連想配列形式で、キーにパラメーターを、値にバリデーションルールを指定する。
なお、バリデーションルールは、
'required|max:50'
のように|区切りでも、
['required', 'max:50']
のように配列でもどちらでも構わない。
attributesメソッド
バリデーションエラーメッセージに表示される項目名をカスタマイズできる。
public function attributes()
{
return [
'title' => 'タイトル',
'body' => '本文',
];
}
DI(Dependency Injection)
Laravelのコントローラーはメソッドの引数で型宣言を行うと、そのクラスのインスタンスが自動で生成されてメソッド内で使えるようになる。
このようにメソッドの内部で他のクラスのインスタンスを生成するのではなく、外で生成されたクラスのインスタンスをメソッドの引数として受け取る流れをDI(Dependency Injection)と言う。
もし、PostクラスのDIを行わなかった場合は、以下のコードになる。
PHP
public function store(PostRequest $request) //-- PostクラスのDIを行わない
{
$post = new Post(); //-- storeアクションメソッド内でArticleクラスのインスタンスを生成している
//-- 以降の処理は同じ
$post->title = $request->title;
$post->body = $request->body;
$post->user_id = $request->user()->id;
$post->save();
return redirect()->route('posts.index');
}
このようにメソッド内で、あるクラスのインスタンスを生成しているような状態を、そのクラス(ここではPostクラス)に依存している、とも表現します。
これに対し、
public function store(PostRequest $request, Post $post)
とするDIは、storeアクションメソッドが依存するPostクラスを外部から注入(injection)している。
DIを使うことで、あるクラスがあるクラスへ依存している度合い、ここではPostControllerがPostクラスへ依存している度合いを下げ、今後の変更がしやすい、テストがしやすい設計となる。
なお、DIは、Laravelに限った話ではなく、他のフレームワーク、プログラミング言語を使っていても登場する概念。
fillableの利用
$post->fill($request->all());
Posrモデルのfillメソッドにこの配列を渡すと、
class Post extends Model
{
protected $fillable = [
'title',
'body',
];
// 略
Postモデルのfillableプロパティ内に指定しておいたプロパティ(ここではtitleとbody)のみが、$postの各プロパティに代入される。
fillableプロパティを使うメリット
1、不正なリクエストへの対策
どういうことかというと、投稿画面ではタイトルと本文のみを入力してPOST送信できるように作ったが、クライアント側でツールなどを使ってそれ以外のパラメーターも含んだ不正なリクエストをPOST送信することは可能。
しかし、fillableプロパティを定義したことで、クライアントからのリクエストのパラメーター値をそのまま取り込んで更新しても良いプロパティは、titleとbodyのみと制限されるようになった。
これによって、不正なリクエストによってpostsテーブルが予期せぬ内容に更新されることを防ぐことができる。
2、コントローラー側にモデルのプロパティへの代入処理をプロパティの数だけ羅列せずに済むようになった点。
Null合体演算子(??)
$post->title ?? old('title')となっているコードの??はNull合体演算子と呼ばれるもの。
null合体演算子は、式1 ?? 式2という形式で記述し、以下の結果となる。
・式1がnullでない場合は、式1が結果となる
・式1がnullである場合は、式2が結果となる
__constructメソッド
PHPのクラスでは、__constructメソッドを定義すると、クラスのインスタンスが生成された時に初期処理として特に呼び出さなくても必ず実行される。
なお、こういったメソッドをオブジェクト指向のプログラミング言語ではコンストラクタと呼ぶ。
authorizeResourceメソッド
PostControllerのようにcreateアクションメソッドやupdatedアクションメソッドを持つリソースコントローラーであれば、コントローラーのコンストラクタでauthorizeResourceメソッドを使用できる。
authorizeResourceメソッドの第一引数には、モデルのクラス名を渡す。
(なお、第一引数に渡したPost::classは'App/Post'という文字列を返すので、第一引数には直接'App/Post'を渡しても構いません)
第二引数には、そのモデルのIDがセットされる、ルーティングのパラメータ名を渡す。
外部キー制約とonDelete('cascade')
user_idとpost_idについては、カラムを作成するだけでなく、外部キー制約をそれぞれ付けている場合。
例えばlikeテーブルを作成する場合(いいね機能)
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
上記は、
likesテーブルのuser_idカラムは、usersテーブルのidカラムを参照することという制約。
こうすることで、likesテーブルのuser_idカラムには、usersテーブルに存在するidと同じ値しか入れられなくなる。
つまり、「いいねは存在するけれど、いいねをしたユーザーが存在しない」という状態を作れないようになる。
さらにonDelete('cascade')を付けることで、いいねをしたユーザーがusersテーブルから削除された場合には、likesテーブルから、そのユーザーに紐づくレコードが削除される
となる。
多対多のリレーション
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Post extends Model
{
// 略
public function user(): BelongsTo
{
return $this->belongsTo('App\User');
}
public function likes(): BelongsToMany
{
return $this->belongsToMany('App\User', 'likes')->withTimestamps();
}
}
「いいね」における記事モデルとユーザーモデルの関係は多対多となる。
そのため、belongsToManyメソッドを使用。
belongsToManyメソッドの第一引数には関係するモデルのモデル名を渡す。
第二引数には中間テーブルのテーブル名を渡す。
もし、第二引数を省略すると、中間テーブル名は2つのモデル名の単数形をアルファベット順に結合した名前であるという前提で処理される。
つまり、post_userという中間テーブルが存在するという前提で処理される。
今回はそうしたテーブルは無く、中間テーブル名はlikesですので、第二引数は省略せずに渡すようにしている。
また、likesテーブルには、created_at、updated_atカラムが存在している。
その場合は、withTimestampsメソッドを付けるようにする。
あるユーザーがいいね済みかどうかを判定するメソッドを作成
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Post extends Model
{
// 略
public function likes(): BelongsToMany
{
return $this->belongsToMany('App\User', 'likes')->withTimestamps();
}
public function isLikedBy(?User $user): bool
{
return $user
? (bool)$this->likes->where('id', $user->id)->count()
: false;
}
}
nullableな型宣言
public function isLikedBy(?User $user): bool
ここでは、引数$userの型が、Userモデルであることを宣言していますが、その手前に?
が付いている。
このように?
を付けると、その引数がnullであることも許容される。
PHP7.1から、このnullableな型宣言が使用可能。
三項演算子の利用
以下コードでは、三項演算子を用いて$userがnullかどうかによって処理を振り分けている。
return $user
? (bool)$this->likes->where('id', $user->id)->count()
: false;
$userがnullでなければ、
(bool)$this->likes->where('id', $user->id)->count()
の結果をメソッドの呼び出し元に返す。
$userがnullであれば、falseを返す。
whereメソッド
$this->likesにより、投稿モデルからlikesテーブル経由で紐付くユーザーモデルが、コレクション(配列を拡張したもの)で返す。
コレクションには、whereというメソッドがある。
whereメソッドの第一引数にキー名、第二引数に値を渡すと、その条件に一致するコレクションが返る。
$this->likes->where('id', $user->id)により、この投稿をいいねしたユーザーの中に、引数として渡された$userがいるかどうかを調べている。
countメソッド
さらに、コレクションにはcountというメソッドがある。
countメソッドは、コレクションの要素数を数えて、数値を返す。
よって、
$this->likes->where('id', $user->id)->count()
の結果は、
この投稿をいいねしたユーザーの中に、引数として渡された$userがいれば、1かそれより大きい数値が返る。
この投稿をいいねしたユーザーの中に、引数として渡された$userがいなければ、0が返る。
型キャスト
(bool)は、型キャストと呼ばれるPHPの機能。
変数の前に記述し、その変数を括弧内に指定した型に変換する。
(bool)と記述することで変数を論理値、つまりtrueかfalseに変換する。
よって、
(bool)$this->likes->where('id', $user->id)->count()
の結果は、
この投稿をいいねしたユーザーの中に、引数として渡された$userがいれば、trueが返る(1以上の数値を論理値へ型キャストするとtrueになるため)
この投稿をいいねしたユーザーの中に、引数として渡された$userがいなければ、falseが返る(0を論理値へ型キャストするとfalseになるため)
eachメソッド
コレクションの各要素に対して順に処理を行うことができる。
また、このeachメソッドには、引数にコールバック(関数)を渡すことができる。
firstOrCreateメソッド
引数として渡した「カラム名と値のペア」を持つレコードがテーブルに存在するかどうかを探し、もし存在すればそのモデルを返す。
テーブルに存在しなければ、そのレコードをテーブルに保存した上で、モデルを返す。
detachメソッド
detachメソッドを引数無しで使うと、そのリレーションを紐付ける中間テーブルのレコードが全削除される。
Eagerロード
loadメソッドに引数としてリレーション名を渡すと、リレーション先のテーブルからもデータを取得する。
docker-compose exec workspace
docker-composeコマンドで起動中のworkspaceという名前のDockerコンテナ(仮想環境)の中で、続くコマンド(php artisan...)を実行してください
といったもの。