20200923のlaravelに関する記事は10件です。

引数の渡し方

今日はlaravelにおいてviewファイルへの引数の渡し方について記録していくよ~

TestController.php
  public function test(){
   $data1 = 1;
  $data2 = 2;

   return view('test_view' , ['data1' => $data1 , 'data2' => $data2 ]);
}//1 2

compact関数を使って同じ出力になる書き方:

TestController.php
  public function test(){
   $data1 = 1;
  $data2 = 2;

   return view('test_view' , compact('data1' , 'data2'));
}//1 2

以上だよお~

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

laravel、S3及びstripe導入時のherokuデプロイについて(備忘録)

投稿経緯

S3とclearDBを使用したherokuデプロイを行いましたが、つまづくことも非常に多かったので備忘録として投稿します。

開発環境

スクリーンショット 2020-09-22 23.29.41.png
laravel5.8
stripe:laravelcashier10

大筋の流れ

①herokuの導入
②clearDBの導入(stripeを実装の場合)
③S3の導入(画像投稿機能を実装の場合)
今回は、②③をメインに説明していきます。

①heroku導入

https://yu-nocode.com/wp/entry/laravel-quest-6/#Heroku
こちらの記事が本当に分かりやすいのでおすすめです。
今回の説明の主目的ではないので省略します。

②clearDBの導入(stripeを実装の場合)

stripeを導入している場合、DB作成時

heroku run php artisan migrate --seed

を行うと以下のエラー。

SQLSTATE[42704]: Undefined object: 7 ERROR: collation "utf8mb4_bin" for encoding "UTF8" does not exist at character 57 (SQL: alter table "users" add column "stripe_id" varchar(191) collate "utf8mb4_bin" null, add column "card_brand" varchar(191) null, add column "card_last_four" varchar(4) null, add column "trial_ends_at" timestamp(0) without time zone null)

一言で言うとstripe導入時に、usersテーブルに追加されたカラム「stripe_id,card_brand,card_last_four,trial_ends_at」が不具合を起こしています。

https://github.com/laravel/cashier-stripe/issues/747
上記の記事(英語)を見ていると、
今回の開発で
local環境→DB:mysql
heroku環境→DB:pqsql(基本mysqlは使えないため)
に移行したことが原因の様でした。

そこでheroku環境でもDB:mysqlが使えるclearDBを導入しました。

ClearDBとは

Herokuでは、ClearDBというクラウドサービスのMySQLを利用することができます。"ignite" と呼ばれるデータ容量5MBの最小構成であれば無料で使えるので、こちらを利用していきます。

ClearDB導入の流れ

1.herokuにてクレジットカードの設定。

※ 超過しない限り請求はされません。

2.①で作成した、herokuのDATABASEURLを競合しない様削除。

heroku addons:destroy heroku-postgresql -a <アプリ名>

3.ClearDBのアドオンを追加、無料枠のigniteの使用を指示。

heroku addons:create cleardb:ignite
表示結果
mysql://[ユーザ名]:[パスワード]@[ホスト]/[DB名]?reconnect=true

4.表示結果の環境変数にデータベース設定を追加

heroku config:set \
DB_CONNECTION=mysql \
DB_HOST=[ホスト] \
DB_DATABASE=[DB名] \
DB_USERNAME=[ユーザ名] \
DB_PASSWORD=[パスワード]

5.データベースの設定(マイグレーション&シーディング)

heroku run php artisan migrate --seed

6.herokuにpush

git push heroku [送りたいbranch名]:master

これでwebアプリを確認して表示されていればOK。

③S3の導入

webアプリを見ると以下の様に写真が表示されていない。

スクリーンショット 2020-09-22 20.57.45.png

local環境ではstorage>app>public下に画像を保存していたが、
ネット環境では、awsのS3を用いて画像の保存、表示をすることとした。

導入の流れ

1.AWSのIAMユーザーから以下のコードを入手。

Access key ID
シークレットアクセスキーをメモ
※後で使用。

2.S3のバケットを作成。

S3へ移動し、以下の画像のバケットを作成するをクリックし、作成する。
スクリーンショット 2020-09-22 22.44.09.png

3.以下をインストール。

LaravelのファイルストレージとS3を連携させる
composer require aws/aws-sdk-php
composer require league/flysystem-aws-s3-v3

4.Herokuの環境変数をそれぞれ設定。

heroku config:set AWS_ACCESS_KEY_ID='AWSで作成したAccess key ID'
heroku config:set AWS_SECRET_ACCESS_KEY='AWSで作成したシークレットアクセスキー'
heroku config:set AWS_DEFAULT_REGION='作成したバケットのリージョン名'
heroku config:set AWS_BUCKET_BUCKET='作成したバケット名'

※東京ならap-northeast-1

5.controllerやVIEWの設定変更。

僕の場合は、
controllerの以下の文でs3のpublic>tempに画像を投稿できる設定を記述。
$temp_path = Storage::disk('s3')->put('public/temp',$path, 'public');
そして、VIEWファイルの以下の文でs3の$temp_pathの場所の画像を表示と記述した。
<img src = "{{ Storage::disk('s3')->url($temp_path) }}" width="450px">

6.S3のアクセス権限を以下に設定。

スクリーンショット 2020-09-22 23.01.33.png
これで以下の様にパブリックアクセスのオブジェクトの読み取りだけがはいになり、全ての人(everyone)に読み取り権限を与えることができた。
スクリーンショット 2020-09-22 23.04.14.png

完成スクリーンショット 2020-09-22 23.05.59.png

投稿画像も表示される様になった。

終わりに

今回は、せっかく整理するならと初めてqiita投稿をしてみました。
この書き方は、ダメだよ等ありましたらコメントいただけると幸いです。

