#はじめに
以前の投稿、Laravel + SQLite 準備編からの続きで、開発を進めます。
ドットインストール Laravel 5.5入門の内容に沿ってシンプルなブログアプリ開発を行います。
#環境
[VisualStudioCode]
(https://code.visualstudio.com)
Laravel Framework 7.12.0
#マイグレーションの設定
まず記事に関するモデルから作ります。
$ php artisan make:model Post --migration
モデルはartisan
コマンドを使えば良いのでphp artisan
とします。
make:model
としてモデル名は記事のModelなのでPost
とします。
その後にバージョン管理するためのマイグレーションファイルも作るので、
--migration
というオプションを付けます。
こうするとModel
が作られてmigration
ファイルも作られます。
migration
ファイルを開いてデータベース構造の設定をします。
migration
ファイルはdatabase
の中のmigrations
にあります。
migration
ファイルにはup()
とdown()
というメソッドがあります。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePostsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('body');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('posts');
}
}
up()
→このマイグレーションで行いたい処理
down()
→それを巻き戻すための処理
これらがあることでデータベース構成を変更したり、その変更を取り消したりすることが出来るので、結果としてデータベース構成のバージョン管理ができるという仕組みです。
upメソッドは自動的にmodelを複数形にしたテーブル名にしてくれてidとtimestampsを設定してくれています。
idは連番で付けられる主キーです。
timestampsはcreated_at updated_at
というcolumnを作ってくれて作成日時と更新日時を自動で管理してくれる仕組みになっています。
columnを追加したいので、titleとbodyというcolumnにします。
$table->id();
$table->string('title');
$table->text('body');
$table->timestamps();
columnの種類
SQLでいうところのvarcharはstringで設定していくのでstring('title')
bodyはテキストで設定したいのでtext('body')
参考:varchar
可変長の文字列です。可変長とは「長さが決まっていない」という意味で、varchar(10)に2バイトを格納すると、10バイトではなく2バイト使用します。
最大8,000バイト。文字データ型として最もよく使われるデータ型です。
このマイグレーションファイルの中身を実行します。
$ php artisan migrate
SQLite でテーブルが作られたか確認。
$ sqlite3 database/database.sqlite
sqlite> .schema posts
sqlite> .quit
確認できたらこれでOKです。
#Modelのインタラクティブな操作
LaravelはEloquentモデルと呼ばれていてSQLを意識しなくても直感的にデータが操作できるようになっています。
Eloquent:利用の開始 5.7 Laravel - ReaDouble
Eloquent ORMはLaravelに含まれている、美しくシンプルなアクティブレコードによるデーター操作の実装です。それぞれのデータベーステーブルは関連する「モデル」と結びついています。モデルによりテーブル中のデータをクエリできますし、さらに新しいレコードを追加することもできます。
Postモデルを作ったのでphp artisan tinker
というコマンドを使いインタラクティブに操作してみます。
$ php artisan tinker
まずはレコードを挿入します。
インスタンスを作りsaveメソッドを使います。
インスタンスの作り方はデフォルトで名前空間がAppになっているのでnew App\Post()
とします。
>>> $post = new App\Post()
=> App\Post {#3054}
他にも設定していきます。
>>> $post->title = 'title 1'; $post->body = 'body 1';
=> "body 1"
>>> $post->save();
=> true
>>>
このようにtrueと出れば正常に処理が終了したという意味になります。
格納したデータを確認する
APP\Post::all();
こちらのコマンドを使います。
>>> APP\Post::all();
=> Illuminate\Database\Eloquent\Collection {#3781
all: [
App\Post {#3780
id: "1",
title: "title 1",
body: "body 1",
created_at: "2020-05-24 07:40:04",
updated_at: "2020-05-24 07:40:04",
},
],
}
もっとシンプルな表示でみたい場合
App\Post::all()->toArray();
>>> App\Post::all()->toArray();
=> [
[
"id" => 1,
"title" => "title 1",
"body" => "body 1",
"created_at" => "2020-05-24T07:40:04.000000Z",
"updated_at" => "2020-05-24T07:40:04.000000Z",
],
]
tinker終了
exit
>>> exit
Exit: Goodbye
SQLiteでも確認する
$ sqlite3 database/database.sqlite
sqlite> select * from posts;
先ほどのtitle1が入っているか確認
1|title 1|body 1|2020-05-24 07:40:04|2020-05-24 07:40:04
Eloquentモデルを使えばSQLを特に意識しなくてもこのようにデータが扱えます。
sqlite> .exit
終了
#Mass Assignmentの設定
Eloquentモデルをいじっていきたいのでまずtinkerを使います。
$ php artisan tinker
データを格納する際にインスタンスを作ってsaveとしていましたが、
App\Post::create(['title'=>'title 2', 'body'=>'body 2']);
これでそれぞれにデータを与えて一気に追加することも可能です。
しかしそのまま実行しても以下のようなエラーになります。
Illuminate/Database/Eloquent/MassAssignmentException with message 'Add [title] to fillable property to allow mass assignment on [App/Post].'
MassAssignment エラー: 意図しないリクエストによって悪意のあるデータが挿入されてしまう脆弱性
これを実行するにはLaravelでデフォルト設定を変える必要があります。
モデルで設定をします。
Modelはappの中にあるのでapp>Post.phpを編集します。
class Post extends Model {}
の中に
protected $fillable = ['title', 'body'];
このcolumnの、titleとbodyにデータを挿入していい という設定になります。
設定を変えたのでtinkerをexitしてもう一度tinkerで入ります。
>>> exit
$ php artisan tinker
これで先ほどと同じコマンドを実行できます。
>>> App\Post::create(['title'=>'title 2', 'body'=>'body 2']);
=> App\Post {#3935
title: "title 2",
body: "body 2",
updated_at: "2020-05-24 08:37:32",
created_at: "2020-05-24 08:37:32",
id: 2,
}
さらにApp\Post::create(['title'=>'title 3', 'body'=>'body 3']);
と追加すれば3も挿入できます。
>>> App\Post::all()->toArray();
=> [
[
"id" => 1,
"title" => "title 1",
"body" => "body 1",
"created_at" => "2020-05-24T07:40:04.000000Z",
"updated_at" => "2020-05-24T07:40:04.000000Z",
],
[
"id" => 2,
"title" => "title 2",
"body" => "body 2",
"created_at" => "2020-05-24T08:37:32.000000Z",
"updated_at" => "2020-05-24T08:37:32.000000Z",
],
[
"id" => 3,
"title" => "title 3",
"body" => "body 3",
"created_at" => "2020-05-24T08:39:36.000000Z",
"updated_at" => "2020-05-24T08:39:36.000000Z",
],
]
#データの抽出
特定のidのデータを引っ張ってくる場合はfindにidを渡します。
idが3のデータを引っ張ってくるには、
>>> App\Post::find(3)->toArray();
=> [
"id" => 3,
"title" => "title 3",
"body" => "body 3",
"created_at" => "2020-05-24T08:39:36.000000Z",
"updated_at" => "2020-05-24T08:39:36.000000Z",
]
条件付きで抽出するにはwhereとgetを使います。
例えばidが1より大きいものをgetせよという命令はこのようにします。
>>> App\Post::where('id', '>', 1)->get()->toArray();
=> [
[
"id" => 2,
"title" => "title 2",
"body" => "body 2",
"created_at" => "2020-05-24T08:37:32.000000Z",
"updated_at" => "2020-05-24T08:37:32.000000Z",
],
[
"id" => 3,
"title" => "title 3",
"body" => "body 3",
"created_at" => "2020-05-24T08:39:36.000000Z",
"updated_at" => "2020-05-24T08:39:36.000000Z",
],
]
このように2と3が抽出されています。
#データの並び替え
orderBy('created_at', 'desc')
で新しい順に表示します。
>>> App\Post::where('id', '>', 1)->orderBy('created_at', 'desc')->get()->toArray();
=> [
[
"id" => 3,
"title" => "title 3",
"body" => "body 3",
"created_at" => "2020-05-24T08:39:36.000000Z",
"updated_at" => "2020-05-24T08:39:36.000000Z",
],
[
"id" => 2,
"title" => "title 2",
"body" => "body 2",
"created_at" => "2020-05-24T08:37:32.000000Z",
"updated_at" => "2020-05-24T08:37:32.000000Z",
],
]
SQLでいうところのlimitで、変数を制限するにはtakeを使います。
指定したレコードを上限に、結果を引っ張ってくる
>>> App\Post::where('id', '>', 1)->take(1)->get()->toArray();
=> [
[
"id" => 2,
"title" => "title 2",
"body" => "body 2",
"created_at" => "2020-05-24T08:37:32.000000Z",
"updated_at" => "2020-05-24T08:37:32.000000Z",
],
]
#データの更新
一旦Postの3を$postに入れます。
>>> $post = App\Post::find(3);
=> App\Post {#4008
id: "3",
title: "title 3",
body: "body 3",
created_at: "2020-05-24 08:39:36",
updated_at: "2020-05-24 08:39:36",
}
$post->title
を更新します。
>>> $post->title = 'title 3 updated';
=> "title 3 updated"
>>> $post->save();
=> true
これでデータベースに反映したので確認します。
>>> App\Post::all()->toArray();
=> [
[
"id" => 1,
"title" => "title 1",
"body" => "body 1",
"created_at" => "2020-05-24T07:40:04.000000Z",
"updated_at" => "2020-05-24T07:40:04.000000Z",
],
[
"id" => 2,
"title" => "title 2",
"body" => "body 2",
"created_at" => "2020-05-24T08:37:32.000000Z",
"updated_at" => "2020-05-24T08:37:32.000000Z",
],
[
"id" => 3,
"title" => "title 3 updated",
"body" => "body 3",
"created_at" => "2020-05-24T08:39:36.000000Z",
"updated_at" => "2020-05-24T09:20:06.000000Z",
],
]
title 3 updatedに変わっているのでOKです。
#データの削除
deleteを使います。
以下のように今$postがidが3のデータになっています。
>>> $post
=> App\Post {#4008
id: "3",
title: "title 3 updated",
body: "body 3",
created_at: "2020-05-24 08:39:36",
updated_at: "2020-05-24 09:20:06",
}
これを削除します。
>>> $post->delete();
=> true
全件表示で確認します。
>>> App\Post::all()->toArray();
=> [
[
"id" => 1,
"title" => "title 1",
"body" => "body 1",
"created_at" => "2020-05-24T07:40:04.000000Z",
"updated_at" => "2020-05-24T07:40:04.000000Z",
],
[
"id" => 2,
"title" => "title 2",
"body" => "body 2",
"created_at" => "2020-05-24T08:37:32.000000Z",
"updated_at" => "2020-05-24T08:37:32.000000Z",
],
]
idが1と2だけになっています。
#Controller
Postモデルにいくつかデータを格納できたので、そのデータをブラウザで表示します。
それにはどのURLでどのような処理をするかの設定をする必要があります。
その設定をルーティングと呼び、routesフォルダの中のweb.phpで設定できます。
デフォルトの以下の部分は今回は使わないので削ります。
Route::get('/', function () {
return view('welcome');
});
URLに/をつけてgetでアクセスした時のroutingを作る場合はまず、
Route::get('/', '')
のように書いて、その後に行いたい処理を書いていきます。
PostsController@indexアクションを実行せよとしていきます。
Route::get('/', 'PostsController@index');
Controllerが必要になるので作っていきます。
artisanコマンドを使うのでphp artisan make:controller
としてPostsController
という名前にします。
$ php artisan make:controller PostsController
Controller created successfully.
Controllerが作られました。
Controllerはapp>Http>ControllersにPostsController.phpができているので編集していきます。
helloと表示してみます。
表示する内容を返せばいいのでreturn "hello";
のようにします。
extends Controller
{
public function index() {
return "hello";
}
}
確認していきたいのでサーバーを立ち上げます。
サーバーを立ち上げるにあたってIPアドレスが必要になるので、
$ ifconfig
で調べられます。
inetの隣に4つ並んでいるものがローカルIPです。
ホストを指定する必要があるので、先程のIPアドレスを入れて--host 192.XXX.X.XX -- portを8000番で立ち上げてます。
$ php artisan serve --host 192.168.X.XXX --port 8000
実行
Laravel development server started: http://192.168.X.XXX:8000
アクセス先のURLが出てきますので確認します。
helloと出てくればOKです。
以上がroutingを設定してその処理をController辺りに作成するという流れでした。
#View
viewを用意してそこにデータを入れていきます。
resourcesの中のViewsの中に作ります。
フォルダ名:Postに関するviewをまとめるのでpostsフォルダを用意します。
その中にAction名に対応したviewの名前を付けます。
ファイル名:indexアクションなのでindex。LaravelではBladeというテンプレートエンジンが使えるので、index.blade.phpとなります。
BladeはシンプルながらパワフルなLaravelのテンプレートエンジンです。他の人気のあるPHPテンプレートエンジンとは異なり、ビューの中にPHPを直接記述することを許しています。全BladeビューはPHPへコンパイルされ、変更があるまでキャッシュされます。つまりアプリケーションのオーバーヘッドは基本的に0です。Bladeビューには.blade.phpファイル拡張子を付け、通常はresources/viewsディレクトリの中に設置します。
内容の編集:emmetの機能でhtml5のテンプレートを貼り付けたら、titleと文字コードを編集。
bodyの方では、中身を後で中央揃えにします。
<div class="container">
で囲っておき、見出し(h1)と記事の一覧(li)を作っていきます。
<!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>Blog Posts</title>
</head>
<body>
<div class="container">
<h1>Blog Posts</h1>
<ul>
<li><a href="">title</a></li>
<li><a href="">title</a></li>
<li><a href="">title</a></li>
</ul>
</div>
</body>
</html>
これでviewができたのでControllerの方でこのviewを指定します。
extends Controller
{
public function index() {
// return "hello"; 編集
return view('posts.index');
}
}
viewを使うにはreturn viewの後にviewの名前を指定します。
Action名と同じにしたのでposts.index
とします。
フォルダの区切りは .(ドット) になっている点に注意します。
ブラウザをリロードして確認
このようにviewが反映されてればOKです。#データの抽出
データを入れるため、まずはデータの取得からしていきます。
データを取得するにはEloquentの命令を使います。
$posts = \App\Post::all();
とすると全件を取得することが出来ます。
しかし名前空間が長くなるので、上の方でuse App\Post;
を使います。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Post;
class PostsController extends Controller
{
public function index() {
$posts = Post::all();
dd($posts->toArray()); // dump die
return view('posts.index');
}
}
データが取得できたかどうかは、Laravelで用意されているdd()という命令を使って確かめます。
ddはdump dieの略で結果を出力して処理をその場で終了する命令です。
配列で見やすく表示させたいのでdd($posts->toArray());
としています。
こうしてブラウザで確認するとtitle1とtitle2のデータが取れています。
あとは記事を新しい順に並べたいのでorderByで並べ替えます。
Post::orderBy('created_at', 'desc')->get();
これでいいのですが、このcreated_atで新しい順に取ってくるという処理はよく行うので、
Laravelではlatest()という書き方も用意されていて
Post::latest()->get();
で同じ意味になります。
class PostsController extends Controller
{
public function index() {
// $posts = Post::orderBy('created_at', 'dest')->get;
$posts = Post::latest()->get();
dd($posts->toArray()); // dump die
return view('posts.index');
}
}
ブラウザで確認します。
title2のほうが上に来ているのがわかります。
これでデータの抽出ができました。
#データをViewに埋め込む
$posts
でデータの取得ができたのでこれをviewの方に渡していきます。
return view()
の第2引数に渡します。
'posts' => $posts
とすると$posts
の内容をviewの中でposts
という名前で使うことが出来ます。
もしくはwith('post', '$posts');
でも全く同じ意味になります。
class PostsController extends Controller
{
public function index() {
$posts = Post::latest()->get();
// return view('posts.index', ['posts' => $posts]);
return view('posts.index')->with('posts', $posts);
}
}
viewの方でこのpostsを使っていきます。
Bladeでは@foreach
という制御構造を使えるので@foreach(\$posts as \$post)
として、この$post
を使ってループを展開していきます。
<div class="container">
<h1>Blog Posts</h1>
<ul>
@foreach ($posts as $post)
<li><a href="">{{ $post->title }}</a></li>
@endforeach
</ul>
</div>
これで$posts
が無くなるまで$post
でひとつひとつの記事を表すことが出来るので値を埋め込みます。
値の埋め込みには二重波括弧を使うとエスケープもします。
{{ $post->title }}
で$postのtitleを表示をします。
ブラウザを確認します。
title2, title1 となっています。
もしこちらの$posts
の中身が空だったら、と書きたい場合
@forelse
という構文を使います。
そうすると、@empty
という命令が使えるので@empty
の後に、このデータが空だった時のテンプレートを入れ込むことが出来ます。
No posts yetと表示させることにします。
<div class="container">
<h1>Blog Posts</h1>
<ul>
@forelse ($posts as $post)
<li><a href="">{{ $post->title }}</a></li>
@empty
<li>No posts yet</li>
@endforelse
</ul>
</div>
一旦$posts
を空にします。
class PostsController extends Controller
{
public function index() {
// $posts = Post::latest()->get();
$posts = [];
// return view('posts.index', ['posts' => $posts]);
return view('posts.index')->with('posts', $posts);
}
}
BladeではHTMLに出力したくないコメントは{{-- --}}
で囲みます。
<div class="container">
<h1>Blog Posts</h1>
<ul>
{{--
@foreach ($posts as $post)
<li><a href="">{{ $post->title }}</a></li>
@endforeach
--}}
@forelse ($posts as $post)
<li><a href="">{{ $post->title }}</a></li>
@empty
<li>No posts yet</li>
@endforelse
</ul>
</div>
スタイルを整える
CSSを読み込むリンクタグを入れます。
<link rel="stylesheet" href="/css/styles.css">
CSSやJavaScriptや画像はpublicフォルダの中に作っていきます。
body {
font-family: Verdana, sans-serif;
font-size: 14px;
}
.container {
width: 400px;
margin: 20px auto;
}
h1 {
font-size: 16px;
padding-bottom: 10px;
margin-bottom: 15px;
border-bottom: 1px solid #ddd;
}
ul >li {
margin-bottom: 5px;
}
#記事の詳細画面の作成
記事の一覧ができたので、次は記事の詳細画面を作ります。
まずはroutingです。
URLとしてはgetでアクセスされたpostsの1や2を処理したいのですが、
ここにきた値をControllerに渡したい場合は波括弧を付けて変数名を書きます。
Controllerはshowアクションに渡します。
Route::get('/posts{id}', 'PostsController@show');
<?php
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| 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('/', 'PostsController@index');
Route::get('/posts{id}', 'PostsController@show');
次にshowアクションを作ります。
Controllerの中に続きから書いていきます。
public function index($id) {
// $post = Post::find($id);
$post = Post::findOrFail($id);
return view('posts.show')->with('post', $post);
}
web.php
のURLのパラメーターから渡ってきた値は引数で受け取ることが出来るので$id
とします。
データを引っ張ってくるのでPost::find($id)
でいいのですが$id
でデータが見つからなかった場合に、例外を返したいのでその場合はfindOrFail()
という命令を使います。
そしてデータをviewに渡してあげれば良いので今回はposts.show
というテンプレートに対してpostという名前で$post
のデータを渡します。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Post;
class PostsController extends Controller
{
public function index() {
$posts = Post::latest()->get();
return view('posts.index')->with('posts', $posts);
}
public function show($id) {
$post = Post::findOrFail($id);
return view('posts.show')->with('post', $post);
}
}
viewを作りたいのでresourcesフォルダに行きます。
index.blade.php
を複製してshow.blade.php
を作り編集していきます。
head内タイトル変更
<title>{{ $post->title }}</title>
body内
<body>
<div class="container">
<h1>{{ $post->title }}</h1>
<p>{!! nl2br(e($post->body)) !!}</p>
</div>
</body>
この部分について
<p>{!! nl2br(e($post->body)) !!}</p>
本文の方は改行が入ってくる可能性もあるので、改行をbrタグに変換したいです。
まずは二重波括弧ではなく、中身をエスケープしないで値を出力するための{{!! !!}}
という命令を使います。
$post->body
を入れたいので、一旦Laravelのe()
ヘルパーでエスケープして、それをnl2br()
で挟み改行をbrタグにします。
e()
https://readouble.com/laravel/5.5/ja/helpers.html#method-e
e関数は、PHPのhtmlspecialchars関数をdouble_encodeオプションにfalseを指定し、実行します。
echo e('<html>foo</html>');
// <html>foo</html>
e()
について
https://laraweb.net/knowledge/835/
HTMLエンティティ―(※ブラウザがHTMLとして処理せずにそのまま出力させるための代替コード)の実行
echo e('<strong>laravel</strong>');
//出力結果 ⇒ <strong>laravel</strong>
nl2br()
https://www.php.net/manual/ja/function.nl2br.php
nl2br — 改行文字の前に HTML の改行タグを挿入する
説明
nl2br ( string $string [, bool $is_xhtml = TRUE ] ) : string
string に含まれるすべての改行文字 (\r\n、 \n\r、\n および \r) の前に
あるいは
を挿入して返します。
#Implicit Binding
記事の一覧から記事の詳細画面にリンクを張ります。
routingの設計通り/posts/{{ $post->id }}
でもいいですが、
@forelse ($posts as $post)
li><a href="/posts/{{ $post->id }}">{{ $post->title }}</a></li>
URLを生成するための命令が他にもいくつかあります。
url()という命令を使う方法
<li><a href="{{ url('/$posts', $post->id }}">{{ $post->title }}</a></li>
これでも同じ意味になります。
もしくはControllerとActionからURLを生成することができるaction()
という命令もあります。
その場合はPostsControllerのshowに対応するURLを生成する、という書き方ができます。
routingのパラメーターに渡す値は、第2引数以降に入れていけばいいので以下のようにします。
<li><a href="{{ action('PostsController@show', $post->id }}">{{ $post->title }}</a></li>
これで各titleをクリックすると以下のように詳細リンクに飛べるようになりました。
URLから$id
を受け取って、Controllerでその$id
を元にモデルを引っ張ってくるという流れはよく行うので、暗黙的にモデルをデータに結びつけられるImplicit Bindingという仕組みも用意されています。
まずroutingを'/posts/{post}'
にします。
// Route::get('/posts{id}', 'PostsController@show');
Route::get('/posts{post}', 'PostsController@show');
// public function show(Post $id) {
public function show(Post $post) {
これで自動的にこの$post
にはURLから受け取ったidに対応するデータが格納されます。
なのでこの場合はidで引っ張ってくる必要がないのでこれでOKです。
public function show(Post $post) {
// $post = Post::findOrFail($id); // 削除
return view('posts.show')->with('post', $post);
}
さらにリンクの生成についても$post->id
を$post
にするだけで、@post
のidをパラメーターに渡してくれます。
<!-- <li><a href="{{ action('PostsController@show', $post->id) }}">{{ $post->title }}</a></li> -->
<li><a href="{{ action('PostsController@show', $post) }}">{{ $post->title }}</a></li>
同じようにtitleから詳細リンクへの動作を確認することができます。
以上がImplicit Bindingです。
長くなるので続きは分けます。
後半は共通部分の部品化、編集やコメント機能などから進めていきます。
#Links
ドットインストール Laravel 5.5入門
https://dotinstall.com/lessons/basic_laravel_v2