20200516のlaravelに関する記事は13件です。

Guzzleを使って外部APIへPOST送信

Guzzleとは

PHP用のHTTPクライアントライブラリです。
WEBサーバに外部APIへPOST送信などを行いたい際にGuzzleが非常に便利でした。

導入方法・簡単な使い方をまとめてみました。

インストール方法

composerコマンドでguzzlehttp/guzzleパッケージのインストール

composer require guzzlehttp/guzzle

2020年5月現在、最新は6.5のようです。

composer.json
{
    "require": {
        "guzzlehttp/guzzle": "^6.5"
    }
}

使い方

app/src/Controller/Front/ApiController.php
use GuzzleHttp\Client;

public function getSession()
{
    $session = $this->request->getSession(); //セッションを取得
    $data = $session->read('data');          //Formで入力しているデータを取得します

    $this->postApi($data); 
    //略
}

public function postApi($data)
{
    $url         = "https://external.api";                  //API側(受取側)のURLを指定
    $basic       = "basic-id:basic-password"                //basic認証のIDとPASSWORD ※envに格納しましょう
    $encodeBasic = base64_encode($basic);                   //basic認証をエンコーディング
    $config      = ['base_uri' => 'https://www.this.com/']; //送信元のURLを指定

    $client = new Client($config);

            $option = [
            'http_errors' => true,                             //エラーを出力
            'verify'      => false,                            //SSL認証を無視

            'headers' =>    //ヘッダーにBASIC認証などを追加
            [                                     
                'Authorization' => 'Basic ' . $encodeBasic,    //エンコードしたbasic認証です。外部API側でデコードしてください
                'Host'          => "external.api",             //外部APIのホスト指定
                'Content-Type'  => "text/plain",               //コンテントタイプを指定 無くてもできました。
            ],

            'form_params' => //application / x-www-form-urlencoded POSTリクエストを送信するために使用
            [
                "username"      => $data["name"],      //外部APIとカラム名が異なっていたのでここで修正。
                "tel"           => $data["telephone"], //同上
                "address"       => $data["mail"],      //同上
                "contents"      => $data["content"],   //同上
            ],
        ];

    $response = $client->request("POST", $url, $option); //第1引数はPOST or GET、第2引数は受取側のAPI,第3引数はPOST送信データ
}

レスポンスのステータスコードのチェック

app/src/Controller/Front/ApiController.php
    $response->getStatusCode(); 

参考にした資料

Guzzleドキュメント
http://docs.guzzlephp.org/en/stable/index.html

Guzzle Httpを試してみたよ clustfe様
https://qiita.com/clustfe/items/f9ff2b12da7a501197f8

Guzzleでファイルアップロードする zaburo様
https://qiita.com/yousan/items/2a4d9eac82c77be8ba8b

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

EC2にデプロイ時、php artisan migrateができないエラーの対処法

LaravelをEC2にデプロイ作業の中でphp artisan migrateを実行しようとするとエラーが出てしまってめっちゃハマってしまったのでメモ兼共有。

php artisan migrateが実行できない

エラー内容
[ec2-user@ip-172-31-36-11 laravel]$ php artisan migrate

   Illuminate\Database\QueryException 

  could not find driver (SQL: select * from information_schema.tables where table_schema = laravel and table_name = migrations and table_type = 'BASE TABLE')

  at vendor/laravel/framework/src/Illuminate/Database/Connection.php:671
    667|         // If an exception occurs when attempting to run a query, we'll format the error
    668|         // message to include the bindings with SQL, which will make this exception a
    669|         // lot more helpful to the developer instead of just the database's errors.
    670|         catch (Exception $e) {
  > 671|             throw new QueryException(
    672|                 $query, $this->prepareBindings($bindings), $e
    673|             );
    674|         }
    675|

  • 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. 
    https://laravel.com/docs/master/database#configuration

      +36 vendor frames 
  37  artisan:37
      Illuminate\Foundation\Console\Kernel::handle()

おそらくDBとLaravel側で連携ができていないと予想。
よくよく考えたら私はMysqlはインストールしたんだけれどもSQLサーバーはインストールしていなかったのが原因なのかなって考えました。
MySQLとSQLサーバーは違うものなのか?正直今でもわかっていないので勉強します。

ただEC2インスタンス内でMySQLを確かにインストールしたはずなのにMySQLコマンドが実行できなかったことから、Qiitaの記事を参考にSQLサーバーであるMariaDBをインストールしました。Mysqlでの派生のものでMysqlを実行できるとのことだったので。
LaravelのローカルでのDB連携でも行ったように.envと同じ名前のDBがないとLaravelとDBの連携ができません。そのため私の場合はMariaDB内にLaravelというDBを用意しました。

[ec2-user@ip-172-31-36-11 laravel]$ mysql -u root -p
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 247
Server version: 5.5.64-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| laravel            |
| mysql              |
| performance_schema |
+--------------------+
4 rows in set (0.00 sec)

これで準備完了です。いざ

migrate実行
[ec2-user@ip-172-31-36-11 laravel]$ php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table

   Illuminate\Database\QueryException 

  SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes (SQL: alter table `users` add unique `users_email_unique`(`email`))

  at vendor/laravel/framework/src/Illuminate/Database/Connection.php:671
    667|         // If an exception occurs when attempting to run a query, we'll format the error
    668|         // message to include the bindings with SQL, which will make this exception a
    669|         // lot more helpful to the developer instead of just the database's errors.
    670|         catch (Exception $e) {
  > 671|             throw new QueryException(
    672|                 $query, $this->prepareBindings($bindings), $e
    673|             );
    674|         }
    675|

      +11 vendor frames 
  12  database/migrations/2014_10_12_000000_create_users_table.php:24
      Illuminate\Support\Facades\Facade::__callStatic()

      +22 vendor frames 
  35  artisan:37
      Illuminate\Foundation\Console\Kernel::handle()

なんとなくエラーでる未来は見えていた。。。
ただ1つだけmigrateできているのでとりあえず実行はできていることは確認。エラー文コピって調べていると下記のサイトを見つけました。
https://helog.jp/laravel/migrate-sql-error/
このサイトの通りに文を追記しました。
再度実行!

[ec2-user@ip-172-31-36-11 laravel]$ php artisan migrate
Migrating: 2014_10_12_000000_create_users_table

   Illuminate\Database\QueryException 

  SQLSTATE[42S01]: Base table or view already exists: 1050 Table 'users' already exists (SQL: create table `users` (`id` bigint unsigned not null auto_increment primary key, `name` varchar(191) not null, `email` varchar(191) not null, `email_verified_at` timestamp null, `password` varchar(191) not null, `remember_token` varchar(100) null, `created_at` timestamp null, `updated_at` timestamp null) default character set utf8mb4 collate 'utf8mb4_unicode_ci')

  at vendor/laravel/framework/src/Illuminate/Database/Connection.php:671
    667|         // If an exception occurs when attempting to run a query, we'll format the error
    668|         // message to include the bindings with SQL, which will make this exception a
    669|         // lot more helpful to the developer instead of just the database's errors.
    670|         catch (Exception $e) {
  > 671|             throw new QueryException(
    672|                 $query, $this->prepareBindings($bindings), $e
    673|             );
    674|         }
    675|

      +11 vendor frames 
  12  database/migrations/2014_10_12_000000_create_users_table.php:24
      Illuminate\Support\Facades\Facade::__callStatic()

      +22 vendor frames 
  35  artisan:37
      Illuminate\Foundation\Console\Kernel::handle()

またしてもエラー、、、しかしこれはローカルでのLaravel開発時に嫌という程見てきたエラーなのですぐに対処できました。
なんなのかというとすでにusersテーブルがあるからmigrateできないとのこと。そのためusersテーブルを削除してあげる必要があります。