最後までみていただいた方、ありがとうございました。

参考記事

Heroku コマンド集
https://qiita.com/yu_eguchi/items/8cd53942b88b7ff2fecb

HerokuでMySQLを利用する方法
https://blog.nakamu.life/posts/heroku-mysql

HerokuでMySQLを使うときはDATABASE_URLを書き換える
https://qiita.com/SomeyaNaoki/items/1167fa816fa9647e4464

「Herokuにデプロイしたページの画像が表示されない!?」を解決する AWS S3 超カンタン利用法
https://qiita.com/Hiroyuki-Hiroyuki/items/72aed73ff7c0f8508700#%E7%94%BB%E5%83%8F%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89%E6%A9%9F%E8%83%BD%E3%82%92%E5%AE%9F%E8%A3%85

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

[laravel]->with関数メモ

以下のようにするとviewに変数を送れる

redirect('パス')->with('value', $value)

viewでは{{ $value }} として受け取れる。

※他ページへ飛ばないかぎり、実はsession('value')にも格納されている

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

LaravelのSeeder実行時に「Class 'Database\Seeders\DB' not found」エラーが出たときの対処法

こんにちは、Kokiです。LaravelでSeeder実行時に少々詰まったので共有します。

環境

Laravel Framework 8.5.0

現象

Seederファイルを用意した状態でSeederを実行するとエラーが発生する

$ php artisan db:seed
Seeding: Database\Seeders\HogeTableSeeder

   Error 

  Class 'Database\Seeders\DB' not found

  at database/seeders/ArtistsTableSeeder.php:16
     12▕      * @return void
     13▕      */
     14▕     public function run()
     15▕     {
  ➜  16▕         DB::table('hoge')->insert([
     17▕             [
     18▕                 'user_id'       => '1',
     19▕                 'hoge_name'     => 'hoge',
     20▕             ],

      +8 vendor frames 
  9   database/seeders/DatabaseSeeder.php:18
      Illuminate\Database\Seeder::call("Database\Seeders\HogeTableSeeder")

      +24 vendor frames 
  34  artisan:37
      Illuminate\Foundation\Console\Kernel::handle(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))

原因

コード内でDBファサードを使用しているのにuse宣言していないため

詳細

php aritsan make:seeder HogeTableSeederを実行すると以下のSeederファイルが生成されます。

HogeTableSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class HogeTabelSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {

    }
}

DBファサードを使用するときはuse Illuminate\Support\Facades\DB;を追記する必要があります。

HogeTableSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB; // ←これを追加

class HogeTabelSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        // DBファサードを使用してデータ追加
        DB::table('hoge')->insert([
            [
                'user_id'       => '1',
                'hoge_name'     => 'hoge',
            ])
    }
}

再度実行すると・・・

以下のように成功のメッセージが表示されました。

$ php artisan make:seeder HogeTabelSeeder
Seeder created successfully.

参考

Laravel 5.5 データベース:シーディング

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

【Laravel】GuzzleでEUC-JPのページを受信すると文字化け→cURLの受信結果を適切にエンコードして解決

現象

  1. LaravelでHTTP通信するならGuzzleらしい
  2. 試す。受信結果が文字化けする
  3. cURLベースで書き直す
  4. やっぱり文字化けする
  5. EUC-JPのページで発生している

解決

以下のエントリを参考に、cURLでの受信結果を適切に変換することで文字化けを防ぐことができた。

コード

function curl_get_contents($url, $timeout = 60) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, false);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
    curl_setopt($ch, CURLOPT_ENCODING, 'gzip');
    $result = $this->curl_exec_utf8($ch);
    curl_close($ch);
    return $result;
  }

  function curl_exec_utf8($ch) {
    $data = curl_exec($ch);
    if (!is_string($data))
        return $data;

    unset($charset);
    $content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);

    /* 1: HTTP Content-Type: header */
    preg_match('@([\w/+]+)(;\s*charset=(\S+))?@i', $content_type, $matches);
    if (isset($matches[3]))
        $charset = $matches[3];

     /* 2: <meta> element in the page */
     if (!isset($charset)) {
      preg_match( '@<meta\s+http-equiv="Content-Type"\s+content="([\w/]+)(;\s*charset=([^\s"]+))?@i', $data, $matches );
      if ( isset( $matches[3] ) ) {
          $charset = $matches[3];
          /* In case we want do do further processing downstream: */
          $data = preg_replace('@(<meta\s+http-equiv="Content-Type"\s+content="[\w/]+\s*;\s*charset=)([^\s"]+)@i', '$1utf-8', $data, 1);
      }
    }

    /* 3: <xml> element in the page */
    if (!isset($charset)) {
        preg_match( '@<\?xml.+encoding="([^\s"]+)@si', $data, $matches );
        if ( isset( $matches[1] ) ) {
            $charset = $matches[1];
            /* In case we want do do further processing downstream: */
            $data = preg_replace('@(<\?xml.+encoding=")([^\s"]+)@si', '$1utf-8', $data, 1);
        }
    }

    /* 4: PHP's heuristic detection */
    if (!isset($charset)) {
        $encoding = mb_detect_encoding($data);
        if ($encoding)
            $charset = $encoding;
    }

    /* 5: Default for HTML */
    if (!isset($charset)) {
        if (strstr($content_type, "text/html") === 0)
            $charset = "ISO 8859-1";
    }

    /* Convert it if it is anything but UTF-8 */
    /* You can change "UTF-8"  to "UTF-8//IGNORE" to
      ignore conversion errors and still output something reasonable */
    if (isset($charset) && strtoupper($charset) != "UTF-8")
        $data = iconv($charset, 'UTF-8', $data);

    return $data;
  }

gzipオプションについて

