20200529のlaravelに関する記事は9件です。

Laravel + SQLiteでシンプルなブログアプリを作っていく

はじめに

以前の投稿、Laravel + SQLite 準備編からの続きで、開発を進めます。

ドットインストール Laravel 5.5入門の内容に沿ってシンプルなブログアプリ開発を行います。

環境

VisualStudioCode

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 migrate

SQLite でテーブルが作られたか確認。

Terminal
$ sqlite3 database/database.sqlite
Terminal
sqlite> .schema posts
Terminal
sqlite> .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()とします。

名前空間の概要 - Manual - PHP

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:  Goodbye

SQLiteでも確認する

Terminal
$ sqlite3 database/database.sqlite
Terminal
sqlite> select * from posts;

先ほどのtitle1が入っているか確認

Terminal
1|title 1|body 1|2020-05-24 07:40:04|2020-05-24 07:40:04

Eloquentモデルを使えばSQLを特に意識しなくてもこのようにデータが扱えます。

Terminal
sqlite> .exit

終了

Mass Assignmentの設定

Eloquentモデルをいじっていきたいのでまずtinkerを使います。

Terminal
$ php artisan tinker

データを格納する際にインスタンスを作ってsaveとしていましたが、

Terminal
App\Post::create(['title'=>'title 2', 'body'=>'body 2']);

これでそれぞれにデータを与えて一気に追加することも可能です。
しかしそのまま実行しても以下のようなエラーになります。

Terminal
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 {}の中に

Terminal
protected $fillable = ['title', 'body'];

このcolumnの、titleとbodyにデータを挿入していい という設定になります。

設定を変えたのでtinkerをexitしてもう一度tinkerで入ります。

Terminal
>>> exit
Terminal
$ 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を使います。

SQLデータ分析入門#4『LIMIT句を理解する』

指定したレコードを上限に、結果を引っ張ってくる

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.php
Route::get('/', function () {
    return view('welcome');
});

URLに/をつけてgetでアクセスした時のroutingを作る場合はまず、
Route::get('/', '')のように書いて、その後に行いたい処理を書いていきます。
PostsController@indexアクションを実行せよとしていきます。

routes>web.php
Route::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.php
extends 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

実行

Terminal
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となります。

Laravel 5.5 Bladeテンプレート

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.php
extends Controller
{
  public function index() {
    // return "hello"; 編集
    return view('posts.index');
  }
}

viewを使うにはreturn viewの後にviewの名前を指定します。
Action名と同じにしたのでposts.indexとします。
フォルダの区切りは .(ドット) になっている点に注意します。

ブラウザをリロードして確認

スクリーンショット 2020-05-25 10.22.44.png
このようにviewが反映されてればOKです。

データの抽出

データを入れるため、まずはデータの取得からしていきます。

データを取得するには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());としています。
スクリーンショット 2020-05-25 10.45.18.png
こうしてブラウザで確認するとtitle1とtitle2のデータが取れています。

あとは記事を新しい順に並べたいのでorderByで並べ替えます。
Post::orderBy('created_at', 'desc')->get();

これでいいのですが、このcreated_atで新しい順に取ってくるという処理はよく行うので、
Laravelではlatest()という書き方も用意されていて
Post::latest()->get();で同じ意味になります。

app>Http>Controllers>PostsController.php
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');
  }
}

ブラウザで確認します。
スクリーンショット 2020-05-25 11.01.12.png
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を表示をします。

ブラウザを確認します。

スクリーンショット 2020-05-25 11.25.45.png

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);
  }
}

スクリーンショット 2020-05-25 11.43.00.png
空のときの表示ができました。
確認できたら$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.css
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;
}

以下のような見た目になればOKです。
スクリーンショット 2020-05-25 13.00.12.png

記事の詳細画面の作成

記事の一覧ができたので、次は記事の詳細画面を作ります。
まずは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.php
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のデータを渡します。

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-e

e関数は、PHPのhtmlspecialchars関数をdouble_encodeオプションにfalseを指定し、実行します。

echo e('<html>foo</html>');

// &lt;html&gt;foo&lt;/html&gt;

e()について
https://laraweb.net/knowledge/835/

HTMLエンティティ―(※ブラウザがHTMLとして処理せずにそのまま出力させるための代替コード)の実行

echo e('<strong>laravel</strong>');
//出力結果 ⇒ &lt;strong&gt;laravel&lt;/strong&gt;

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()という命令を使う方法
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をクリックすると以下のように詳細リンクに飛べるようになりました。
スクリーンショット 2020-05-25 14.49.32.png

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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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の時でも入力チェックが発動するようになった。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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.php
use 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.型を判定する

gettype

Log::info('valueの中身' . gettype($value));

リクエスト情報の型を判定するのに使うことが多いです。

4.クラスを判定する

get_class

Log::info('valueの中身' . get_class($value));

タイプヒンティングでクラスを固めている場合は使うことが多いかも。

まとめ

Logを使うとAPI含めてバックエンドのデバッグができるよ。
調べたい値に応じてPHP組み込み関数と組み合わせて使おう!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

mysqlデータベース作成と権限の付与

laravelプロジェクトでデータベース構築

  • 概要:laravelのプロジェクトでデータベースを作成して、権限を与えてあげるまで

mysqlに接続

terminalから以下で接続できます。
接続されると mysql>となり、待機状態になります。

bash
$mysql -uroot 

laravelのenvファイルに設定を記入する

laravelの環境設定ファイルがenvファイルです。こちらにdbの接続情報を記入します。

.env
DB_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;

参考

MySQL のデータベースユーザーとデータベースアクセス権限

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

データベースに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 と怒られます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.php
namespace 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.php
namespace 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.php
namespace 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内で指定した数値のデータをモデルから持ってくることができるんですね。

え?それの次が知りたい?
それはもっと偉い人に聞いてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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

発生したこと

  1. LighthouseResolverを使用してスキーマを実装し、GitHubからAWSのCode兄弟を使ってECSにデプロイ。
  2. ローカルでは元気に動いていたプログラムがECS上では全く動かない。
  3. デバッグログ仕込みまくって調べてみたらschema.graphqlのファイルの更新が反映されてない。
  4. ECSで動いているimageを確認してもちゃんと新しいschema.graphqlが入っている。
  5. キャッシュか?でもデプロイのたびにコンテナ作り直しているからそれはないよなぁ……
  6. 原因がわからなくて詰む。

原因

やっぱりキャッシュでした。

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に書いてある通り、.envAPP_ENVを参照しlocal以外だったら自動でキャッシュを有効化します。

これによりGraphQLの全てのスキーマが一つのデータとしてキャッシュされます。

、このキャッシュはどこに保存されるかというと、メモリではなく.envCACHE_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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む