$ mysql -u root -p
$ use laravel;
$ drop table users;

これで大丈夫なはず。migrateを邪魔しているusersテーブルを削除しました。コード間違っていたらごめんなさい。
migrate実行したら見事いけました!!

しかし、、、、Laravelの画面にはこんなエラーが、、、
スクリーンショット 2020-05-14 13.10.01.png
???もうよくわからん、、、
ただこれはWebサーバーの再起動で消えました。
晴れてローカルのLaravelとEC2にデプロイしたLaravelと見た目が一致しました。本当に大変でした。。。

備考

EC2にMysql実行環境があってもLaravelプロジェクト側にMysql実行環境がないとmigrateはできません。
そのためLaravel側にもMySQL実行できる環境(ドライバ)をインストールしてあげる必要があります。

Mysqlのドライバ入れた
$ sudo yum install php74-php-mysqlnd.x86_64

上記のコマンドを実行していないと下記のphpinfo()PDO driversにはsqliteの記述しかないはずなのでMysqlコマンドが実行されません。気をつけてください。
自分はこれに気づくのに一時間くらいかかりました。

phpinfo()
PDO

PDO support => enabled
PDO drivers => mysql, sqlite
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Lraravelヘルパ関数の配列とオブジェクトについて

はじめに

今回はLraravelヘルパ関数の配列とオブジェクトについてまとめます。

連想配列の関数について(抜粋)

Arrクラスは配列を操作する時に便利に使えます。

①Arr::addメソッドは指定キー/値のペアをそのキーが存在していない場合とnullがセットされている場合に、配列に追加します。

use Illuminate\Support\Arr;
$array = Arr::add(['name' => 'skirt'], 'price', 5000);
// ['name' => 'Desk', 'price' => 5000]
$array = Arr::add(['name' => 'skirt', 'price' => null], 'price', 5000);
// ['name' => 'Desk', 'price' => 5000]

②Arr::getメソッドは「ドット」記法で指定した値を深くネストされた配列から取得します。

use Illuminate\Support\Arr;
$array = ['products' => ['skirt' => ['price' => 5000]]];
$price = Arr::get($array, 'products.skirt.price');
// 5000

③Arr::forgetメソッドは「ドット記法」で指定キー/値のペアを深くネストされた配列から取り除きます。

use Illuminate\Support\Arr;
$array = ['products' => ['skirt' => ['price' => 5000]]];
Arr::forget($array, 'products.skirt');
// ['products' => []]

終わりに

いかがでしたでしょうか。
よく使いそうなものを抜粋しましたが公式ドキュメントに他にも記載してあるので確認してみてください。

参考:ヘルパ 7.x Laravel

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

【初心者】LaravelファイルをEC2にデプロイの際Composer installができない際の対処法 simplexml

まずLaravelプロジェクトのcomposer installまでの手順はQiitaなどの技術系ブログ記事に多くの手順ブログがありますのでこちらをご覧ください。
私が参考にしたデプロイ記事はこちらになります。
https://qiita.com/nakm/items/0bcc6564538a0604b2ce

PHPのバージョンについては適宜対応してください。

EC2インスタンス内でcomposer installをしようとすると。。。下記のようなエラー

[ec2-user@ip-172-31-36-11 laravel]$ composer install

>
  Problem 1
    - Installation request for aws/aws-sdk-php 3.135.2 -> satisfiable by aws/aws-sdk-php[3.135.2].
    - aws/aws-sdk-php 3.135.2 requires ext-simplexml * -> the requested PHP extension simplexml is missing from your system.
  Problem 2
    - Installation request for doctrine/dbal 2.10.2 -> satisfiable by doctrine/dbal[2.10.2].
    - doctrine/dbal 2.10.2 requires ext-pdo * -> the requested PHP extension pdo is missing from your system.
  Problem 3
    - Installation request for laravel/framework v7.6.2 -> satisfiable by laravel/framework[v7.6.2].
    - laravel/framework v7.6.2 requires ext-mbstring * -> the requested PHP extension mbstring is missing from your system.
  Problem 4
    - Installation request for league/commonmark 1.3.4 -> satisfiable by league/commonmark[1.3.4].
    - league/commonmark 1.3.4 requires ext-mbstring * -> the requested PHP extension mbstring is missing from your system.
  Problem 5
    - Installation request for tijsverkoyen/css-to-inline-styles 2.2.2 -> satisfiable by tijsverkoyen/css-to-inline-styles[2.2.2].
    - tijsverkoyen/css-to-inline-styles 2.2.2 requires ext-dom * -> the requested PHP extension dom is missing from your system.
  Problem 6
    - Installation request for facade/ignition 2.0.2 -> satisfiable by facade/ignition[2.0.2].
    - facade/ignition 2.0.2 requires ext-mbstring * -> the requested PHP extension mbstring is missing from your system.
  Problem 7
    - Installation request for phar-io/manifest 1.0.3 -> satisfiable by phar-io/manifest[1.0.3].
    - phar-io/manifest 1.0.3 requires ext-dom * -> the requested PHP extension dom is missing from your system.
  Problem 8
    - Installation request for phpunit/php-code-coverage 7.0.10 -> satisfiable by phpunit/php-code-coverage[7.0.10].
    - phpunit/php-code-coverage 7.0.10 requires ext-dom * -> the requested PHP extension dom is missing from your system.
  Problem 9
    - Installation request for phpunit/phpunit 8.5.3 -> satisfiable by phpunit/phpunit[8.5.3].
    - phpunit/phpunit 8.5.3 requires ext-dom * -> the requested PHP extension dom is missing from your system.
  Problem 10
    - Installation request for scrivo/highlight.php v9.18.1.1 -> satisfiable by scrivo/highlight.php[v9.18.1.1].
    - scrivo/highlight.php v9.18.1.1 requires ext-mbstring * -> the requested PHP extension mbstring is missing from your system.
  Problem 11
    - Installation request for theseer/tokenizer 1.1.3 -> satisfiable by theseer/tokenizer[1.1.3].
    - theseer/tokenizer 1.1.3 requires ext-dom * -> the requested PHP extension dom is missing from your system.
  Problem 12
    - aws/aws-sdk-php 3.135.2 requires ext-simplexml * -> the requested PHP extension simplexml is missing from your system.
    - league/flysystem-aws-s3-v3 1.0.24 requires aws/aws-sdk-php ^3.0.0 -> satisfiable by aws/aws-sdk-php[3.135.2].
    - Installation request for league/flysystem-aws-s3-v3 1.0.24 -> satisfiable by league/flysystem-aws-s3-v3[1.0.24].
  To enable extensions, verify that they are enabled in your .ini files:
    - /etc/opt/remi/php74/php.ini
    - /etc/opt/remi/php74/php.d/20-bz2.ini
    - /etc/opt/remi/php74/php.d/20-calendar.ini
    - /etc/opt/remi/php74/php.d/20-ctype.ini
    - /etc/opt/remi/php74/php.d/20-curl.ini
    - /etc/opt/remi/php74/php.d/20-exif.ini
    - /etc/opt/remi/php74/php.d/20-fileinfo.ini
    - /etc/opt/remi/php74/php.d/20-ftp.ini
    - /etc/opt/remi/php74/php.d/20-gettext.ini
    - /etc/opt/remi/php74/php.d/20-iconv.ini
    - /etc/opt/remi/php74/php.d/20-json.ini
    - /etc/opt/remi/php74/php.d/20-phar.ini
    - /etc/opt/remi/php74/php.d/20-sockets.ini
    - /etc/opt/remi/php74/php.d/20-sodium.ini
    - /etc/opt/remi/php74/php.d/20-tokenizer.ini
  You can also run `php --ini` inside terminal to see which files are used by PHP in CLI mode.