curl_setopt($ch, CURLOPT_ENCODING, 'gzip');はサーバー依存です。

筆者の環境では、gzipで圧縮されたレスポンスが返ってくるため必要でした。
(参考:PHPのcurl()でレスポンスが文字化けしたら確認すること2つ - PHP | ゆるりの足あと

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

Laravel バリデーションを設定する ~フォームリクエスト編~

目的

  • リクエストにバリデーションを記載する方法をまとめる

実施環境

  • ハードウェア環境
項目 情報
OS macOS Catalina(10.15.3)
ハードウェア MacBook Pro (16-inch ,2019)
プロセッサ 2.6 GHz 6コアIntel Core i7
メモリ 16 GB 2667 MHz DDR4
グラフィックス AMD Radeon Pro 5300M 4 GB Intel UHD Graphics 630 1536 MB
  • ソフトウェア環境
項目 情報 備考
PHP バージョン 7.4.3 Homwbrewを用いて導入
Laravel バージョン 7.0.8 commposerを用いて導入
MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いて導入

前提情報

  • ターミナルコマンドは基本的に$ laravel newコマンドで作成されたアプリ名ディレクトリで実行するものとする。
  • ブラウザ上でコンテンツを新規作成する際にコンテンツタイトルとコンテンツ詳細に入力がない場合にバリデーションで弾く処理を追加する。

概要

  1. フォームリクエストクラスの作成
  2. コントローラの修正
  3. ビューファイルの修正
  4. 確認

詳細

  1. フォームリクエストクラスの作成

    1. 下記コマンドを実行してフォームリクエストクラファイルの作成を行う。

      $ php artisan make:request StoreContentPost
      
    2. 下記コマンドを実行して先に作成したフォームリクエストクラスファイルを開く。

      $ vi app/Http/Requests/StoreContentPost.php
      
    3. 開いたフォームリクエストクラスファイルを下記の様に修正する。

      アプリ名ディレクトリ/app/Http/Requests/StoreContentPost.php
      <?php
      
      namespace App\Http\Requests;
      
      use Illuminate\Foundation\Http\FormRequest;
      
      class StoreContentPost extends FormRequest
      {
          /**
           * Determine if the user is authorized to make this request.
           *
           * @return bool
           */
          public function authorize()
          {
              return true;
          }
      
          /**
           * Get the validation rules that apply to the request.
           *
           * @return array
           */
          public function rules()
          {
              return [
                  //バリデーションルールを記載する
                  //'チェックするデータ名' => 'ルール1|ルール2'の様にルールを定義する
                  'title' => 'required',
                  'detail' => 'required',
              ];
          }
      }
      
  2. コントローラファイルの修正

    1. 下記コマンドを実行してコントローラファイルを作成する。

      $ vi app/Http/Controllers/ContentController.php
      
    2. 開いたコントローラファイルを下記の様に修正する。

      アプリ名ディレクトリ/app/Http/Controllers/ContentController.php
      <?php
      
      namespace App\Http\Controllers;
      
      //use Illuminate\Http\Request;
      //下記を追記する
      use App\Http\Requests\StoreContentPost;
      
      use App\Content;
      
      class ContentController extends Controller
      {
          public function index()
          {
              $contents = Content::all();
              return view('contents.index', ['contents' => $contents]);
          }
      
          public function create()
          {
              return view('contents.create');
          }
      
          //下記を修正する
          public function save(StoreContentPost $request)
          {
              $new_content = new Content();
              $new_content->title = $request['title'];
              $new_content->detail = $request['detail'];
              $new_content->save();
      
              return redirect(route('content.index'));
          }
      
          public function detail($content_id)
          {
              $content = Content::find($content_id);
              return view('contents.detail', ['content' => $content]);
          }
      
          public function edit($content_id)
          {
      
              $content = Content::find($content_id);
              return view('contents.edit', ['content' => $content]);
          }
      
          //下記を修正する
          public function update($content_id, StoreContentPost $request)
          {
              $content = Content::find($content_id);
              $content->title = $request['title'];
              $content->detail = $request['detail'];
              $content->save();
              return redirect(route('content.index'));
          }
      
          public function destroy($content_id)
          {
              $content = Content::find($content_id);
              $content->delete();
              return redirect(route('content.index'));
          }
      
      }
      
  3. ビューファイルの修正

    1. 下記コマンドを実行して新規登録する時に表示しているビューファイルを開く。

      $ vi resources/views/contents/create.blade.php
      
    2. 開いたビューファイルを下記の様に修正してエラー文を表示できる様にする。

      アプリ名ディレクトリ/resources/views/contents/create.blade.php
      <h1>create</h1>
      
      <form action="{{route('content.save')}}" method="post">
          @csrf
          <h3>タイトル</h3>
          {{-- 下記を追記する --}}
          @if ($errors->has('title'))
              @foreach ($errors->get('title') as $title_errors)
                  <p>{{$title_errors}}</p>
                  <br>
              @endforeach
          @endif
          {{-- 上記までを追記する --}}
          <input type="text" name="title">
          <br>
          <h3>内容</h3>
          {{-- 下記を追記する --}}
          @if ($errors->has('detail'))
              @foreach ($errors->get('detail') as $detail_errors)
                  <p>{{$detail_errors}}</p>
                  <br>
              @endforeach
          @endif
          {{-- 上記までを追記する --}}
          <textarea name="detail" cols="30" rows="10"></textarea>
          <br>
          <input type="submit" value="保存">
          <input type="button" onclick="location.href='{{ route('content.index') }}'" value="一覧に戻る">
      </form>
      
  4. 確認

    1. 各種情報入力画面でバリデーションルールと異なる情報を入力した時にエラーが表示されれば作業完了である。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

front-end に Nuxt.js と Laravel が混在するシステムで Cookie を用いてクライアントを識別する

Nuxt-2.14.4 Laravel-6.x

前回の記事の続きですが、 A/B テストのような文脈でクライアント(ブラウザ)ごとに一意なランダムの ID を割り当て、 Cookie を用いてリクエストごとにクライアントを分類できるようにする処理を実装する機会がありました。この記事では front-end に Nuxt.js と Laravel (の view )が混在するシステムで、両方から ID を生成・利用できるようにする方法について紹介します。

方針と注意

基本的な方針は以下の通りです。

  1. リクエストの Cookie ヘッダから ID が得られなかった場合(初回訪問)は ID を生成して Cookie に追加 1 する。
  2. リクエストの Cookie ヘッダから ID を得られた場合(2回目以降の訪問)は、それを利用する。

今回の実装では Nuxt.js と Laravel のどちら側でも Cookie から ID を読み取れるようにするため、 HttpOnly 属性を外し、暗号化を行いません。このため、ログインセッション用の Cookie など、第三者による Cookie の盗聴や改ざんがセキュリティ上のリスクとなるような情報に関しては本記事中の実装方法を避け、フレームワークが提供する機能などを利用してください。

また front-end がすべて Nuxt.js で構成されている場合には Laravel 側の処理は不要なので、 Laravel のセクションを読み飛ばしてください。

実装

Nuxt.js

以下の useClientId は ID の生成および Cookie 操作の処理を集約した composition です。

app/compositions/client-id.ts
import { Store } from 'vuex/types'
import { IncomingMessage } from 'connect'
import Cookie from 'universal-cookie'
import {
  name,
  StoreAbTesting,
  ACTIONS,
  GETTERS
} from '~/store/abTesting'

export function useClientId(store: Store<StoreAbTesting>, req: IncomingMessage) {
  const CLIENT_ID_KEY = 'client_id'

  const cookie = process.server
    ? new Cookie(req.headers?.cookie ?? '')
    : new Cookie()

  const getIdFromStore = (): string => {
    return store.getters[`${name}/${GETTERS.clientId}`]
  }

  const prepareId = async (): Promise<void> => {
    if (process.client) {
      throw new Error('CSR でのメソッド呼び出しは不正です')
    }

    const generateId = (): string => {
      // ランダムな ID を生成して返す
    }

    await store.dispatch(
      `${name}/${ACTIONS.setClientId}`,
      getIdFromCookie() ?? generateId()
    )
  }

  const getIdFromCookie = (): string|undefined => {
    return cookie.get(CLIENT_ID_KEY)
  }

  const isIncludedInCookie = (): boolean => {
    return getIdFromCookie() !== undefined
  }

  const setCookie = (): void => {
    if (process.server) {
      throw new Error('SSR でのメソッド呼び出しは不正です')
    }

    if (isIncludedInCookie()) {
      return
    }

    cookie.set(CLIENT_ID_KEY, getIdFromStore())
  }

  return {
    prepareId,
    setCookie,
    getId: getIdFromStore
  }
}

主要な処理について解説します。

まず Cookie の操作に関しては universal-cookie というライブラリを利用しています。

  const cookie = process.server
    ? new Cookie(req.headers?.cookie ?? '')
    : new Cookie()

このライブラリを利用すると Nuxt の SSR モードでもリクエストヘッダから Cookie を取得することができます。使用例に関しては以下の記事が参考になります。

Nuxt は Context から context.req のようにしてリクエストの情報が取得できるので、 useClientId の利用時はこの変数を引数に入力します。この変数から req.headers?.cookie のようにして Cookie ヘッダの値を文字列として取得できるので、これを Cookie コンストラクタの引数に渡してインスタンス生成しています。

次に Cookie から ID を取得、または生成を行う prepareId メソッドです。

  const prepareId = async (): Promise<void> => {
    if (process.client) {
      throw new Error('CSR でのメソッド呼び出しは不正です')
    }

    const generateId = (): string => {
      // ランダムな ID を生成して返す
    }

    await store.dispatch(
      `${name}/${ACTIONS.setClientId}`,
      getIdFromCookie() ?? generateId()
    )
  }

最初の処理で CSR でのメソッド呼び出しを禁止しています。これは直アクセス時の SSR の時点で ID を用意しておけば以降の処理で ID を使い回せるので、 CSR での呼び出しが不要になるためです。

getIdFromCookie() ?? generateId() の部分で Cookie から ID を取得するか、取得できなければ生成を行っています。取得した ID は Vuex の Store を用いて保存しておきます 2。 Store に関しても Nuxt の Context から context.store のように取得できます。何となく cookie.set(id) のようにしておけば後から cookie.get() で ID を取得できそうな気もしてしまいますが、 SSR で cookie.set() を呼び出してもブラウザ上の Cookie には影響がないため、このような方法では実現できません。

次に Cookie を設定する setCookie メソッドです。

  const setCookie = (): void => {
    if (process.server) {
      throw new Error('SSR でのメソッド呼び出しは不正です')
    }

    if (isIncludedInCookie()) {
      return
    }

    cookie.set(CLIENT_ID_KEY, getIdFromStore())
  }

前述の通り SSR で cookie.set() を呼び出しても意味がないので、 SSR でのメソッド呼び出しを禁止しています。そして ID が Cookie に含まれていない場合のみ Store に保存しておいた ID を取得して Cookie に設定します。

このように実装した useClientId の諸々の処理は plugins から呼び出します。

app/plugins/ab-testing.ts
import { Context } from '@nuxt/types'
import { useClientId } from '~/compositions/libs/ab-testing/client-id'

export default (context: Context) => {
  if (process.server) {
    useClientId(context.store, context.req).prepareId()
  } else {
    useClientId(context.store, context.req).setCookie()
  }
}

前述した通り、 prepareId は SSR で、 setCookie は CSR で呼び出します。これらの処理を Middleware から呼び出しても問題ないように思えてしまいますが、 Middleware の場合は直アクセス時に SSR の処理しか実行されないため Cookie が設定されません。 Nuxt のライフサイクルに関しては以下の記事が参考になります。

後は nuxt.config.js に plugins を追加し、 Vue コンポーネント等から useClientId(store, req).getId() のような形で ID を取得する流れになります。

Laravel

大まかな内容は Nuxt.js と変わりませんが、 Laravel の場合は Store の代わりに単なる singleton インスタンスとしてメモリに保持する方針で実装します 3

まず singleton インスタンスの元となるクラス AbTestingCookie を以下のように作成します。

app/Http/Cookie/AbTestingCookie.php
<?php

namespace App\Http\Cookie;

use Illuminate\Http\Request;

class AbTestingCookie
{
    private $id;

    public function __construct(Request $request)
    {
        $key = 'client_id';

        if ($request->hasCookie($key)) {
            $this->id = $request->cookie($key);
            return;
        }

        $this->id = $this->make();
    }

    public function make()
    {
        // ランダムな ID を生成して返す
    }

    public function id()
    {
        return $this->id;
    }
}

コンストラクタでは Request を引数に取り、 Cookie から ID を取得するか、取得できなければ生成し、 ID をプロパティに保持しておきます。

singleton を生成するための ServiceProvider の実装は以下のとおりです。

app/Providers/AbTestingServiceProvider.php
<?php

namespace App\Providers;

use App\Http\Cookie\AbTestingCookie;
use Illuminate\Http\Request;
use Illuminate\Support\ServiceProvider;

class AbTestingServiceProvider extends ServiceProvider
{
    public function boot(Request $request)
    {
        $this->app->singleton(AbTestingCookie::class, function ($app) use ($request) {
            return new AbTestingCookie($request);
        });
    }
}

次にレスポンスの Set-Cookie ヘッダに ID を付与する処理を Middleware で実装します。

app/Http/Middleware/IssueAbTestingClientId.php
<?php

namespace App\Http\Middleware;

use App\Http\Cookie\AbTestingCookie;
use Closure;

class IssueAbTestingClientId
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $key = 'client_id';

        if ($request->hasCookie($key)) {
            return $next($request);
        }

        return $next($request)->withCookie(cookie(
            $key,
            app(AbTestingCookie::class)->id(),
            0,
            null,
            null,
            false,
            false,  // front-end からも利用するため httpOnly にしない
            false,
            null
        ));
    }
}

