- 投稿日:2020-05-29T22:16:01+09:00
Laravel + SQLiteでシンプルなブログアプリを作っていく
はじめに
以前の投稿、Laravel + SQLite 準備編からの続きで、開発を進めます。
ドットインストール Laravel 5.5入門の内容に沿ってシンプルなブログアプリ開発を行います。
環境
Laravel Framework 7.12.0
マイグレーションの設定
まず記事に関するモデルから作ります。
Terminal$ 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バイト。文字データ型として最もよく使われるデータ型です。このマイグレーションファイルの中身を実行します。
Terminal$ php artisan migrateSQLite でテーブルが作られたか確認。
Terminal$ sqlite3 database/database.sqliteTerminalsqlite> .schema postsTerminalsqlite> .quit確認できたらこれでOKです。
Modelのインタラクティブな操作
LaravelはEloquentモデルと呼ばれていてSQLを意識しなくても直感的にデータが操作できるようになっています。
Eloquent:利用の開始 5.7 Laravel - ReaDouble
Eloquent ORMはLaravelに含まれている、美しくシンプルなアクティブレコードによるデーター操作の実装です。それぞれのデータベーステーブルは関連する「モデル」と結びついています。モデルによりテーブル中のデータをクエリできますし、さらに新しいレコードを追加することもできます。
Postモデルを作ったので
php artisan tinker
というコマンドを使いインタラクティブに操作してみます。Terminal$ php artisan tinkerまずはレコードを挿入します。
インスタンスを作りsaveメソッドを使います。インスタンスの作り方はデフォルトで名前空間がAppになっているので
new App\Post()
とします。Terminal>>> $post = new App\Post() => App\Post {#3054}他にも設定していきます。
Terminal>>> $post->title = 'title 1'; $post->body = 'body 1'; => "body 1"Terminal>>> $post->save(); => true >>>このようにtrueと出れば正常に処理が終了したという意味になります。
格納したデータを確認する
APP\Post::all();
こちらのコマンドを使います。Terminal>>> 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();
Terminal>>> 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
Terminal>>> exit Exit: GoodbyeSQLiteでも確認する
Terminal$ sqlite3 database/database.sqliteTerminalsqlite> select * from posts;先ほどのtitle1が入っているか確認
Terminal1|title 1|body 1|2020-05-24 07:40:04|2020-05-24 07:40:04Eloquentモデルを使えばSQLを特に意識しなくてもこのようにデータが扱えます。
Terminalsqlite> .exit終了
Mass Assignmentの設定
Eloquentモデルをいじっていきたいのでまずtinkerを使います。
Terminal$ php artisan tinkerデータを格納する際にインスタンスを作ってsaveとしていましたが、
TerminalApp\Post::create(['title'=>'title 2', 'body'=>'body 2']);これでそれぞれにデータを与えて一気に追加することも可能です。
しかしそのまま実行しても以下のようなエラーになります。TerminalIlluminate/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 {}
の中にTerminalprotected $fillable = ['title', 'body'];このcolumnの、titleとbodyにデータを挿入していい という設定になります。
設定を変えたのでtinkerをexitしてもう一度tinkerで入ります。
Terminal>>> exitTerminal$ php artisan tinkerこれで先ほどと同じコマンドを実行できます。
Terminal>>> 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も挿入できます。Terminal>>> 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のデータを引っ張ってくるには、Terminal>>> 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せよという命令はこのようにします。Terminal>>> 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')
で新しい順に表示します。Terminal>>> 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を使います。
指定したレコードを上限に、結果を引っ張ってくる
Terminal>>> 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に入れます。
Terminal>>> $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
を更新します。Terminal>>> $post->title = 'title 3 updated'; => "title 3 updated"Terminal>>> $post->save(); => trueこれでデータベースに反映したので確認します。
Terminal>>> 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のデータになっています。
Terminal>>> $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", }これを削除します。
Terminal>>> $post->delete(); => true全件表示で確認します。
Terminal>>> 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で設定できます。
デフォルトの以下の部分は今回は使わないので削ります。
routes>web.phpRoute::get('/', function () { return view('welcome'); });URLに/をつけてgetでアクセスした時のroutingを作る場合はまず、
Route::get('/', '')
のように書いて、その後に行いたい処理を書いていきます。
PostsController@indexアクションを実行せよとしていきます。routes>web.phpRoute::get('/', 'PostsController@index');Controllerが必要になるので作っていきます。
artisanコマンドを使うのでphp artisan make:controller
としてPostsController
という名前にします。Terminal$ php artisan make:controller PostsController Controller created successfully.Controllerが作られました。
Controllerはapp>Http>ControllersにPostsController.phpができているので編集していきます。
helloと表示してみます。
表示する内容を返せばいいのでreturn "hello";
のようにします。app>Http>Controllers>PostsController.phpextends Controller { public function index() { return "hello"; } }確認していきたいのでサーバーを立ち上げます。
サーバーを立ち上げるにあたってIPアドレスが必要になるので、
$ ifconfig
で調べられます。
inetの隣に4つ並んでいるものがローカルIPです。ホストを指定する必要があるので、先程のIPアドレスを入れて--host 192.XXX.X.XX -- portを8000番で立ち上げてます。
Terminal$ php artisan serve --host 192.168.X.XXX --port 8000実行
TerminalLaravel 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)を作っていきます。resources>views>posts>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>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を指定します。
app>Http>Controllers>PostsController.phpextends Controller { public function index() { // return "hello"; 編集 return view('posts.index'); } }viewを使うにはreturn viewの後にviewの名前を指定します。
Action名と同じにしたのでposts.index
とします。
フォルダの区切りは .(ドット) になっている点に注意します。ブラウザをリロードして確認
データの抽出
データを入れるため、まずはデータの取得からしていきます。
データを取得するにはEloquentの命令を使います。
$posts = \App\Post::all();
とすると全件を取得することが出来ます。
しかし名前空間が長くなるので、上の方でuse App\Post;
を使います。app>Http>Controllers>PostsController.php<?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();
で同じ意味になります。app>Http>Controllers>PostsController.phpclass 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
を使ってループを展開していきます。resources>views>posts>index.blade.php<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); } }
空のときの表示ができました。
確認できたら$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フォルダの中に作っていきます。
public>css>styles.cssbody { 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');
routes>web.php<?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の中に続きから書いていきます。
app>Http>Controllers>PostsController.phppublic 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
のデータを渡します。app>Http>Controllers>PostsController.php<?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内
resources>views>posts>show.blade.php<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-ee関数は、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.phpnl2br — 改行文字の前に 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()という命令を使う方法
php
<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}'
にします。routes>web.php// Route::get('/posts{id}', 'PostsController@show'); Route::get('/posts{post}', 'PostsController@show');app>Http>Controllers>PostsController.php// 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をパラメーターに渡してくれます。resources>views>posts>index.blade.php<!-- <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
- 投稿日:2020-05-29T19:40:56+09:00
How to Check Laravel Version via CLI and Application File
how to check the laravel version using a command-line interface, and we also have a look at the project files to identify the find out the laravel version.
click here to read more:
https://www.positronx.io/how-to-check-laravel-version-via-cli-application-file/
- 投稿日:2020-05-29T19:35:31+09:00
Install Laravel 7 with Composer on macOS, Ubuntu and Windows
This step by step tutorial explains how to install the latest Laravel 7 version and Composer package manager comfortably on your development system.
click here to read more:
https://www.positronx.io/install-laravel-with-composer-on-macos-ubuntu-and-windows/
- 投稿日:2020-05-29T14:51:12+09:00
Laravel で値がない時にカスタムバリデーションがうまく効かない問題への対策
結構ハマったので、メモ。
前提
- 特定条件のときだけ必須にしたかった。(return が trueの時だけ、第1引数のプロパティに、第2引数のバリデーションルールを追加する)
$validator->sometimes('fuga', ['nullable', new RequiredIfHoge($status)], function ($input) { return $input->piyopiyo == true; });原因
class RequiredIfHoge implements Ruleで値がないときに動かなかった
対応
class RequiredIfHoge implements ImplicitRuleで、nullの時でも入力チェックが発動するようになった。
- 投稿日:2020-05-29T13:00:32+09:00
【vscodeスニペット】LaravelでAPIを実装する時のデバッグ方法
laravelでのデバッグといえばdd()が有名ですね!
が、コントローラがAPIの場合には、dd()を使っても画面上に情報を表示させることができません。ではどうすればいいのかというと、ログ出力を使って情報を出力させます。
APIを使ってない方でも、ログならばデバッグ情報が残るので、dd()との使い分けとして知っておくといいかも!
Logファサードの基本
エイリアス登録
laravelのプロジェクトフォルダ内部「src/config/app.php」を開きます。
aliasesという箇所にLogが登録されているのをチェックしましょう。config/app.php'aliases' => [ 'Log' => Illuminate\Support\Facades\Log::class, ]ファイル内でLogを呼び出す
呼び出す方法は
use
を使う方法と\
(バックスラッシュ)を使う方法の2つです。
どちらにしろ、グローバル空間から呼び出すことに違いはありません。HogeController.phpuse Log;\Log::info();使うならどちらか一方を使う方が、統一感があっていいですね!
ログの出力場所
config/loggin.php
をチェックしましょう。'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => ['daily'], 'ignore_exceptions' => false, ], 'single' => [ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), 'level' => 'debug', ],デフォルトでは↑のようになっていて、
src/storage/logs
の中にlaravel.log
が生成・更新されます。スニペット
これから紹介するメソッドをスニペットにしておきました。
ユーザースニペット(php.json)にコピペするだけで、すぐに使えます。php.json"引数の表示": { "prefix" : "log-args", "body" : [ "Log::info('${1:value}の中身' . $1);", ], "description": "expose to args content also can use typeof array" }, "配列の表示": { "prefix" : "log-array", "body" : [ "Log::info('${1:array}の中身' . print_r(\\$$1, true));", ], "description": "expose to arg content also can use typeof array" }, "型の判定": { "prefix" : "log-type", "body" : [ "Log::info('type of \\$${1:value}' . gettype(\\$$1));", ], "description": "expose to type" }, "クラスの判定": { "prefix" : "log-class", "body" : [ "Log::info('class of \\$${1:value}' . get_class(\\$${1}));", ], "description": "expose to arg class" },Logを使う
スニペット
1.引数を表示する
Log::info('引数の中身' . $value);2.引数(配列)を表示する
1だと、配列などの中身は表示できません。
そんな時はprint_rを使いましょう。Log::info('valueの中身' . print_r($value, true));3.型を判定する
Log::info('valueの中身' . gettype($value));リクエスト情報の型を判定するのに使うことが多いです。
4.クラスを判定する
Log::info('valueの中身' . get_class($value));タイプヒンティングでクラスを固めている場合は使うことが多いかも。
まとめ
Logを使うとAPI含めてバックエンドのデバッグができるよ。
調べたい値に応じてPHP組み込み関数と組み合わせて使おう!
- 投稿日:2020-05-29T12:18:24+09:00
mysqlデータベース作成と権限の付与
laravelプロジェクトでデータベース構築
- 概要:laravelのプロジェクトでデータベースを作成して、権限を与えてあげるまで
mysqlに接続
terminalから以下で接続できます。
接続されるとmysql>
となり、待機状態になります。bash$mysql -urootlaravelのenvファイルに設定を記入する
laravelの環境設定ファイルがenvファイルです。こちらにdbの接続情報を記入します。
.envDB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=dbuser DB_USERNAME=dbuser@localhost DB_PASSWORD=dbpasswordデータベースの作成
mysql>create database dbuser;データベースが作成されたか確認
以下のコマンドを入力すると一覧が確認できる
mysql>show databases;データベースに権限を与える
データベースユーザー作成とアクセス権限付与します。
mysql>GRANT ALL PRIVILEGES ON dbname.* TO dbuser@'localhost' IDENTIFIED BY 'dbpassword';既に、ユーザーを作成している場合などは上記の作成を分けることもできます。
(データベースユーザー作成) mysql> CREATE USER dbuser@localhost IDENTIFIED BY 'dbpassword'; (データベースに対するユーザーのアクセス権限付与) mysql> GRANT ALL PRIVILEGES ON dbname.* TO dbuser@'localhost;参考
- 投稿日:2020-05-29T11:45:53+09:00
データベースにsqlファイルをインポートする
sqlファイルをインポートする
データベースにダウンロードしたsqlのファイルをインポートする必要があったので、備忘録として残します。
インポートコマンド
sqlのことをよく分かっていなかったので、sqlをインポートする時はデータベースに接続して、やると思っていましたが、zshやbashなどのシェルスクルプト上で実行します。
bash[~/user]$mysql -u username -p database_name < file.sql
file.sql
はプロジェクトのディレクトリでなくて、ダウンロードなど、どこに置いても良いが、importする際はパスを指定してあげないといけないです。
ファイルをドラックしてterminalに持ってくると、そのファイルの絶対パスが自動で入るので便利です。ハマったこと
すでに書きましたが、データベースに接続してインポートするものだと思いこんでいたので、何度やってもsyntaxエラーになってしまいました。あと、インポートするファイルのパスも指定しないと、当然、no such file と怒られます。
- 投稿日:2020-05-29T10:24:36+09:00
laravelの認識で間違っていたこと その1~Route-Controller間のパラメーター~
laravelのRoute-Controller間のパラメーター間違い
言い訳
youtubeなんかの動画や初心者用のwebページなどをみてlaravelにおけるroute-controller間のパラメーターの受け渡しを学びました。(学んだつもりでした。)
まさか他の人が沼にハマるまいとは思いますが、救済する1本の蜘蛛の糸になれば幸いです。科学の力ってスゲー
フレームワークを利用し始めた当初簡単にsqliteのようなデータベースと接続し、簡単にSELECTできる(値を引っ張ってこれる)ことに感心していました。
「(データがあるテーブル名がposts
の場合)え?Post::find(1)
だけでid=1の(レコードの)データをもって来れるの!?科学の力ってスゲー。」間違い
実際にハマっていた間違い↓
web.php<?php use Illuminate\Support\Facades\Route; Route::get('/post','PostsController@index'); Route::post('/post/{id}','PostsController@detail'); //ルートの設定postで一覧画面、その後ろに数字をつけることで、そのidの詳細画面に行けるようにするPostController.phpnamespace App\Http\Controllers; use App\Post; use Illuminate\Http\Request; class PostsController extends Controller { public function index() { $posts = Post::all(); //postsテーブルの全データ取得 return view('ichiran',['posts' => $posts]); //表示は「ichiran.blade.php」の方に引数postsを渡して任せる } public function detail(Post $post) { $title = $post->title; $body = $post->body; //例えば/postページから/post/1へと遷移した時は$id=1のデータが勝手に取得される return view('shousai',['title' => $title,'body' => $body]); //表示は「shousai.blade.php」の方に引数title,bodyを渡して任せる //普段は$postをビューの方に渡してそっちで{{ $post->title }}としていますよ、念為。 } }
/postページから/post/1へと遷移した時は$id=1のデータが勝手に取得される
なんて程、人生は甘くはないんですねぇ。
何万回試そうが、$id=1のデータは取れず…最初に見つけた解決策1
PostController.phpnamespace App\Http\Controllers; use App\Post; use Illuminate\Http\Request; class PostsController extends Controller { //略 public function detail(Post $post,$id)//<-new { $post = Post::find($id); //routeの方で{id}と指定したためcontrollerで使いたい時にはfunctionの引数に指定すれば使える //よって/post/1へと遷移したら$id=1のデータをfindすることができる $title = $post->title; $body = $post->body; return view('shousai',['title' => $title,'body' => $body]); } }でもこれなんかスマート(ここで言うスマートとは「引数が多くなってるなぁ、function内の記述もちょっと多いし…なんかもっと削れないんかなぁ」程度のこと)でないなぁ…
試行錯誤し、行きついた解決策2
まずrouteをちょっと変更し、{id}ではなく{post}にします。
そうすると…web.php<?php use Illuminate\Support\Facades\Route; Route::get('/post','PostsController@index'); Route::post('/post/{post}','PostsController@detail');PostController.phpnamespace App\Http\Controllers; use App\Post; use Illuminate\Http\Request; class PostsController extends Controller { //略 public function detail(Post $post)//<-new { $title = $post->title; $body = $post->body; return view('shousai',['title' => $title,'body' => $body]); } }これだけで
$title
には{post}に渡された数字のデータが入るんですね。ビックリです。いつも{id}で指定していたので、「なんでリソースコントローラー作るとeditには勝手に任意のデータが読み込まれるのだろう…リソースコントローラー専用なのか、ずるいな」と思っていました。
からくり
detailファンクションの引数にPostモデルのインスタンスを指定してあげた(いわゆるDI)変数名とrouteで指定した変数名を合わせることで、URL内で指定した数値のデータをモデルから持ってくることができるんですね。
え?それの次が知りたい?
それはもっと偉い人に聞いてください。
- 投稿日:2020-05-29T01:08:18+09:00
【Lighthouse】schema.graphqlがキャッシュされて更新できない時の対処法【Laravel + GraphQL + Redis】
Lighthouseを使っている時にローカルで実装して動作確認した後、AWS環境(+Redis)にデプロイしてECS確認したら
schema.graphql
の更新が全く反映されず、めっちゃハマりました。
この記事ではその解決策と簡単な解説をしていきたいと思います。結論
.env
または環境変数にLIGHTHOUSE_CACHE_ENABLE=false
を設定するか、以下のコマンドを実行する。
ただし本番環境で上記の環境変数を設定するのは非推奨なので注意が必要。php artisan lighthouse:clear-cache環境
Docker 19.03.8
PHP 7.3.16
Laravel 6.18.3
Lighthouse 4.11発生したこと
Lighthouse
でResolverを使用してスキーマを実装し、GitHubからAWSのCode兄弟を使ってECSにデプロイ。- ローカルでは元気に動いていたプログラムがECS上では全く動かない。
- デバッグログ仕込みまくって調べてみたら
schema.graphql
のファイルの更新が反映されてない。- ECSで動いているimageを確認してもちゃんと新しい
schema.graphql
が入っている。- キャッシュか?でもデプロイのたびにコンテナ作り直しているからそれはないよなぁ……
- 原因がわからなくて詰む。
原因
やっぱりキャッシュでした。
config/lighthouse.php
にキャッシュの設定があります。config/lighthouse.php'cache' => [ 'enable' => env('LIGHTHOUSE_CACHE_ENABLE', env('APP_ENV') !== 'local'), 'key' => env('LIGHTHOUSE_CACHE_KEY', 'lighthouse-schema'), 'ttl' => env('LIGHTHOUSE_CACHE_TTL', null), ],
enable
に書いてある通り、.env
のAPP_ENV
を参照しlocal
以外だったら自動でキャッシュを有効化します。これによりGraphQLの全てのスキーマが一つのデータとしてキャッシュされます。
で、このキャッシュはどこに保存されるかというと、メモリではなく
.env
のCACHE_DRIVER
で設定されているストレージに保存されます。
(詳細な実装はこちらに書いてあります。)今回はRedisを使用してたので、そこに保存されることになります。
ローカルならDockerコンテナでRedisを管理しているので、コンテナを落とせば自然とリセットされますがAWSでの開発環境ではRedisを落とすことはほぼありません。
そのため永遠にキャッシュが残ることになってしまったのです。
最後に
注意点として
config/lighthouse.php
に書いてある通り、キャッシュをオフにするとパフォーマンスに影響がでます。
なので本番環境ではキャッシュを切らないように気をつけましょう。/* |-------------------------------------------------------------------------- | Schema Cache |-------------------------------------------------------------------------- | | A large part of schema generation consists of parsing and AST manipulation. | This operation is very expensive, so it is highly recommended to enable | caching of the final schema to optimize performance of large schemas. | */ECSを使用している場合はいちいちコンテナに入るのも大変なので、Dockerfileにコマンドを追加したり直接Redis上のキャッシュを削除するようにしましょう。
参考文献
Schema caching
GraphQL Server Cannot Be Reached Status Code 500
nuwave/lighthouse