たくさん文字が出てきて驚きました笑
上記のエラーによるとcomposer install時に必要なモジュールがインストールされていなかったようで上記のようなエラーがでております。。。
エラー文のrequiresに続いているものが必要なモジュールの名前になります。そのため、全ての正しいモジュールをインストールしてあげなければなりません。
しかしモジュールをインストールをしているとまたしてもエラー。
ext-simplexmlをインストールしようとするとエラーが発生してしまいます。

[ec2-user@ip-172-31-36-11 laravel]$ sudo yum install --enablerepo=remi  ext-simplexml 
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
amzn2-core                                                                                                   | 2.4 kB  00:00:00     
251 packages excluded due to repository priority protections
エラー: 何もしません
[ec2-user@ip-172-31-36-11 laravel]$ sudo yum install --enablerepo=remi-php74 php-xml
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
amzn2-core                                                                                                   | 2.4 kB  00:00:00     
remi-php74                                                                                                   | 3.0 kB  00:00:00     
remi-php74/primary_db                                                                                        | 211 kB  00:00:00     
246 packages excluded due to repository priority protections
パッケージ php-xml-5.4.16-46.amzn2.0.2.x86_64 はインストール済みか最新バージョンです
何もしません
[ec2-user@ip-172-31-36-11 laravel]$ sudo yum install --enablerepo=remi-php74 unzip
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
246 packages excluded due to repository priority protections
パッケージ unzip-6.0-20.amzn2.x86_64 はインストール済みか最新バージョンです
何もしません

上記のような感じで結構頑張ったんですがなぜかext-simplexmlが入りません。
そこで下記を実行してみました。

[ec2-user@ip-172-31-36-11 laravel]$ sudo yum --enablerepo=epel,remi,remi-php74 install php74-php-xml.x86_64
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
301 packages excluded due to repository priority protections
依存性の解決をしています

できたっぽい:joy:
つかさず

$ composer install

いけました。simplexmlだけでこんなにもハマるとは思っていませんでした。もし同じ症状で困っている方がいましたら参考にしてみてください。

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

PostmanでLaravel Sanctumを使う手順

Larvel 7.xには「Laravel Sanctum」というSPAやモバイルアプリケーション向けの認証システムが用意されています。簡単にログイン・ログアウトができて大変便利なのですが、Postmanを使う際に色々と設定が必要だったので、ここに残しておきます。

とはいえ、海外のチュートリアルをそのまま日本語テキストにしただけなんですけどね...

英語のチュートリアル動画はこちら
https://codecourse.com/watch/laravel-sanctum-airlock-with-postman?part=laravel-sanctum-airlock-with-postman

それでは、やっていきましょう。

0. 事前情報

Dockerにて下記のイメージを使用しました。

  • nginx:latest
  • bitnami/php-fpm:latest
  • mariadb:latest

今回とは特に関係がありませんが、この環境でも問題なく動いたよという報告も兼ねて記載しておきます。

また、FiddleのHOSTを使って localhost:1000 qiita-sanctum として登録しています。 http://qiita-sanctum/ を叩けば、Laravelのトップページが表示されるようになっています。

以上が今回の環境です。


読む前に...下記の手順について追記

Collectionに「Pre-request Script」としてCSRFトークンを取得・セットする記述を行いましたが、login用のリクエストタブ内の「Pre-request Script」に追加するだけで十分なようです。

Collectionに設定してしまうと、他のリクエストを行う際も毎度まいどCSRFトークン発行をリクエストしてしまうので、余計な1リクエスト分を節約するためにも避けた方が良いかもしれません。(詳しいことは検証していないので、各自判断してください。)


1.environmentを作成する

c8438fa156129f157a6c7b7ca2a92ec2.png

  1. 目マークをクリックする。
  2. 「Add」をクリックする。

aa9bafc413c901cfd8f14a2eeadf4f36.png

  1. 適当にenvironmentの名前を付ける。(今回は「qiitaの環境」としました。)
  2. 「Add」で追加をする。
  3. Xボタンからモーダルを閉じる。

13acd1a6aa81d135ae8e578450df47ff.png

右上の欄から先程作成したenvironmentを選択する。(「qiitaの環境」を選択しました。)

2.Collectionを作成する

8bf8c0520564ff0c083ea6dd6b6c708c.png

  1. 「New Collection」をクリックする。
  2. 適当な名前を入力する。
  3. 「Create」でCollectionを作成する。

3.リクエストを諸々設定していく

リスエストを設定していく前に、リクエストをコレクションに保存しておく必要があります。

4897c36a29696df5259c093d4ee3bf93.png
4404bc38f325843c2b68b55dcadf539f.png

  1. ▼マークをクリックする。
  2. Save Asをクリックする。
  3. 先程作成した「Collection」を選択する。
  4. 「Save」をクリックする。

以上でコレクションへの保存は完了です。
次にリクエストを設定していきましょう。

0208444f4251c92007bd78cd7ec5a520.png

  1. POSTを選択する。
  2. LaravelのURL + /login を入力する。(今回の環境では「qiita-sanctum/login」としました。)

14d9a3aa0df7ad76f37c3e710e6f686b.png

事前に作っておいたログイン用のアカウント情報を入力する。KEYには「email」と「password」と入力し、それぞれに沿った情報を入力する。

この状態で一旦、「Send」からリクエストを実行してみます。

9face4e321846c900e0f00ea17abf29d.png

エラーコード「419」が返ってきました。
これはCSRFトークンが添えられていないために起きる問題です。
Laravel Sanctumではログインを行う前にCSRFトークンを発行しておく必要があります。次の手順に進んでトークンを発行し、headerに含む設定を行います。

4.CSRFトークンを発行して適用する

f3be37292cc13a6b2a876e2964718861.png

  1. 先程作成したCollectionにカーソルを合わせて、三点リーダーのアイコンをクリックする。
  2. 「Edit」をクリックする。

2d7f06c6c6dc668a56228b924c747591.png

  1. 「Pre-request Scripts」タブを選択する。
  2. CSRFトークン取得・セットのスクリプトを記述する。
  3. 「Update」をクリックする。

(2)の記述は下記の通りです。

pm.sendRequest({
    url: 'http://qiita-sanctum/sanctum/csrf-cookie',
    method: 'GET'
}, function (error, response, { cookies }) {
    if (!error) {
        pm.environment.set('xsrf-token', cookies.get('XSRF-TOKEN'))
    }
})

Pre-request Scriptは、リクエストタブで指定したURLへのリクエストよりも先にリクエストを実行します。事前にCSRFトークンを発行し、Postmanのenvironmentにこのトークンをセットする...という処理です。(コードは動画からパクりましたw)

ここで一旦、「Send」でリクエストを実行してみましょう。

6120b8c51cb05863d8386b908a46545d.png

  1. 目マークをクリックする。
  2. 「xsrf-token」という名前でCSRFトークンがセットされていることを確認する。

CSRFトークンがセットできましたので、トークンを適用していきましょう。

63bef8a234108502a3c1bfad073ba888.png

  1. Headersタブを開く。
  2. それぞれの項目を入力する。

(2)では、下記の情報を入力しました。

Accept: application/json
X-XSRF-TOKEN: {{xsrf-token}}

{{xsrf-token}} と入力してマウスカーソルを当てると、トークンの内容が表示されるはずです。ここで表示されない場合は、何らかの手順が抜けている可能性があります。

5.ログインを実行する

では、ログインを実行するために「Send」をクリックしましょう。
Bodyに何も表示されなければ、もう一度実行してみましょう。

f3c11c07b1dcc9ea96b19646cd87a2a3.png

Bodyの75行目あたりに「You are logged in!」と表示されれば、ログイン成功となります!