この Middleware は ID の生成・利用が必要なすべてのルートで呼び出されるように Kernel.php に設定しておきます。また withCookie() メソッドはデフォルトで HttpOnly 属性を付与しますが、 Nuxt.js からも読み出せるように HttpOnly 属性を外しています。

最後に Nuxt.js から Cookie を読み出せるように EncryptCookies の暗号化の処理対象から除外しておきます。

app/Http/Middleware/EncryptCookies.php
<?php

namespace App\Http\Middleware;

use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;

class EncryptCookies extends Middleware
{
    /**
     * The names of the cookies that should not be encrypted.
     *
     * @var array
     */
    protected $except = [
        'client_id', // front-end からも利用するため暗号化しない
    ];
}

後は view 等から app()->make(AbTestingCookie::class)->id() のようにして ID を取得する流れになります。


  1. サーバー側ではレスポンスに Set-Cookie ヘッダを、クライアント側では document.cookie を利用します。 

  2. 単に State に clientId を保存するだけなので、 Store の実装は割愛します。 

  3. Stack Overflow の Q&A を参考にしています。 

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

[Blade]@includeWhenを使えば@if @include @endif と書くよりシンプルだった

はじめに

@includeWhenというBladeディレクティブはご存知ですか? 私は知りませんでした。

@includeWhen

これを

@if($bool)
    @include('view.name')
@endif

こう書けます。

@includeWhen($bool, 'view.name')

ただ、以下のような落とし穴もあるようなので、使い所は限られそうです。

LaravelのincludeWhenはfalseでも右辺の評価が行われてしまう。

@if @include @endifと書く方が逆にシンプルかもしれない。

他の便利なディレクティブ

公式のドキュメントを読むと@includeIf@authなど、知ってると便利かもしれないディレクティブが色々載っています。
時間があるときに目を通して見ると、スマートな記述方法を見つけられるかもしれません。
Blade拡張を使えば、独自のディレクティブも定義できるようです。

https://readouble.com/laravel/7.x/ja/blade.html

参考

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

【備忘録】Laravel 6でユーザー認証作る時につまづいたけど解決した方法共有!!

はじめに

こんばんは!
私は、毎週、「1週間1制作計画」っていうのをやっています。
最近は、PHPの勉強を始めたので、今週は、PHP及びLaravelの練習を兼ねてLaravelでアプリケーションを制作してみようと思っていました。

そこで、認証機能をつけようと思い、Laravelに標準装備されている認証機能Authを使ってみようと考え、トライしてみました!!

...すると、めっちゃつまづいた!!

今日の投稿では、そのつまづいた部分について、解決した方法を、認証機能の実装方法と共に記録していきます!
また、その方法自体は最後に記載されている参考資料・文献を参考に実装しました。

まずはLaravel 6をインストールしたい!!

Laravel 6をなぜ選んだかというと、サポート期間が最新のLaravel 8よりも長いためです(LTS = Long-Term-Support)。

一般的なインストール方法

laravel new プロジェクト名

ですよね!
しかし、今回は違います!以下のコードをご覧ください。

composer create-project "laravel/laravel=6.*" プロジェクト名