番外編: api/user を叩くには

api/userをPostmanで叩くためには、ここでもいくつか作業が必要となります。

25bc6f6f425cbbb23a56a042652b55c0.png

単純に「qiita-sanctum/api/user」に対してHeader「Accept: application/json」だけを設定してリクエストを送っても正しく動きません。

2b84ba4e61395b35dd55518540f925b1.png

Headersにて「Referer」というKEYを追加し、「localhost」というVALUEを設定する必要があります。

このように設定を行うと、ログイン中のユーザー情報を取得することができます。

お疲れ様でした。

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

Laravel + Oracle 12c 以前の名残と向き合うために

対象読者

  • 様々な事情で、DB Oracle バージョン12c以前を利用している
    • もしくは、12c以前の名残が残っており、SEQUENCEオブジェクトから取得した番号をIDカラムに指定してINSERTしている
  • Laravelを利用している

環境

  • Oracle 12c
  • Laravel 5.4

結論

yajra/laravel-oci8 パッケージを利用して IDの採番を意識しなくても済むようにする

本題

Oracle 12c でIdentity Columnsの登場により、以前よりIDの自動採番を意識しなくて済むようになりました。

内部的にはSEQUENCEオブジェクトを生成して、 .NEXTVAL を取得してます。

CREATE TABLE HOGE (
    ID    NUMBER    GENERATED AS IDENTITY,
    ..

もしくは、12c以降だと カラムのDEFAULT値にSEQUENCEを指定できるようにもなっています。

CREATE TABLE HOGE (
    ID    NUMBER    DEFAULT HOGE_SQ.NEXTVAL  NOT NULL,
    ..

これから新規に作成するテーブルは、これに準じて作成すればいいと思うんですが、

なかなか既存のテーブルの変更に踏み込めていなかったりするわけです。

12c以前の名残で、SEQUENCEオブジェクトから .NEXTVAL で次の番号を取得して INSERT の際に ID に指定しているというサービスも少なくはないと思います。

CREATE TABLE HOGE (
    ID    NUMBER    NOT NULL, -- IDENTITY も DEFAULT SEQUENCE も使えていない名残
INSERT INTO HOGE (ID, ..) VALUES ({SEQUENCEオブジェクトから .NEXTVAL で取得した値}, ..)

データを登録する度に、アプリ側のコードでIDを考慮してSEQUENCEオブジェクトから番号を取得して、IDに埋め込んで..

というのは、手間で、なるべくサービスのアプリケーションコードでは気にしたくないですよね。

  

幸いにも、Laravelを利用していると、 yajra/laravel-oci8 パッケージというものが見つかりました ?

使い方は簡単で、モデルクラスに $sequence メンバ変数を定義するだけです。

class Hoge extends Model -- Illuminate\Database\Eloquent\Model
{
    public $sequence = 'HOGE_SQ';
    ..

参考: https://yajrabox.com/docs/laravel-oci8/master/sequence

これを定義してあげると、わざわざIDに埋め込む必要もなくなります。

ただし、もともとIDを指定してデータを作成しているようなアプリケーションコードがあれば注意が必要です!

Hoge::create(['ID' => 1, 'NAME' => 'hoge']);

例えば、上記のようなコードがもともと存在していたとすると、 ORA-01036: illegal variable name/number エラーが発生します。

原因は、Oracleの RETURNING句INTO句 です。

内部的に実行されるクエリを確認してみましょう。

DB::enableQueryLog();
// 実行されるクエリを確認したいコード
Hoge::create(['ID' => 1, 'NAME' => 'hoge']);
dd(DB::getQueryLog());
insert into "HOGE" ("ID", "NAME") values (?, ?) returning "ID" into ?
// bindされる値は順に、 1, hoge, {.NEXTVALから採番された値} 

Oracleの RETURNING句INTO句 を組み合わせることで、

INSERTによって影響を受けたレコードに対しての評価(IDは {.NEXTVALから採番された値} ですよね? という評価)をしています。

結果、 1{.NEXTVALから採番された値} が異なるのでエラーとなってしまいます。

もし、 $sequence メンバ変数を定義する際には、既存コードの影響を確認した上で変更を加えてください。

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

【Laravel7】PHPUnitの設定をしてテストを実行してみる

はじめに

プロジェクトを作成するたびにPHPUnitの設定方法忘れて都度調べてるので、忘備録として残して忘れないようにしておきたいと思います。
PHPUnitの設定方法は何通りかあるかと思いますが、この記事では SQLiteのインメモリ機能 を用いて開発用のDBを汚さない設定方法を使用します。

実行環境

今回PHPUnitの設定は下記のバージョンで行います。厳密に同じ環境でなくてもメジャーバージョンがあっていれば同様の設定で動くかと思います。

言語・ツール バージョン
Laravel 7.10.3
PHP 7.2.26
PHPUnit 8.5.4

設定方法

PHPerからするとMySQLと比較してあまり馴染みがないかもしれませんが、本記事ではテスト用DBにSQLiteを使用します。

SQLiteとは?
SQLiteはオープンソースのリレーショナルデータベースです。主に以下の特徴を持っています。

・サーバ側に設置する必要がなく、アプリケーションに組み込みこんで使用することができる
・動作が軽量
・データ量の少ないデータを扱うのに適している
・ローカルのファイルを直接データベースとして扱うので、複数のサーバとネットワーク越しに同時接続するのには不向き

インメモリ機能とは?
インメモリ機能(インメモリデータベース)とはデータストレージを主にメインメモリ上で管理する、データベースシステムです。今回の場合ですと、PHPUnitでテスト実行時にメモリにデータベースが作成されて、テスト終了時にデータベースは存在しなくなります。そのため開発用のデータベースを汚さずにテストを実行することができます。

設定
それでは実際に、設定をしていきましょう。

まずはデータベースの接続設定をconfig/datbase.phpに追加します。databaseの項目に:memory:を指定している点がポイントです。こうすることによりデータベースのメインメモリ機能を使用することができます。

database.php
'connections' => [
  ....
  ....
  'testing' => [
    'driver'   => 'sqlite',
    'database' => ':memory:', // SQLiteのインメモリ機能を使用
    'prefix'   => '',
    'options'  => [
      PDO::ATTR_PERSISTENT => false,
    ],
  ],
],

次にPHPUnitの設定を行います。<server>にて環境変数が指定されるので、DB_CONECTIONを追加して、値を上記のconfig/database.phpで設定したキーtestingを使用します。

phpunit.xml
  <php>
    <server name="APP_ENV" value="testing"/>
    <server name="BCRYPT_ROUNDS" value="4"/>
    <server name="CACHE_DRIVER" value="array"/>
    <server name="DB_CONNECTION" value="sqlite"/>
    <server name="DB_DATABASE" value=":memory:"/>
    <server name="MAIL_MAILER" value="array"/>
    <server name="QUEUE_CONNECTION" value="sync"/>
    <server name="SESSION_DRIVER" value="array"/>
    <server name="TELESCOPE_ENABLED" value="false"/>
    <server name="DB_CONNECTION" value="testing"/>  // この行を追加
  </php>

テストの実行
Laravelのプロジェクトを作成した際にデフォルトで用意されているExampleTest.phpを実行してテストが通るか確認します。テスト結果が下記のようにOKとなれば正しくPHPUnitの設定ができているかと思います。

$ vendor/bin/phpunit tests/Feature/ExampleTest.php
PHPUnit 8.5.4 by Sebastian Bergmann and contributors.
.                                                                   1 / 1 (100%)
Time: 1.85 seconds, Memory: 18.00 MB
OK (1 test, 1 assertion)
おまけ

テスト実行時にvendor/bin/phpunitと毎回入力するのは煩わしいかと思うので、その際は下記のように、コマンドをエイリアスを設定してあげるといいかと思います。こうすることで、puとコマンドを叩いてあげることで、画面がリフレッシュされた後でテストを実行することができます。

$ alias="clear && vendor/bin/phpunit"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

laravel apache サブディレクトリで動かす場合

サブディレクトリでlaravelプロジェクトを運用する場合、
以下のようなエラー

Internal Server Error
The server encountered an internal error or misconfiguration and was unable to complete your request.

Please contact the server administrator at devel@vue.jp to inform them of the time this error occurred, and the actions you performed just before this error.

More information about this error may be available in the server error log.

対応方法

以下追加

prj_dir/public/.htaccess

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

Docker×Laravel×Vue (Laravel部分)

こちら側ではタイトルにあるように

Docker×Laravel×Vue 開発における
(Laravel部分)のお話をしていきます。

SPA認証

Laravel Airlock -> Sanctumに改名!!

https://stackoverflow.com/questions/54675520/composer-update-the-requested-php-extension-ext-http-missing

これでは実装できなかったので諦めてLaravelのweb版で対応することにしました。

マルチ認証

この記事に死ぬほどお世話になりました!!
https://qiita.com/namizatop/items/5d56d96d4c255a0e3a87?utm_campaign=popular_items&utm_medium=feed&utm_source=popular_items#controller

RouteServiceProviderの値でlogin後の挙動が変わることは初知りでした!!

Auth::routeって??

middlewareの認証されてるかされてないか

blade.php
@guest('admin')
//認証されていない場合表示させるもの!!
@endguest
blade.php
@auth('admin')
 //認証されてる場合表示させるもの 
@endauth

でadminのmiddleware側の認証の有無が確認できます。

https://readouble.com/laravel/5.5/ja/blade.html

Middleware

https://tech-blog.optim.co.jp/entry/2019/08/13/173000

Class消してるのに Cannot declare class Illuminate\Support\Facades\App\User, because the name is already in use が出てくる

composer dump-autoload

で対応!!

https://qiita.com/WebSysRider/items/e41f211f8c913e008d03

バリデーション

Requestクラス説明
https://www.ritolab.com/entry/41

https://www.ritolab.com/entry/40#aj_3

ルーティングで名前指定した時にどうやって値渡す??

{{ route('user.profile', ['id' => 1]) }}

こんな感じで第一引数にルーティング名指定して上げて、第二引数で連想配列で値を渡してあげるみたいです!!
これでパラメータ指定してURL渡せそうです!!

https://qiita.com/kazuhei/items/935257b0d72fa314d461

フォームリクエスト

Controller内にValidationロジックを書くのは保守運用上あまり良くない。
→Requestクラス内に切り出してあげる。

その際に出た
This action is unauthorized

    public function authorize()
    {
        return false;
    }

    public function authorize()
    {
        return true;
    }

に変更することで対応完了!!

https://nekorokkekun.hatenablog.com/entry/2019/07/13/223834

登録画面

 // 入力画面post時
    public function postIndex()
    {
        // postデータ取得
        $data = Request::all();

        // エラーチェックなどの処理

        // 確認画面へリダイレクト
        return redirect('/form/confirm')->withInput();
    }

    // 確認画面
    public function getConfirm()
    {
        // post内容を取得
        $postdata = Session::get('_old_input');

        return view('form.confirm', compact('postdata'));
    }

http://cly7796.net/wp/php/take-over-the-value-when-you-redirect-in-laravel-of-form/

$validationCode = request()->session()->get('validation_code', '');

組み合わせで前ページの値持ってける。

Routing書くの面倒なんでグルーピング

middleware ・・・ ミドルウェアでグループ化し適用する
namespace ・・・ ネームスペースでグループ化
domain ・・・ サブドメインをグループ化
prefix ・・・ URLが始まる文字列でグループ化
name ・・・ ルーティング名でグループ化

ブログから拝借しましたが→これで対応できそうです。

https://blog.capilano-fw.com/?p=556

エラー関係

TOO Large

自分の場合localのphp.iniの設定ファイルと同じようにdocker内のphpが読みこまれていたのでローカルの
php.iniを変更して対応しました。

https://qiita.com/kangyoosam/items/609b6eb1a262dee9f547

ファイルアップロードが失敗する場合

https://qiita.com/sano1202/items/b2babd55a6b11109de9a

Laravel ファイルがアップロードできない

まじか!!!!

調子に乗ってupload_max_filesizeを1024Mにしたのが原因でした。。。

アップロードできるサイズに直接関係するのは「upload_max_filesize」と「memory_limit」です。
「memory_limit」は「upload_max_filesize」よりも大きい値を設定するようにします。

upload_max_filesize > memory_limit になったらエラーになる!!

https://gray-code.com/php/setting-for-file-upload-by-phpini/

シンボリックリンク

php artisan strage:link

で作ることができるリンク
publicからファイルを取得するのに使うんやけど
アップロードされたファイルは基本Storageに入ってくるからここにアクセスするためのコマンド。

storage/ap/public
から触れる。

src ="{{asset('/storage/company/description/first/'.$companyDescription["img_1"])}}"

みたいに自分はアクセスした。

認可

自分が投稿した記事しか編集できないようにする。
https://tech.windii.jp/backend/laravel/authorization-basic

画面

Flash Message

https://qiita.com/usaginooheso/items/6a99e565f16de2f9ddf7

ページネーション

なんとか対応完了

https://qiita.com/rorensu2236/items/abf5706f56a124e50640

Eloquentの検索結果がからかどうか判定するメソッド

if($blogs->isEmpty()){
//なんらかの処理
}

if($blogs->first()){
//なんらかの処理
}

403 This Action is unauthorized

スクリーンショット 2020-05-12 21.01.16.png

この画面になった時は大概 Requestクラス内の

  public function authorize()
    {
        return true;
    }

になっていない可能性を疑う。
全部自分の場合はこれでした。

https://hiroslog.com/post/173

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

Laravelでログ出力を行う

前提条件

eclipseでLaravel開発環境を構築する。デバッグでブレークポイントをつけて止める。(WindowsもVagrantもdockerも)
本記事は上記が完了している前提で書かれています
プロジェクトの作成もapacheの設定も上記で行っています

Controllerにメソッド追加

(1) /sample/app/Http/Controllers/SampleController.phpに下記を追記
use Illuminate\Support\Facades\Log;

(2) /sample/app/Http/Controllers/SampleController.phpにlogメソッドを追記

    public function log(Request $request)
    {
        Log::emergency('ログサンプル', ['memo' => 'sample1']);
        Log::alert('ログサンプル', ['memo' => 'sample1']);
        Log::critical('ログサンプル', ['memo' => 'sample1']);
        Log::error('ログサンプル', ['memo' => 'sample1']);
        Log::warning('ログサンプル', ['memo' => 'sample1']);
        Log::notice('ログサンプル', ['memo' => 'sample1']);
        Log::info('ログサンプル', ['memo' => 'sample1']);
        Log::debug('ログサンプル', ['memo' => 'sample1']);

        return view('sample.log');
    }

Logクラスのログ出力メソッド名はログレベルに相当します
emergencyは最も重要度が高く、debugは最も重要度が低いです

(2) /sample/routes/web.phpに下記を追記
Route::get('sample/log', 'SampleController@log');

viewの作成

/sample/resources/views/sample/log.blade.phpファイル作成

log.blade.php
<html>
    <head>
        <title>sample</title>
    </head>
    <body>
        <div>log</div>
    </body>
</html>

logの設定

(1) /sample/config/logging.phpファイル修正
logging.phpは配列を返すファイルになっています
default要素の値が使用するログチャンネルとなります
ログ出力の際には、どこ(ファイル、DB、メール等)に何(ファイル名、行番号、リクエストURL等)を、どのように(日時の形式等)出力するかを決めなければなりません
どこ、何、どのようにの組み合わせを定義したものをログチャンネルと呼ぶと考えて差し支えないと思います
default要素はenv('LOG_CHANNEL', 'stack')となっていると思います。つまりstackチャンネルを使用します

stackチャンネルはどのような定義になっているか見ていきます

logging.phpファイルのchannels要素に様々なログチャンネルが定義されています
'channels'=>'stack'要素がstackチャンネルです
'channels'=>'stack'要素(stackチャンネル)を修正します

    'channels' => [
        'stack' => [
            'driver' => 'stack',
            'ignore_exceptions' => false,
            'channels' => ['single', 'daily'],
            'name' => 'stack-channel',
        ],
     ‥‥

channels要素を修正し、name要素を追加しました
stackチャンネルというのは複数のログチャンネルを1つにまとめたものです。したがって、使用するログチャンネルを'channels'=>'stack'=>'channels'要素に書くだけです
今回はsingleチャンネルとdailyチャンネルを使用する設定にしました
name要素はチャンネル名です。お好きな名前を設定してください

では、'channels'=>'single'要素(singleチャンネル)を修正します

    'channels' => [
     ‥‥
        'single' => [
            'driver' => 'single',
            'path' => storage_path('logs/laravel.log'),
            'level' => 'debug',
            'tap' => [App\Logging\SampleTap::class],
        ],
     ‥‥

singleチャンネルは1つのファイルにログを出力するチャンネルです。
level要素はどのログレベル以上をログファイルに出力するかを設定しています。例えば、infoと設定した場合、infoより重要度の低いdebugはメソッドが呼ばれたとしてもログに出力されません
path要素でstorage/logs/laravel.logにログを出力する設定になっています。これでどこにログを出力するかは決まりました
次に、何を、どのように出力するかを決めます。それがtap要素です。tap要素にはSampleTapクラスを設定しました
これからSampleTapクラスを作成します

(2) Tapクラスの修正
/sample/app/Loggingフォルダ作成
/sample/app/Logging/SampleTap.phpファイル作成

SampleTap.php
<?php

namespace App\Logging;

use Monolog\Logger;
use Monolog\Formatter\LineFormatter;
use Monolog\Processor\HostnameProcessor;
use Monolog\Processor\WebProcessor;
use Monolog\Processor\IntrospectionProcessor;
use App\Logging\SampleProcessor;

class SampleTap
{
    const FORMAT = "[%datetime%] %level_name% %channel% %message% %extra.file% %extra.line% %extra.class% %extra.function% %extra.hostname% %extra.url% %extra.ip% %extra.http_method% %extra.server% %extra.referrer% %extra.custom% %context.memo%\n";

    public function __invoke($logger)
    {
        $hostnameProcessor = new HostnameProcessor();
        $webProcessor = new WebProcessor();
        $introspectionProcessor = new IntrospectionProcessor(Logger::DEBUG, ['Illuminate\\']);
        $sampleProcessor = new SampleProcessor();

        $lineFormatter = new LineFormatter(static::FORMAT);

        foreach ($logger->getHandlers() as $handler) {
            $handler->pushProcessor($webProcessor);
            $handler->pushProcessor($hostnameProcessor);
            $handler->pushProcessor($introspectionProcessor);
            $handler->pushProcessor($sampleProcessor);
            $handler->setFormatter($lineFormatter);
        }
    }
}

pushProcessorに渡しているのが何を出力するか
setFormatterに渡しているのがどのようにを出力するか
を定義しているクラスです

まずはProcessorを見ていきましょう
Laravelのログ出力はMonologを使用しています
したがってMonologのクラスを使用していきます
HostnameProcessor、WebProcessor、IntrospectionProcessorはMonologが提供してくれているクラスです
他にもProcessorクラスを提供してくれています。
Monologが提供しているProcessor
HostnameProcessorはホスト名を出力してくれます
WebProcessorはリクエストURI、リクエストメソッド、クライアントIP、サーバ名、リファラを出力してくれます
IntrospectionProcessorはファイル、行番号、クラス、関数名を出力してくれます
Monologが提供しているProcessorでは出力されない値をログに出力したい場合、自分でProcessorクラスを作成することになります
SampleProcessorクラスは今回自作したProcessorです。後述します

つぎにFormatterを見ていきましょう
LineFormatterはMonologが提供してくれているクラスです
他にもFormatterクラスを提供してくれています。
Monologが提供しているFormatter
LineFormatterはコンストラクタの第一引数に与えられた文字列に従ってフォーマットしてくれます
今回はSampleTapクラスのFORMAT定数を与えています
LineFormatterは%で囲われた文字列を下記の通りに置換します
%datetime%:ログ出力の日時
%level_name%:ログレベル。Log::error関数でログ出力した場合はerrorがログレベルです
%channel%:logging.phpファイルの'channels'=>'stack'=>'name'
%message%:ログ出力関数の第一引数。Log::error('ログサンプル', ['memo' => 'sample1']);とログ出力した場合、'ログサンプル'
%context.xxx%:ログ出力関数の第二引数。Log::error('ログサンプル', ['memo' => 'sample1']);とログ出力した場合、%context.memo%が'sample1'に置換される
%extra.xxx%:Processorクラスで出力する値。あとでSampleProcessorを作るときに説明します

Formatterを自作することもできますが、ほとんどの場合、Monologが提供しているFormatterで十分でしょう
もし、Formatterを自作する場合は、Monolog\Formatter\FormatterInterfaceをimplementsしたクラスを作ってください

(3) Processorクラスの修正
自作のProcessorクラスをつくります
/sample/app/Logging/SampleProcessor.phpファイル作成

SampleProcessor.php
namespace App\Logging;

use Monolog\Processor\ProcessorInterface;

class SampleProcessor implements ProcessorInterface
{
    public function __invoke(array $record)
    {
        $record['extra']['custom'] = 'sample_extra';
        return $record;
    }
}

$record['extra']['custom']に値を入れて返すと
フォーマット文字列内の%extra.custom%部分が$record['extra']['custom']の値に置換されます
第一要素を'extra'とするのはProcessorを使う上での決まりです
先ほど使用したMonologが提供しているProcessorが置換するのは下記です
HostnameProcessor:extra.hostname
WebProcessor:extra.url、extra.ip、extra.http_method、extra.server、extra.referrer
IntrospectionProcessor:extra.file、extra.line、extra.class、extra.function

(4) 再び/sample/config/logging.phpファイル修正
今度は、'channels'=>'daily'要素(dailyチャンネル)を修正します

    'channels' => [
     ‥‥
        'daily' => [
            'driver' => 'daily',
            'path' => storage_path('logs/laravel.log'),
            'level' => 'debug',
            'days' => 0,
            'tap' => [App\Logging\SampleTap::class],
        ],
     ‥‥

dailyチャンネルは日付毎に1つのファイルにログを出力するチャンネルです。
どこに出力するか:path要素でstorage_path('logs/laravel.log')となっている場合、laravel-YYYY-MM-DD.logというファイル名形式のファイルが日付ごとにできます
何を、どのように出力するか:先ほどsingleチャンネルに設定したのと同じSampleTapクラスを設定しました
days要素は最大何日分のログファイルを保存しておくかです。0の場合、制限無しです

動作確認

http://localhost/laravelSample/sample/log

storage/logs配下を見るとログファイルができており、ログが出力されています

他のログチャンネル

Laravelには他にもログチャンネルが用意されています
下記リンク先の利用可能なチャンネルドライバという箇所に使用できるログチャンネル一覧があります
Laravel ログ
また、config/logging.php内のchannels配列を見ても使えるログチャンネルを確認できます
これらのログチャンネルを使うためのlogging.phpでの設定は
vendor/laravel/framework/src/Illuminate/Log/LogManager.phpのcreateチャンネル名Driverという名前のメソッド(createSlackDriverとかcreateSyslogDriverとか)を見るとわかります。
それらのメソッドは$configを引数に取り、MonologのHandlerクラスのコンストラクタに$configの要素を渡しています
MonologのHandlerクラスのコンストラクタに渡している$configの要素をlogging.phpで定義すれば使うことができます
MonologのHandlerは下記で確認できます
MonologのHandler

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

Docker+LaravelでSNSログインをやってみた

目的

開発プロジェクトでSNSログインを行う場合、

  • SSLが必要になる事
  • ローカル専用ドメインでSSLがしたい

本番用のドメインでSSLを作成して、hosts等で本番ドメインをローカルホストにアクセスするように設定すれば良いが、それはそれで後々問題が起きそうなので、今回は

  • ローカル専用ドメインを用意
  • ローカル専用SSLを作成
  • Docker上で使用する

この条件で、開発環境の構築からLaravelでSNSログインの実装までを説明する。

前提

下記の環境で実装した。

  • ローカルはmacos
  • Dockerコンテナはnginx+phpfpm
  • DockerコンテナのnginxにローカルSSLを組み込む
  • ローカル専用ドメインはsnslogin.dev.cdeとする
  • コンテナはdocker-composeを使用する
  • UIはとりあえずbootstrapで行う

ローカルホスト側の設定

ローカル専用SSLはmkcertを使用する。

よってmacosのターミナルで

% brew install mkcert                 # mkcertをhomebrewからインストール
% mkcert --install                    # ローカルSSL初期設定
% mkcert localhost dev.cde *.dev.cde  # ローカルドメイン用のSSLを作成
% mkcert -cert-file ./snslogin_dev_cde.crt.pem \
    -key-file ./snslogin_dev_cde.key.pem snslogin.dev.cde # key,certファイルの作成

以上で、SSLの必要なファイルを作成した。
作成したファイルはSslディレクトリにコピーしておく。

nginxコンテナ

nginxコンテナの作成は下記の通りである。

services:

  nginx:
    image: nginx
    container_name: "snslogin-nginx"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./svr:/svr
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
      - ./Ssl/snslogin_dev_cde.crt.pem:/etc/nginx/conf.d/snslogin_dev_cde.cert.pem
      - ./Ssl/snslogin_dev_cde.key.pem:/etc/nginx/conf.d/snslogin_dev_cde.key.pem
      - ./weblog:/var/log/nginx
    depends_on:
      - phpfpm

  phpfpm:
    build: ./phpfpm

次にnginxコンテナ上のdefault.confにてSSLの設定を行う。

server {
    index index.php index.html;
    root /svr/app/public;

    listen       443 ssl;
    server_name  snslogin.dev.cde;

    ssl_certificate      /etc/nginx/conf.d/snslogin_dev_cde.cert.pem;
    ssl_certificate_key  /etc/nginx/conf.d/snslogin_dev_cde.key.pem;

    ssl_session_cache    shared:SSL:1m;
    ssl_session_timeout  5m;

    ssl_ciphers  HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers  on;

    〜以下省略〜

Laravelの設定

ここで、一旦コンテナを起動して、Laravelの設定を行う。
phpfpmに接続して、コマンドラインから

% laravel new app
% composer require laravel/socialite 
% composer require laravel/ui
% php artisan ui bootstrap
% php artisan ui bootstrap --auth
% npm install
% npm run dev

以上を行う。

Laravelのパッケージはlaravel/socialite、laravel/uiをインストールする。

UIはbootstrapを使用し、認証スカフォールドを生成する。

テーブル設定

laravel標準のusersを使用するが、SNSログインの場合、パスワードがNULLになるため、マイグレーションを変更しておく。

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password')->nullable(); // ここ。
            $table->rememberToken();
            $table->timestamps();
        });
    }

    〜以下省略〜

passwordをnullableしておく。
これが済めば、.envにDB接続情報を設定してから、マイグレーションを実行する。

% php artisan migrate

ルーティングとサービスの設定

まず、route/web.phpにルーティングの設定を行う。

Route::get('/login/{social}', 'Auth\LoginController@socialLogin')
    ->where('social', 'facebook|twitter');
Route::get('/login/{social}/callback', 'Auth\LoginController@handleProviderCallback')
    ->where('social', 'facebook|twitter');

SNSログインのURLとコールバックのURLを設定した。

次にサービスの設定をconfig/service.phpに記述する。

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Third Party Services
    |--------------------------------------------------------------------------
    |
    | This file is for storing the credentials for third party services such
    | as Mailgun, Postmark, AWS and more. This file provides the de facto
    | location for this type of information, allowing packages to have
    | a conventional file to locate the various service credentials.
    |
    */

    'mailgun' => [
        'domain' => env('MAILGUN_DOMAIN'),
        'secret' => env('MAILGUN_SECRET'),
        'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
    ],
〜中略〜

 // 以下を追加
    'facebook' => [
        'client_id' => env('FACEBOOK_API_ID'),
        'client_secret' => env('FACEBOOK_API_SECRET'),
        'redirect' => env('FACEBOOK_CALLBACKURL'),
    ],
];

とりあえず、facebookの設定のみを行った。

実際の値は、.envにて設定する。

コントローラとViewの編集

コントローラーは認証スカフォールドで作成されたLoginControllerを使用する。

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

// ここから
use Socialite; 
use App\User;
use Auth;
// ここまで追加

class LoginController extends Controller
{

〜中略〜
    // SNSログインのメソッド
    public function socialLogin($social)
    {
        return Socialite::driver($social)->redirect();
    }

    // SNSログインコールバックのメソッド
    public function handleProviderCallback($social)
    {
        $userSocial = Socialite::driver($social)->stateless()->user();
        $user = User::where(['email' => $userSocial->getEmail()])->first();

        if ($user) {
            Auth::login($user);
        } else {
            $newuser = new User;
            $newuser->name = $userSocial->getName();
            $newuser->email = $userSocial->getEmail();
            $newuser->save();

            Auth::login($newuser);
        }
        $uri = "/home";
        if (session()->has("login_after_url")) {   // ログイン後リダイレクト
            $uri = session("login_after_url");     
            session()->forget("login_after_url");
        }
        return redirect($uri);
    }
}

ここでは、SNSログインの開始とコールバックの処理を行う。

コールバック時にログインユーザがusersになければ、作成して、

Auth::login()にて強制ログインする。

SNSログイン開始前にセッション変数login_after_urlにログイン後のリダイレクト先URLを

設定しておくと、そのURLに遷移する。

Viewは同じく認証スカフォールドで作成されたresources/views/auth/login.blade.php

にSNSログインのリンクを記述する。

<div class="form-group row">
   <label for="name" class="col-sm-4 col-form-label text-md-right">
     Login With
   </label>
   <div class="col-md-6">
      <a href="{{ url('login/facebook')}}" 
        class="btn btn-social-icon btn-facebook">
        <i class="fa fa-facebook"></i>Facebookログイン
      </a>
   </div>
</div>

以上で、ローカル開発環境にSNSログインの組み込みは完了である。

実際に実行する前にSNS側のアプリ設定とログインコールバックのURLを設定すればOKである。

考察

意外と簡単にできたのである。

laravel/socialiteで他に何ができるかは未調査であるが、少なくともSNSログインは簡単にできたのである。

むしろdocker-compse.ymlやローカルSSLの方が大変だった。

作成したプロジェクトはGitHubにアップしたので、必要であれば参照していただきたい。

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

Laravel7.x ログインに独自項目追加

Laravel7.xでログイン時のreCAPTCHA認証を実装するために独自のバリデーション項目が必要になったのでその手順を紹介します。

1, LoginControllerのチェック

まず、> php artisan route:listをしてみると以下のような結果になると思います。
(環境によって変わる場合があります。

> php artisan route:list
+--------+----------+------------------------+------------------+------------------------------------------------------------------------+--------------+
| Domain | Method   | URI                    | Name             | Action                                                                 | Middleware   |
+--------+----------+------------------------+------------------+------------------------------------------------------------------------+--------------+
|        | GET|HEAD | api/user               |                  | Closure                                                                | api,auth:api |
|        | GET|HEAD | home                   | home             | App\Http\Controllers\HomeController@index                              | web,auth     |
|        | GET|HEAD | login                  | login            | App\Http\Controllers\Auth\LoginController@showLoginForm                | web,guest    |
|        | POST     | login                  |                  | App\Http\Controllers\Auth\LoginController@login                        | web,guest    |
|        | POST     | logout                 | logout           | App\Http\Controllers\Auth\LoginController@logout                       | web          |
|        | GET|HEAD | password/confirm       | password.confirm | App\Http\Controllers\Auth\ConfirmPasswordController@showConfirmForm    | web,auth     |
|        | POST     | password/confirm       |                  | App\Http\Controllers\Auth\ConfirmPasswordController@confirm            | web,auth     |
|        | POST     | password/email         | password.email   | App\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail  | web          |
|        | GET|HEAD | password/reset         | password.request | App\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm | web          |
|        | POST     | password/reset         | password.update  | App\Http\Controllers\Auth\ResetPasswordController@reset                | web          |
|        | GET|HEAD | password/reset/{token} | password.reset   | App\Http\Controllers\Auth\ResetPasswordController@showResetForm        | web          |
|        | GET|HEAD | register               | register         | App\Http\Controllers\Auth\RegisterController@showRegistrationForm      | web,guest    |
|        | POST     | register               |                  | App\Http\Controllers\Auth\RegisterController@register                  | web,guest    |
+--------+----------+------------------------+------------------+------------------------------------------------------------------------+--------------+

これにより、/loginにPOSTすると、Auth\LoginController@loginに処理が行くことがわかります。

さて、そのAuth\LoginController@loginを確認しようと思いますが、@loginなんてものはありません。
use Illuminate\Foundation\Auth\AuthenticatesUsers;で他のファイルから読み込まれています。

2, AuthenticatesUsersをチェック

use Illuminate\Foundation\Auth\AuthenticatesUsers;とはどこのファイルでしょうか?
正解は、vendor\laravel\ui\auth-backend\AuthenticatesUsers.phpです。

vendor\laravel\ui\auth-backend\AuthenticatesUsers.php
<?php

namespace Illuminate\Foundation\Auth;

use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;

trait AuthenticatesUsers
{
    use RedirectsUsers, ThrottlesLogins;

    /**
     * Show the application's login form.
     *
     * @return \Illuminate\View\View
     */
    public function showLoginForm()
    {
        return view('auth.login');
    }

    /**
     * Handle a login request to the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\Http\JsonResponse
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    public function login(Request $request)
    {
        $this->validateLogin($request);

        // If the class is using the ThrottlesLogins trait, we can automatically throttle
        // the login attempts for this application. We'll key this by the username and
        // the IP address of the client making these requests into this application.
        if (method_exists($this, 'hasTooManyLoginAttempts') &&
            $this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);

            return $this->sendLockoutResponse($request);
        }

        if ($this->attemptLogin($request)) {
            return $this->sendLoginResponse($request);
        }

        // If the login attempt was unsuccessful we will increment the number of attempts
        // to login and redirect the user back to the login form. Of course, when this
        // user surpasses their maximum number of attempts they will get locked out.
        $this->incrementLoginAttempts($request);

        return $this->sendFailedLoginResponse($request);
    }

    /**
     * Validate the user login request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return void
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    protected function validateLogin(Request $request)
    {
        $request->validate([
            $this->username() => 'required|string',
            'password' => 'required|string',
        ]);
    }

    /**
     * Attempt to log the user into the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return bool
     */
    protected function attemptLogin(Request $request)
    {
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')
        );
    }

    /**
     * Get the needed authorization credentials from the request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    protected function credentials(Request $request)
    {
        return $request->only($this->username(), 'password');
    }

    /**
     * Send the response after the user was authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    protected function sendLoginResponse(Request $request)
    {
        $request->session()->regenerate();

        $this->clearLoginAttempts($request);

        if ($response = $this->authenticated($request, $this->guard()->user())) {
            return $response;
        }

        return $request->wantsJson()
                    ? new Response('', 204)
                    : redirect()->intended($this->redirectPath());
    }

    /**
     * The user has been authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  mixed  $user
     * @return mixed
     */
    protected function authenticated(Request $request, $user)
    {
        //
    }

    /**
     * Get the failed login response instance.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Symfony\Component\HttpFoundation\Response
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    protected function sendFailedLoginResponse(Request $request)
    {
        throw ValidationException::withMessages([
            $this->username() => [trans('auth.failed')],
        ]);
    }

    /**
     * Get the login username to be used by the controller.
     *
     * @return string
     */
    public function username()
    {
        return 'email';
    }

    /**
     * Log the user out of the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function logout(Request $request)
    {
        $this->guard()->logout();

        $request->session()->invalidate();

        $request->session()->regenerateToken();

        if ($response = $this->loggedOut($request)) {
            return $response;
        }

        return $request->wantsJson()
            ? new Response('', 204)
            : redirect('/');
    }

    /**
     * The user has logged out of the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return mixed
     */
    protected function loggedOut(Request $request)
    {
        //
    }

    /**
     * Get the guard to be used during authentication.
     *
     * @return \Illuminate\Contracts\Auth\StatefulGuard
     */
    protected function guard()
    {
        return Auth::guard();
    }
}

$this->validateLogin($request);で、同ファイルのvalidateLoginが呼び出されています。

これをオーバーライドしてあげます。

3, LoginControllerへの追記

それではオーバーライドしていきます。
validateLoginの個所をコピペしたのちに、reCAPTCHAの部分を加えます。

app\Http\Controllers\Auth\LoginController
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = RouteServiceProvider::HOME;

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }

    protected function validateLogin(Request $request) //この関数をコピペ
    {
        $this->validate($request, [
            $this->username() => 'required|string',
            'password' => 'required|string',
            'g-recaptcha-response' => 'required|captcha' //この一行を追加
        ]);
    }
}

Requestに関しては、vendor\laravel\ui\auth-backend\AuthenticatesUsers.phpvalidateLoginの上部にコメントアウトで書かれているのでそれを以下のように直接書いていきます。

app\Http\Controllers\Auth\LoginController
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = RouteServiceProvider::HOME;

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }

    protected function validateLogin(\Illuminate\Http\Request $request) // Illuminate\Http\Requestに変更
    {
        $this->validate($request, [
            $this->username() => 'required|string',
            'password' => 'required|string',
            'g-recaptcha-response' => 'required|captcha'
        ]);
    }
}

まとめ

Laravelの処理を追うのって意外と楽しいですね。

Atom IDEこそ、最高のIDEである(関係なし

  • このエントリーをはてなブックマークに追加
  • 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を用いて導入

実施例

  • 下記にカラム追加に必要なマイグレーションファイルの作成コマンドを記載する。

    $ php artisan make:migration add_カラム名_column_to_テーブル名_table --table=テーブル名
    

具体例

  • 「users」テーブルに「flag」カラムを追加したい時のマイグレーションファイルの作成コマンドを下記に記載する。

    $ php artisan make:migration add_flag_column_to_users_table --table=users
    
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む