バージョンを指定して、Laravelをインストールする方法は、上記のように、composerを用います。
アプリケーションの実行には、通常通り、

php artisan serve

を使います。

標準装備Auth使っていきましょう!

それでは、いよいよ標準装備のAuthを使っていきます!
まずは、Auth機能を使うために、laravel/uiというパッケージを導入しましょう。

composer require laravel/ui:^1.0 --dev

上記コードをターミナルに入力して実行すると、認証部分のフロントエンドを制作できるlaravel/uiを導入することができます。
Auth関連のファイルを生成するために、以下の2つをターミナルに記述して実行します。

php artisan ui vue --auth
php artisan migrate

php artisan migrateしたらエラー返ってきた!!

そうです、このphp artisan migrateしたら、以下のようなエラーが返ってきてしまいました。

   Illuminate\Database\QueryException  : SQLSTATE[HY000] [2002] Connection refused (SQL: select * from information_schema.tables where table_schema = homestead and table_name = migrations)

  at /Users/username/code/LaravelSample/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664
    660|         // If an exception occurs when attempting to run a query, we'll format the error
    661|         // message to include the bindings with SQL, which will make this exception a
    662|         // lot more helpful to the developer instead of just the database's errors.
    663|         catch (Exception $e) {
  > 664|             throw new QueryException(
    665|                 $query, $this->prepareBindings($bindings), $e
    666|             );
    667|         }
    668| 

  Exception trace:

  1   PDOException::("SQLSTATE[HY000] [2002] Connection refused")
      /Users/username/code/LaravelSample/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70

  2   PDO::__construct("mysql:host=127.0.0.1;port=3306;dbname=homestead", "homestead", "secret", [])
      /Users/username/code/LaravelSample/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70

  Please use the argument -v to see more details.

正直、まだデータベースいじってないのに、何なんだ!!
「よくわからん!!調べても、Dockerを利用している人が多くて、自分の開発環境とは違うので参考にならん!!」
って感じでした。
なので、自分の手持ちのテキストに書いてあったように、そのまま以下をターミナルに記述して実行しちゃいました(笑)

npm install && npm run dev

そして、先ほど、アプリケーションを実行した時のように、

php artisan serve

を実行。
アプリケーションを開くことができる状態になります。
http://localhost:8000/
を開いてみてください。
スクリーンショット 2020-09-22 23.09.58.png
上の画像のようなページが開きます。最初に開いた時と変わらないようにも消えますが、
右上を見て頂くと、きちんと、LOGINとREGISTERと書かれていることがわかります。つまり、きちんと、Auth機能、実装できたんじゃないかと思いますよね!!
これが実は落とし穴...テキストでは触れられていない落とし穴だった。

REGISTER(ユーザー登録)できない!?

まず、初めて利用するので、ユーザー登録をしてみます。
REGISTERを押して、ユーザー登録画面に遷移します。
そして、ユーザー名やメールアドレス、パスワードと言った、登録に必要なデータを入力します。
さぁ、REGISTERボタンを押して、登録だ!

Database name seems incorrect
You're using the default database name laravel. This database does not exist.
Edit the .env file and use the correct database name in the DB_DATABASE key.

なんか出てきた...泣

先ほど、php artisan migrate失敗した時もデータベース関連のエラーが出てきましたね。
今回も同じく、データベース関連のエラーです。データベース名が違うんですって。(incorrect)

次項から、この問題を解決していきます。

まず、データベースに接続しよう!

Laravelプロジェクト作ってから全くいじっていなかったデータベースを、ついにいじっていきます!

MySQLをインストール

MySQLをまずはインストールしましょう。以下をターミナルに記述して実行してください。

brew install mysql

インストールできたら、起動してください。

mysql.server start --skip-grant-tables

上記の方法で起動すると、パスワード不要でした!
その後、ルートユーザーで接続。

mysql -uroot

データベースを作っていきましょう

それでは、続いて、データベースを作っていきます。
最初に、以下のコマンドを記述して、現在のデータベースの様子をチェックします。

mysql> show databases;

ダッシュ(途切れ途切れのあの線)で囲われて、Databaseっていうのが出てきます。出てきたらオッケーです。
次に、新しくデータベースを作成するコマンドを記述して実行してください。

create database sample;

私が、上記でsampleと記述した部分は、みなさんの任意の名前です。
そしてもう一度、show databases;で、データベースの状況を確認します。出力結果に、ここでいう、sampleが含まれていたら問題ないです!

rootユーザーのパスワードの変更

MySQLのrootユーザーのパスワードの変更を行います。

mysql> FLUSH PRIVILEGES;

mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'secret';

上記を実行してください。
それぞれ、実行直後には、Query OK, なんちゃらとかいうのが返ってきたらOKです!
ここでは、rootユーザーのパスワードを、「secret」に変更しています。
こちらも特に決まりはないので、任意の言葉で。
まぁ、サンプルコードでよく見るのは、secretですね(笑)

この処理が終了すれば、一旦MySQLを終了してから再起動させます。以下のコマンドを入力。

mysql> exit

再起動

mysql.server restart

認証プラグインの変更

ここで、すいません、MySQLをまた違う方法で開きたいので、またまたexitをお願いします。
以下のコマンドでMySQLに接続してください。

mysql -uroot -p

その後、Enter Passwordと言われ、パスワードの入力を求められます。
先ほど、変更したルートユーザーのパスワード、secretを入力してください。
接続できたら、次のコマンドを実行。

SELECT user, host, plugin FROM mysql.user;

SELECT文が出てきたら、ようやくデータベースいじってる感出てきましたねー!
上記コマンドで現状の認証プラグインを調べることができます。
おそらく、caching_sha2_passwordとなっているかと思います。
今回は、mysql_native_passwordを使いたいので、変更しましょう。以下のコマンドを実行してください。

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'secret';

それでは、再度、以下のコマンドで確認してみましょう。

SELECT user, host, plugin FROM mysql.user;

root項目のプラグインが、caching_sha2_passwordだったのに、mysql_native_passwordに変更されているかと思います。

あと少し!.envファイルの編集をしよう!

一旦、ターミナルからは離れて、エディタに移動してください。
作成したプロジェクトのフォルダ内に、「.env」という名前のファイルがあると思います。そのファイルを開きましょう。
※examapleがついている.envファイルは違います!!今回編集するのは、ただの.envファイルです!

開いたらなんだかデータベースに関連しそうな内容がたくさん書かれています。
その中でも、以下のコメントアウトした部分を変更します。

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel // ココ変更するよ!
DB_USERNAME=root
DB_PASSWORD= // ココ変更するよ!

まず、DB_DATABASE=laravelの、laravelの部分を、sampleにします。
このsampleは、新しくデータベースを作った時に使った名前です。

DB_PASSWORD= は、元々空欄になっていますが、rootユーザーのパスワードとして設定した、secretを空欄部分に入れます。

最終的には、以下のような状態になるかと思います。

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=SampleApp
DB_USERNAME=root
DB_PASSWORD=secret

編集後、またターミナルに帰りましょう。
キャッシュの削除を行います。

php artisan config:cache

最初できなかったphp artisan migrateしてみる!

この時点で、

php artisan migrate

してみると、成功します。

REGISTERリベンジ!

もう一度、Laravelアプリケーションを実行してみましょう。

php artisan serve

ですね。
そしてもう一度、REGISTERに挑戦してみてください!!
すると、エラーは特に出ず、登録できるようになっています!!

まとめ

長くなりましたが、数時間かけて格闘していたので、備忘録としてまとめてみました!
ここまでお読みくださり、ありがとうございました。
理解不足等による不備があれば、コメントお願い致します。

参考資料・文献

【Laravel】MySQLの接続方法を徹底解説【コピペでOK】

更新!!Laravel6/7 (laravel/ui)でのLogin機能の実装方法〜MyMemo

【Laravel】Laravelでバージョンを指定してインストールする方法

掌田津耶乃著『PHPフレームワーク Laravel入門 第2版』(2020)

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

【備忘録】Laravel 6でユーザー認証作る時につまずいたけど解決した方法共有!!

はじめに

こんばんは!
私は、毎週、「1週間1制作計画」っていうのをやっています。
最近は、PHPの勉強を始めたので、今週は、PHP及びLaravelの練習を兼ねてLaravelでアプリケーションを制作してみようと思っていました。

そこで、認証機能をつけようと思い、Laravelに標準装備されている認証機能Authを使ってみようと考え、トライしてみました!!

...すると、めっちゃつまづいた!!

今日の投稿では、そのつまづいた部分について、解決した方法を、認証機能の実装方法と共に記録していきます!
また、その方法自体は最後に記載されている参考資料・文献を参考に実装しました。

まずはLaravel 6をインストールしたい!!

Laravel 6をなぜ選んだかというと、サポート期間が最新のLaravel 8よりも長いためです(LTS = Long-Term-Support)。

一般的なインストール方法

laravel new プロジェクト名

ですよね!
しかし、今回は違います!以下のコードをご覧ください。

composer create-project "laravel/laravel=6.*" プロジェクト名

バージョンを指定して、Laravelをインストールする方法は、上記のように、composerを用います。
アプリケーションの実行には、通常通り、

php artisan serve

を使います。

標準装備Auth使っていきましょう!

それでは、いよいよ標準装備のAuthを使っていきます!
まずは、Auth機能を使うために、laravel/uiというパッケージを導入しましょう。

composer require laravel/ui:^1.0 --dev

上記コードをターミナルに入力して実行すると、認証部分のフロントエンドを制作できるlaravel/uiを導入することができます。
Auth関連のファイルを生成するために、以下の2つをターミナルに記述して実行します。

php artisan ui vue --auth
php artisan migrate

php artisan migrateしたらエラー返ってきた!!

そうです、このphp artisan migrateしたら、以下のようなエラーが返ってきてしまいました。

   Illuminate\Database\QueryException  : SQLSTATE[HY000] [2002] Connection refused (SQL: select * from information_schema.tables where table_schema = homestead and table_name = migrations)

  at /Users/username/code/LaravelSample/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664
    660|         // If an exception occurs when attempting to run a query, we'll format the error
    661|         // message to include the bindings with SQL, which will make this exception a
    662|         // lot more helpful to the developer instead of just the database's errors.
    663|         catch (Exception $e) {
  > 664|             throw new QueryException(
    665|                 $query, $this->prepareBindings($bindings), $e
    666|             );
    667|         }
    668| 

  Exception trace:

  1   PDOException::("SQLSTATE[HY000] [2002] Connection refused")
      /Users/username/code/LaravelSample/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70

  2   PDO::__construct("mysql:host=127.0.0.1;port=3306;dbname=homestead", "homestead", "secret", [])
      /Users/username/code/LaravelSample/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70

  Please use the argument -v to see more details.

正直、まだデータベースいじってないのに、何なんだ!!
「よくわからん!!調べても、Dockerを利用している人が多くて、自分の開発環境とは違うので参考にならん!!」
って感じでした。
なので、自分の手持ちのテキストに書いてあったように、そのまま以下をターミナルに記述して実行しちゃいました(笑)

npm install && npm run dev

そして、先ほど、アプリケーションを実行した時のように、

php artisan serve

を実行。
アプリケーションを開くことができる状態になります。
http://localhost:8000/
を開いてみてください。
スクリーンショット 2020-09-22 23.09.58.png
上の画像のようなページが開きます。最初に開いた時と変わらないようにも消えますが、
右上を見て頂くと、きちんと、LOGINとREGISTERと書かれていることがわかります。つまり、きちんと、Auth機能、実装できたんじゃないかと思いますよね!!
これが実は落とし穴...テキストでは触れられていない落とし穴だった。

REGISTER(ユーザー登録)できない!?

まず、初めて利用するので、ユーザー登録をしてみます。
REGISTERを押して、ユーザー登録画面に遷移します。
そして、ユーザー名やメールアドレス、パスワードと言った、登録に必要なデータを入力します。
さぁ、REGISTERボタンを押して、登録だ!

Database name seems incorrect
You're using the default database name laravel. This database does not exist.
Edit the .env file and use the correct database name in the DB_DATABASE key.

なんか出てきた...泣

先ほど、php artisan migrate失敗した時もデータベース関連のエラーが出てきましたね。
今回も同じく、データベース関連のエラーです。データベース名が違うんですって。(incorrect)

次項から、この問題を解決していきます。

まず、データベースに接続しよう!

Laravelプロジェクト作ってから全くいじっていなかったデータベースを、ついにいじっていきます!

MySQLをインストール

MySQLをまずはインストールしましょう。以下をターミナルに記述して実行してください。

brew install mysql

インストールできたら、起動してください。

mysql.server start --skip-grant-tables

上記の方法で起動すると、パスワード不要でした!
その後、ルートユーザーで接続。

mysql -uroot

データベースを作っていきましょう

それでは、続いて、データベースを作っていきます。
最初に、以下のコマンドを記述して、現在のデータベースの様子をチェックします。

mysql> show databases;

ダッシュ(途切れ途切れのあの線)で囲われて、Databaseっていうのが出てきます。出てきたらオッケーです。
次に、新しくデータベースを作成するコマンドを記述して実行してください。

create database sample;

私が、上記でsampleと記述した部分は、みなさんの任意の名前です。
そしてもう一度、show databases;で、データベースの状況を確認します。出力結果に、ここでいう、sampleが含まれていたら問題ないです!

rootユーザーのパスワードの変更

MySQLのrootユーザーのパスワードの変更を行います。

mysql> FLUSH PRIVILEGES;

mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'secret';

上記を実行してください。
それぞれ、実行直後には、Query OK, なんちゃらとかいうのが返ってきたらOKです!
ここでは、rootユーザーのパスワードを、「secret」に変更しています。
こちらも特に決まりはないので、任意の言葉で。
まぁ、サンプルコードでよく見るのは、secretですね(笑)

この処理が終了すれば、一旦MySQLを終了してから再起動させます。以下のコマンドを入力。

mysql> exit

再起動

mysql.server restart

認証プラグインの変更

ここで、すいません、MySQLをまた違う方法で開きたいので、またまたexitをお願いします。
以下のコマンドでMySQLに接続してください。

mysql -uroot -p

その後、Enter Passwordと言われ、パスワードの入力を求められます。
先ほど、変更したルートユーザーのパスワード、secretを入力してください。
接続できたら、次のコマンドを実行。

SELECT user, host, plugin FROM mysql.user;

SELECT文が出てきたら、ようやくデータベースいじってる感出てきましたねー!
上記コマンドで現状の認証プラグインを調べることができます。
おそらく、caching_sha2_passwordとなっているかと思います。
今回は、mysql_native_passwordを使いたいので、変更しましょう。以下のコマンドを実行してください。

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'secret';

それでは、再度、以下のコマンドで確認してみましょう。

SELECT user, host, plugin FROM mysql.user;

root項目のプラグインが、caching_sha2_passwordだったのに、mysql_native_passwordに変更されているかと思います。

あと少し!.envファイルの編集をしよう!

一旦、ターミナルからは離れて、エディタに移動してください。
作成したプロジェクトのフォルダ内に、「.env」という名前のファイルがあると思います。そのファイルを開きましょう。
※examapleがついている.envファイルは違います!!今回編集するのは、ただの.envファイルです!

開いたらなんだかデータベースに関連しそうな内容がたくさん書かれています。
その中でも、以下のコメントアウトした部分を変更します。

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel // ココ変更するよ!
DB_USERNAME=root
DB_PASSWORD= // ココ変更するよ!

まず、DB_DATABASE=laravelの、laravelの部分を、sampleにします。
このsampleは、新しくデータベースを作った時に使った名前です。

DB_PASSWORD= は、元々空欄になっていますが、rootユーザーのパスワードとして設定した、secretを空欄部分に入れます。

最終的には、以下のような状態になるかと思います。

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=SampleApp
DB_USERNAME=root
DB_PASSWORD=secret

編集後、またターミナルに帰りましょう。
キャッシュの削除を行います。

php artisan config:cache

最初できなかったphp artisan migrateしてみる!

この時点で、

php artisan migrate

してみると、成功します。

REGISTERリベンジ!

もう一度、Laravelアプリケーションを実行してみましょう。

php artisan serve

ですね。
そしてもう一度、REGISTERに挑戦してみてください!!
すると、エラーは特に出ず、登録できるようになっています!!

まとめ

長くなりましたが、数時間かけて格闘していたので、備忘録としてまとめてみました!
ここまでお読みくださり、ありがとうございました。
理解不足等による不備があれば、コメントお願い致します。

参考資料・文献

【Laravel】MySQLの接続方法を徹底解説【コピペでOK】

更新!!Laravel6/7 (laravel/ui)でのLogin機能の実装方法〜MyMemo

【Laravel】Laravelでバージョンを指定してインストールする方法

掌田津耶乃著『PHPフレームワーク Laravel入門 第2版』(2020)

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