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

【Laravel】バージョン8.27から利用可能なafterメソッドでカラムを追加する

はじめに

Laravel8.27よりafterメソッドで既存のカラムのあとに複数のカラムを**追加できるようになりました。この記事では、afterメソッドでカラムを追加する方法についてまとめました。

前提

追加するテーブル構造

今回Userテーブルのカラム「name」のあとに新しくカラム「birth_year、birth_month、birth_day」を追加していきます。

+-------------------+-----------------+------+-----+---------+----------------+
| Field             | Type            | Null | Key | Default | Extra          |
+-------------------+-----------------+------+-----+---------+----------------+
| id                | bigint unsigned | NO   | PRI | NULL    | auto_increment |
| name              | varchar(255)    | NO   |     | NULL    |                |
| email             | varchar(255)    | NO   | UNI | NULL    |                |
| email_verified_at | timestamp       | YES  |     | NULL    |                |
| password          | varchar(255)    | NO   |     | NULL    |                |
| remember_token    | varchar(100)    | YES  |     | NULL    |                |
| created_at        | timestamp       | YES  |     | NULL    |                |
| updated_at        | timestamp       | YES  |     | NULL    |                |
+-------------------+-----------------+------+-----+---------+----------------

migrationファイルを追加する

$ php artisan make:migration add_birth_to_users_table --table=users

afterメソッドを使用してカラム追加

public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->after('name', function ($table) {
                $table->integer('birth_year');
                $table->integer('birth_month');
                $table->integer('birth_day');
            });
        });
    }

修正が終わったら、マイグレーション実行する。

$ php artisan migrate
Migrating: 2021_02_20_142718_add_birth_to_users_table
Migrated:  2021_02_20_142718_add_birth_to_users_table (342.99ms)

きちんとカラム追加されているか確認。

mysql> desc users;
+-------------------+-----------------+------+-----+---------+----------------+
| Field             | Type            | Null | Key | Default | Extra          |
+-------------------+-----------------+------+-----+---------+----------------+
| id                | bigint unsigned | NO   | PRI | NULL    | auto_increment |
| name              | varchar(255)    | NO   |     | NULL    |                |
| birth_year        | int             | NO   |     | NULL    |                |
| birth_month       | int             | NO   |     | NULL    |                |
| birth_day         | int             | NO   |     | NULL    |                |
| email             | varchar(255)    | NO   | UNI | NULL    |                |
| email_verified_at | timestamp       | YES  |     | NULL    |                |
| password          | varchar(255)    | NO   |     | NULL    |                |
| remember_token    | varchar(100)    | YES  |     | NULL    |                |
| created_at        | timestamp       | YES  |     | NULL    |                |
| updated_at        | timestamp       | YES  |     | NULL    |                |
+-------------------+-----------------+------+-----+---------+----------------
11 rows in set (0.01 sec)

上手く追加されていました!

おまけ: afterメソッド使用しないでカラム追加(従来のやり方)

public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->integer('birth_year')->after('name');
            $table->integer('birth_month')->after('birth_year');
            $table->integer('birth_day')->after('birth_month');
        });
    }

参考

Add Multiple Columns After a Column in Migrations

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

Laravel5.8 画像アップロード機能を仕組みから理解する

1.はじめに

ECサイトを開発中に画像アップロード機能について最高に詰まったのでこの記事にまとめます。
誰かのお役に立てたら幸いです。
なお、今回実装したのは
画像をストレージに保存してDBにその「パス」を保存する方法です。

対象読者
・画像アップロードに躓いている方
・Laravel初学者

注)
本記事に記載してあるコードは要点部分のみです。
本記事は画像アップロードの仕組みを理解するためのものとお考えください。

環境
・Laravel Framework 5.8.38
・PHP 7.2.34
・mysql Ver 14.14 Distrib 5.7.32
・phpmyadmin

2.実現したい内容

今回実装したかったのはECサイトにおける商品登録の段階で、
①フォームに画像データを添付して送信したい
②送ったデータをDBに保存したい
③保存された画像データをView(商品一覧)に表示させたい
という内容を実装したい。

つまりは
スクリーンショット 2021-02-20 15.24.13.png
このようなFormを作って、画像添付して登録ボタンを押すと

スクリーンショット 2021-02-20 15.29.15.png
データがデータベースに保存されるようにして

スクリーンショット 2021-02-20 15.31.22.png
保存したものをviewに表示させる。

この一連の流れを習得できるようにします。

3.体系的に理解する

まず前提知識を列挙していきます。

3.1.今回の画像アップロード機能概要

第一に、画像アップロード機能の構造的解釈で躓いたので記載しておきます。
「2.実現したい内容」で述べた手順(構成)がそもそも間違っていました。

実現したい内容を要約すると

①商品登録をする際に、商品情報を記載するフォーム画面に画像を添付して送信する
②その画像をDBに保存する
③商品一覧画面にその画像を表示させる

となりますが、正しくは下記の通りです

①商品登録をする際に、商品情報を記載するフォーム画面に画像を添付して送信する
②画像データ自体はサーバ(Laravel)に保存される
③DBには②で保存した位置情報(ファイルパス)のみを保存する
④商品一覧画面にその画像を表示させる

僕はこの赤文字部分を理解するのに時間がかかりました...
順に解説しますと
「①」はそのままです。

「②、③」に関して、「①」で送信したリクエストに画像データファイルパスの二つの情報を持たせます。そのうち画像データはサーバー(Laravel)内に保存します。ファイルパスのみをDBに保存します。

「④」は表示のさせ方が少しややこしいですが、すぐ理解できると思います。(後ほど記載します)

次に画像データはどこに保存されるか?について記載していきます

3.2.publicフォルダについて

前項で、画像データはサーバー(Laravel)内に保存すると書きましたが
その保存先が、publicディレクトリです。

実際のpublicディレクトリを見てみると...
スクリーンショット 2021-02-20 16.53.44.png

...!?
publicが二つある..!?

ここも理解に苦しみました。。解説します

Laravelにはpublicディレクトリが二つあります。
・public(appと同じ階層)(便宜上、「上のpublic」と呼ばせていただきます)
・storage/app/public   (便宜上、「下のpublic」と呼ばせていただきます)

3.2.1.上のpublicディレクトリ

アプリケーションに送られる、全てのリクエストのエントリーポイント(最初に実行される)となるindex.php ファイルがあるところ。
ここには他に画像、JS、CSSといったものを置いたりする。
https://qiita.com/shosho/items/93cbff79376c41c3a30b (参考サイト)
このpublicはブラウザからサーバーにアクセスした時には公開ディレクトリとなっていることが特徴(セキュリティ的に弱いディレクトリである)
つまりは公開ディレクトリなので、あまり情報はおきたくないところです。

3.2.2.下のpublicディレクトリ

まずstorageディレクトリについて
Bladeテンプレートをコンパイルしたものやセッションのファイル、キャッシュファイル、その他フレームワークが作り出したファイルなんかが置かれる。

storage以下の階層
ディレクトリは app framework logs の3つに分かれている。
このうちの「app」のなかに「public」は存在する。

storage/app/publicについて
storage/app/public についてはユーザーのプロフィールアバターといったいろんなとこで使いたくなるようなファイルを置く場所で、非公開ディレクトリ。
https://laracasts.com/series/whats-new-in-laravel-5-3/episodes/12 (参考サイト)

Laravel 5.3からアップロードファイルの格納が簡単にできるようになり、その置き場所となっているそう。
つまりは、あまり外部に漏らしたくないアップロードされた画像を格納しておくところぐらいの解釈で良いかなと思ってます。

3.2.3.publicまとめ

上のpublic = 公開ディレクトリ
下のpublic = 非公開ディレクトリ
いったんこれだけ抑えておけば事足りるかと思います

結論をいうと、
フォームから送信された画像データは「下のpublic」(非公開ディレクトリ)に格納します。
この二つの「public」のせいでかなり混乱しましたが、それぞれ存在意義があります。
というのも、僕らがブラウザでショッピングサイトを開いても、公開されているのは上のpublicであって、画像データがあるのは下のpublicです。
ではどうやって下のpublicから画像を引っ張ってきているのか..?
答えは次項のシンボリックリンクで

3.3.シンボリックリンクについて

最初この言葉を見た時、意味不明だったので簡単に記載します。

3.3.1シンボリックリンクとは??

ショートカットのようなものです。今回の例で言うと、laravelアプリの、上のpublicが、ウェブ上に公開されるディレクトリとなります。ブラウザからサーバー上のファイルにアクセスするときは、画像は /storage/app/public(下のpublic)の中にあるため、表示することができません。そこで、上のpublic/storage(後に、上のpublicのなかにstorageを作成します) と、下のstorage/app/public にリンクを持たせることにより、public/storageにアクセスする=storage/app/publicにアクセスする、ということを実現できます。(間違っていたらすみません)
https://qiita.com/si-ma/items/16565d925b0558cbba58 (参考サイト)

つまりは、シンボリックリンクとやらを行うと、公開されてる上のpublicから非公開である下のpublicにアクセスできるようになるということ(多分)
感覚的には上のpublicと下のpublicは全く別物ではなく「表と裏の存在」のようなイメージ。

3.3.2シンボリックリンクを張る

というわけでシンボリックリンクを張っていきます。

ターミナル上にて(artisanファイルがあるディレクトリで)

php artisan storage:link

を実行すると...
スクリーンショット 2021-02-20 17.37.12.png

上のpublicのなかにstorageファイルができました。
そしてその右側に「↩️」こんなマークができました。(リンクが張られているマークだと思います)
これで、下のpublicにアクセスが可能となったはずです。

3.4.画像データの保存先を理解する

ここまでで何度か記載しましたが、重要なので大項目で記載しておきます。
画像データの保存先は下のpublicとなります

3.5.DBへの保存について

3.5.1.保存する内容

前項でも書きましたがDBに保存するのは
下のpublicに保存した画像データのファイルパス
となります。

「下のpublic」 と 「DB」 に保存するデータ内容抑えておくと考えがまとまりやすいです。

※DBに画像データを保存するやり方もあります。ですが推奨はファイルパスのみをDBに保存する方法だそうです。
ーー番外編ーー
アップロード画像をDBに保存しないほうがいい理由
・レコードのデータ量が多くなり、クエリに時間がかかる
・WebとDBを分割しようと思った時に弊害がある
・DBのストレージ容量を圧迫する
・ネットワークを圧迫する
・メンテナンス性が低下する
・キャッシュしにくくなる
どこかのサイトで見ましたが、URLがわかりませんでした。。
ーーーーーーー

3.5.2.DBに保存するために

僕の場合画像アップロード機能をつけることは当初の設計に入っていなかったので、migrationファイルからいじる必要がありました。
つまりは、新しくファイルパスをいれるカラムが必要となったので作成しておかないといけません。
既にmigrationファイルを作成してしまっている人でも、すぐに追加することができます。

画像のファイルパスを入れたいmigrationファイルに以下のコードを追加してください

$table->string('product_image');  //カラム名は好きなものでOK

完成した僕のmigrationファイルです↓↓

//略
    public function up()
    {
        Schema::create('m_products', function (Blueprint $table) {
            $table->increments('id');
            $table->string('product_name', 64);
            $table->integer('category_id')->unsigned();
            $table->integer('price')->unsigned();
            $table->integer('sale_status_id')->unsigned();
            $table->integer('product_status_id')->unsigned();
            $table->string('description', 256);
            $table->string('product_image');          //←ここに入れました
            $table->timestamp('resist_date');
            $table->integer('user_id')->unsigned();
            $table->char('delete_flag', 1);
            $table->foreign('sale_status_id')->references('id')->on('m_sales_statuses')->onDelete('cascade');
            $table->foreign('product_status_id')->references('id')->on('m_products_statuses')->onDelete('cascade');
            $table->timestamps();
        });
    }
//略

これで、DBでのファイルパスの受け皿も完成しました。
では実装していきます。

4.実装

4.1.Form部分

{!! Form::open(['route' => 'back_product_store', 'enctype'=>'multipart/form-data']) !!}

//略

<div class="form-group-sm">
    {!! Form::label('image', '商品画像', ['class' => 'd-block mt-2 mb-0']) !!}
    <input type="file" name="product_image" value="" class="ml-3 mr-2 d-inline">
</div>

//略

{!! Form::close() !!}

※bootstrapとLaravelCollectiveを用いているので、少し書き方が独特な部分があります。

ポイント
①'enctype'=>'multipart/form-data'
ファイルのアップロードを行う場合は、enctype=”multipart/form-data”は忘れずにform要素に設定をしてください。

②type="file"
取り扱うデータはfileなのでtype属性はfileに設定します

③name="product_image"
送信するデータの名前です。なんでもいいです。
僕の場合は商品画像なのでproduct_imageとしました。

④'route' => 'back_product_store'
このフォームを送信するとback_product_storeという名前のルーティングにいきます。

4.2.Routing

今回の場合は、、

Route::post('product/store', 'BackProductController@store')->name('back_product_store');

ルーティング部分に関しては特に注意事項はないです。

このルーティングで
BackProductController の storeアクションにいきます。

4.3.Controller

    public function store(CreateProductRequest $request)
    {
        //バリデーションの記載
        $this->validate($request, CreateProductRequest::rules());
        $productImage = $request->product_image;
        if ($productImage) {

            //一意のファイル名を自動生成しつつ保存し、かつファイルパス($productImagePath)を生成
            //ここでstore()メソッドを使っているが、これは画像データをstorageに保存している
            $productImagePath = $productImage->store('public/uploads');
        } else {
            $productImagePath = "";
        }

        $user = Auth::user();
        if ($user->id) {
            $userId = $user->id;
        }
        //userIdとproductImageが存在すれば以下の項目をMProductテーブルに保存
        if ($userId && $productImage) {
            $data = [
                'product_name'      => $request->productName,
                'category_id'       => $request->categoryId,
                'price'             => $request->price,
                'sale_status_id'    => $request->saleStatusId,
                'product_status_id' => $request->productStatusId,
                'description'       => $request->description,
                'user_id'           => $userId,
                'resist_date'       => date('Y-m-d H:i:s'),
                //DBにはファイルパスを保存!!!!!!
                'product_image'     => $productImagePath,
                'delete_flag'       => '',
            ];
           //$dataをクリエイトする
           MProduct::create($data);
        }
    }

ポイント
①バリデーションについて

$this->validate($request, CreateProductRequest::rules());

今回はクラスを使ってるので少しややこしい書き方です、、
調べれば、これとは別に基本的な書き方がすぐ出てくると思うので割愛させていただきます。

ファイルパスの作成及び画像データの保存を1行で記載 ※超重要※

$productImagePath = $productImage->store('public/uploads');

この一文、超重要です。
全てがここに集約されていると言っても過言ではないくらい重要です。

解説)
・前提として、$productImageは、送信されてきた画像データです。
・まず$productImagePathという変数を作ります。
・右側にて、$productImageをstore()関数で保存します。
 保存場所は引数にある'public/uploads'です。
 つまりpublicディレクトリのなかのuploadsに保存します。
・「uploadsディレクトリ」は準備していませんでしたが、保存の際に自動的に作成されます。
・ここでのpublicはもちろん「下のpublicディレクトリ」です。
・この時保存されるファイル名はstore()関数によって乱数的に作成されます。
 (これによりプロフィール画像など、ファイル名が被って欲しくない時には有効です。ファイル名を固定したければstoreAs()関数を使います。)

そして
・作った変数$productImagePathの値として何が入るかというと、
 この変数名からお察しかと思いますが
 "保存された場所へのパス"がこの変数の値となります。(ここで感動しました)

コードのコメントアウトにあるように、このコード1行のみで
//一意のファイル名を自動生成しつつ保存し、かつファイルパス($productImagePath)を生成
//ここでstore()メソッドを使っているが、これは画像データをstorageに保存している
これだけのことを行っています。

③DBにファイルパスを保存

//DBにはファイルパスを保存!!!!!!
'product_image'     => $productImagePath,

少しカラム数が多くてわかりにくいですが、やってることで重要なのはこの部分です。
product_imageというカラムに $productImagePath (ファイルパス)を保存します
dd()で取り出してみるとわかりやすいので見てみます。

以下のコメントアウト部分でデバッグをかけてみます

BackProductController
    public function store(CreateProductRequest $request)
    {
        //バリデーションの記載
        $this->validate($request, CreateProductRequest::rules());
        $productImage = $request->product_image;
        if ($productImage) {

            //一意のファイル名を自動生成しつつ保存し、かつファイルパス($productImagePath)を生成
            //ここでstore()メソッドを使っているが、これは画像データをstorageに保存している
            $productImagePath = $productImage->store('public/uploads');

            dd($productImagePath);    //ここでデバッグをかけてみる

        } else {
            $productImagePath = "";
        }

結果がこちら

スクリーンショット 2021-02-20 22.30.10.png

このようなデータが取れました。
これはファイルパスなので画像データの位置情報です。

public/uploads/にある7qaJNxnb4cKml1aOwUWoOVUoHdaAUos1U55SkYU2.jpgという画像

という意味です(そのままですみません)

というわけでしっかりとファイルパスも設定できているのでこのままDBに保存してしまえば、この後viewで表示したい時にそのパスを頼りに、下のpublic(の中のuploads)から画像データを引っ張り出せるということになります。

4.4.View(画像を表示)

最後にサーバー(Laravel)に保存した画像データを、データベースに保存したファイルパスを使ってviewに表示させます。

表示に関しては冒頭で「少しややこしい」と言いましたが、すぐ理解できるはずです。

結論から書くとこうです。

<img class="product_image" src="{{ Storage::url($product->product_image) }}" alt="" width="150px" height="100px">

class=""とか、alt=""に関してはつけたい方は適当につけてください。
width=""やheight=""は割愛します。

大事な部分はここです。

src="{{ Storage::url($product->product_image) }}"

分けて考えます。
まずは
Storage::url() の部分ですが、
指定したファイルのURLを取得するには、urlメソッドを使います。
Storage::をつけてurl()メソッドを使用することで、その引数のファイルパスを取得することができます。(言い回しがあってるかわかりませんが)

詳しくはリファレンスに記載があります。
https://readouble.com/laravel/5.5/ja/filesystem.html

次に
$product->product_image の部分ですが

$productは僕が設定した商品データの変数です。
なので
$product->product_image と記載することで
その商品のファイルパスを表しています。

つまり、最初のコードの src は「ある商品のファイルパス」を取得していたことになるので、その商品にあった場所に適した画像を表示できるわけです。

というわけで、実際のView画像である

スクリーンショット 2021-02-20 15.31.22.png

こちらにたどり着きます。

画像アップロードの解説は以上となります。

5.まとめ

何がどこに保存されているかわからなくて、大いに躓いたので記事にしました。
データの動きをみることがすごく大切だと痛感しました。。

僕なりの解釈でこの記事を書きましたが、不備などありましたらご指摘頂けると幸いです。
以下参考にさせていただいたサイトです。

参考サイト一覧
https://qiita.com/ryo-program/items/35bbe8fc3c5da1993366
https://note.com/akina7/n/ne9af79fea62e
https://reffect.co.jp/laravel/how_to_upload_file_in_laravel#i-13
https://qiita.com/koru1893/items/1d2f522e20744b03e3ad#%E5%BE%8C%E3%81%AF%E7%A7%BB%E5%8B%95%E3%81%95%E3%81%9B%E3%81%A6db%E3%81%B8%E4%BF%9D%E5%AD%98
https://laraweb.net/tutorial/2707/
https://note.com/laravelstudy/n/n038bd68f53a7#nRJwi
https://qiita.com/u-dai/items/8a904cc7fd2795c0e70d
https://biz.addisteria.com/image-upload/
https://qiita.com/shosho/items/93cbff79376c41c3a30b
https://qiita.com/si-ma/items/16565d925b0558cbba58
https://reffect.co.jp/laravel/laravel-storage-manipulation-master#visibility

最後まで読んでいただき、ありがとうございました。

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

クライアントがアプリの一部機能にドメインを割り当てる際に自動でSSL化する方法

概要

アプリケーションの一部機能、カート機能や予約機能をクライアントのページで作成する時に、ドメインを指定したいという要望に対してHTTPS接続なので、ACM証明書をこちらで追加する必要があります。今回は、その部分を自動化を行います。

使用技術

・AWS ACM
 https://aws.amazon.com/jp/certificate-manager/
・AWS SDK for PHP
 https://docs.aws.amazon.com/aws-sdk-php/v3/api/index.html

作業手順

  1. 特定の機能のURLにクライアント固有のidなど含めます。
  2. クライアントが、【使用したいドメイン】をフォームに入力を行う。(AWS SDKを使用し入力されたドメインをACMに追加 レスポンスに含まれる「エントリー名」「値」を表示)
  3. クライアントが、DNSサービスプロバイダで作成した【使用したいドメイン】にCNAMEで「1.のURL」 「エントリー名」 「値」を登録

上記の3ステップでクライアント側の操作のみで、SSL化されたドメインを割り当てれるようになります。

AWS SDK for PHPを使用しACM登録処理,「エントリー名」 「値」を確認する

参考コード(for_laravel)

use App\Certification;
use Aws\Acm\AcmClient;

public function createACM(Request $request)
{
    $client = new AcmClient([
                    'version'     => 'latest',
                    'region'      => 'ap-northeast-2',
                    'credentials' => [
                    'key'    => config('aws.key'),
                    'secret' => config('aws.secret_key'),
                ],
            ]);

  //ACM登録
  $result = $client->requestCertificate([
            'DomainName' => $request->url;,
            'Tags' => [
                [
                     'Key' => '<string>', // REQUIRED
                     'Value' => '<string>',
                ],
            ],
            'ValidationMethod' => 'DNS',
        ]);

   //こちらで、ACMの情報を呼び出します。
   $resource_name = $result['CertificateArn'],

  $results = $client->describeCertificate([
       'CertificateArn' => $resource_name
  ]);

    $エントリー名 = $results['Certificate']['DomainValidationOptions'][0]["ResourceRecord"]['Name'];
    $値        = $results['Certificate']['DomainValidationOptions'][0]["ResourceRecord"]['Value'];
}

参考URL
https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-acm-2015-12-08.html#requestcertificate

あとがき

半年ほど前に実装した機能で、上手く説明出来なかったのですが記事にして理解できました。
クラウドフロントのキャッシュ削除なんかも下記サイトをみて実装しました。めっちゃ便利です!
・AWS SDK for PHP
 https://docs.aws.amazon.com/aws-sdk-php/v3/api/index.html

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

Laravel初期設定について【個人用メモ】

1.言語や時刻の設定

言語や時刻の設定は config/app.php で設定いたします。

config/app.php
'timezone' => 'Asia/Tokyo',
'locale' => 'ja',
'fallback_locale' => 'ja',
'faker_locale' => 'ja_JP',

2.DBの設定

①デフォルトでは以下の設定になっています。

.env
DB_CONNECTION=mysql // どのRDBMSを利用するか?
DB_HOST=127.0.0.1 // HOST名
DB_PORT=3306 //PORT番号
DB_DATABASE=homestead // DB名
DB_USERNAME=homestead // DBのユーザー名
DB_PASSWORD=secret // DBのパスワード

設定を変えにDBを作成するなら

DB名:作成したDB名
アクセスユーザー名:homesteadなど
アクセスユーザー名のパスワード:secretなど

この設定でDBとユーザーアカウントを作成します。

②冒頭にある'default'の第二引数を使用したいデータベース名に設定すればOK。sqliteならsqlite、MySQLならmysql。

config/database.php
'default' => env('DB_CONNECTION', データベース名),

DB文字コード変更を下記の通り変更する。

config/database.php
// 変更前
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',

// 変更後
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',

3.エラーメッセージの日本語化

メッセージの日本語ファイルはデフォルトでは提供されていないので、
手順としては以下の通りです。

①laravel/resources/lang/en ディレクトリを丸ごとコピーする
②コピーしたディレクトリ名を ja へ変更する。
③jaディレクトリ内のvalidation.phpに記述されている英文を日本語に置き換える
※エラーメッセージを格納するファイルは validation.php になります。

最終的には以下のようなディレクトリ構成になります。

laravel
├── resources
│   ├── lang
│   │   ├── en
│   │   │   ├── auth.php
│   │   │   ├── pagination.php
│   │   │   ├── passwords.php
│   │   │   └── validation.php
│   │   └── ja
│   │       ├── auth.php
│   │       ├── pagination.php
│   │       ├── passwords.php
│   │       └── validation.php

日本語化参考URL
https://readouble.com/laravel/5.8/ja/validation-php.html?header=%25E8%25AA%25AC%25E6%2598%258E

①アプリケーションのロケール設定

'locale' => 'ja',

アプリケーションロケールによって、翻訳サービスプロバイダによって使用されるデフォルトのロケールが決定されます。 この値は、アプリケーションがサポートするロケールのいずれかに自由に設定できます。

②アプリケーションフォールバックロケール

'fallback_locale' => 'en',

フォールバックロケールは、現在のロケールが使用できない場合に使用するロケールを決定します。 アプリケーションによって提供される任意の言語フォルダに対応するように値を変更することができます。

エラーメッセージ内の「:attribute」と記載されている部分は、検証フォームのname設定に置き換えられます。

name設定部分も日本語に置き換えたい場合、上記日本語リソースファイル(validation.php)の106行目にあるattributes部分に追加をおこないます。

例えば、name設定が「title」と設定されており、その部分を「タイトル」と日本語化したい場合は、以下のように記述をおこないます。

    'date' => ':attributeを有効な日付形式にしてください。',
    'min' => [
        'string'  => ':attributeを:min文字以上にしてください。',
    ],
    'required' => ':attributeを入力してください。',

    // ...

    /*
    |--------------------------------------------------------------------------
    | Custom Validation Attributes
    |--------------------------------------------------------------------------
    */

    'attributes' => [
        'title' => 'タイトル',
        'body' => '本文',
        'published_at' => '公開日',
    ],
];

4.head内の設定【BootStrapなど】

BootStrap(CDNでの方法)

①レスポンシブ用メタタグの記述。
②CSS を < head >タグ 内側に他のスタイルシートよりも先に入れてください。
③いくつかのコンポーネントで jQuery, Popper.js などの JavaScript プラグインが必要です。< script > を </ body> の直前に順番通りに入れてください。

resources/views/layout.blade.php
<!doctype html>
<html lang="ja">
  <head>
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
....
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
....
  </head>
  <body>
....

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
  </body>
</html>

公式リファレンス
https://getbootstrap.jp/docs/4.5/getting-started/introduction/#%E3%81%AF%E3%81%98%E3%82%81%E3%82%8B

ページネーションをBootstrap 4 スタイルにしたい。
bootstrap/app.php の末尾の return の手前あたりに以下を追記する。

bootstrap/app.php
Illuminate\Pagination\AbstractPaginator::defaultView("pagination::bootstrap-4");
jQuery サイトの CDN
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
Font AwesomeのCDN
<link href="https://use.fontawesome.com/releases/v5.6.1/css/all.css" rel="stylesheet">
Google font
<link rel = "preconnect" href = "https://fonts.gstatic.com">
<link href = "https://fonts.googleapis.com/css2?family = Noto + Sans + JP& display = swap" rel = "stylesheet">

Googleフォント
https://fonts.google.com/?subset=japanese

5.キャッシュファイルの作成

①binフォルダの作成
②clear-laravel.shファイルの作成
③clear-laravel.shファイル内の記述

laravel
├── bin
│   ├── clear-laravel.sh

#!/bin/bash
php artisan view:clear
php artisan cache:clear
php artisan config:clear
php artisan route:clear
php artisan clear-compiled
php artisan config:cache
composer dump-autoload
php artisan ide-helper:generate
php artisan ide-helper:models -N
php artisan ide-helper:meta
find . -name '._.DS_Store' -type f -ls -delete

設定が完了したら、下記を叩く。

$ bin/clear-laravel.sh
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

バリデーションをControllerではなくFormRequestに記述

概要

今までバリデーションはControllerに記述していたのですが、分離させた方が読みやすくなると思って調べてみたら、FormRequestというものが使えるらしいので、実装してみました。

実装

公式サイトを参考に実装してみました。
公式サイト

改善前

AccountController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests\AccountRequest;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rule;


class AccountController extends Controller
{
    public function update(Request $request)
    {
        $request->validate([
            'email' => ['required', 'string', 'email', 'max:255', 
            Rule::unique('users')->ignore($request->id)],
            'password' => ['required', 'string', 'min:8'],
        ]); 
        DB::beginTransaction();
        try{
            $user = User::find($request->id);
            $user->email = $request->email;
            $user->password = Hash::make($request->password);
            $user->save();  

            DB::commit();
        }catch(\Exception $e){
            DB::rollBack();
        }

    }
}

改善後

php artisan make:request AccountRequest
AccountController.php
<?php

namespace App\Http\Controllers;

use App\Http\Requests\AccountRequest;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;

class AccountController extends Controller
{
    public function index($login_id){
        $user = User::find($login_id);
        return $user;
    }
    public function update(AccountRequest $request) //←ここ変更
    {
        DB::beginTransaction();
        try{
            $user = User::find($request->id);
            $user->email = $request->email;
            $user->password = Hash::make($request->password);
            $user->save();  

            DB::commit();
        }catch(\Exception $e){
            DB::rollBack();
        }
    }
}
AccountRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Http\Request;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class AccountRequest 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(Request $request)
    {
        return [
            'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users')->ignore($request->id)],
            'password' => ['required', 'string', 'min:8'],
        ];
    }
    public function attributes()
    {
        return [
            'email' => 'メールアドレス',
            'password' => 'パスワード'
        ];
    }
}

まとめ

分けた方が管理しやすいし便利ですね。

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

LaravelでFullcalendarに登録したイベントを削除する方法

はじめに

こちらは以下の記事の続きになります。
LaravelでFullcalendarを実装する方
LaravelでFullcalendarに登録した内容を更新する方法
ディレクトリ名等も同じものを使用しているので、ご了承くださいませ。

前回までにFullcalendarにイベントの登録と更新はできるようになったので、今回は削除方法について見ていきたいと思います。

-各バージョン
-Laravel 6.x
-PHP 7.4.9
-MySQL 5.7.30
-Fullcalendar v5

削除ボタンを作成する

前回までに更新用のモーダルを開くと、登録されている情報が確認できているようにしました。
今回は更新用のモーダルに削除ボタンを追加し、このボタンを押すとデータが削除されるようにします。

modal.blade.php
<div class="modal micromodal-slide" id="modal-1" aria-hidden="true">
    <div class="modal__overlay" tabindex="-1" data-micromodal-close>
        <div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-1-title">
            <header class="modal__header">
                <h2>Editing my task list</h2>
                <button class="modal__close" aria-label="Close modal" data-micromodal-close></button>
            </header>
            <main>
         <form method="POST" action="{{ route('editEvent') }}">
                 @csrf
                 <input type="hidden" id="id" value="" name="id">
           <input type="text" id="edit_title" name="title" value="">
           <input type="date" id="edit_start" name="start" value="">
           <input type="color" id="edit_color" name="textColor" value="">
                  <button class="modal__btn modal__btn-primary" type="submit">変更する</button>
          </form>
        // ここから追加
              <form id="delete-event-btn" method="POST" action="{{ route('deleteEvent') }}">
                    @csrf
                    <input type="hidden" name="id">
                    <a href="#" id="delete-event" class="cancel-btn">削除する</a>
                </form> 
        // ここまで
            </main>
        </div>
    </div>
</div>

前回と同様、カレンダーのイベントを表示させるアクションであるindexアクションではAjaxで通信時のみidを渡すようになっているので(LaravelでFullcalendarを実装する方法)、idはJSで取得するようにします。

更新時にvalueの値を取得するコードを書いているので、こちらに追加していきます。

event.blade.php
<script>
  $(document).ready(function () {
    $('#calendar').fullCalendar({
      // はじめりの曜日を月曜日に変更 デフォルトは日曜日になっており、日=0,月=1になる
      firstDay: 1,
      headerToolbar: {
                     right: 'prev,next'
                     },
      events: '/index',

    eventClick: function(info){
        document.getElementById("id").value = info.id;
        document.getElementById("edit_title").value = info.title
        document.getElementById("edit_start").value = info.start._i
        document.getElementById("edit_color").value = info.textColor
        MicroModal.show('modal-1');
     }
    });

    // ここから追加
    $("#delete-event").on("click", function() {
     var form = document.getElementById("delete-event-btn");
     var eventId = document.getElementById("id").value; // 削除対象のidを前回作成した更新フォームから取得
     form.elements['id'].value = eventId; // 削除対象のidを削除フォームにもセット
     form.submit(); // 削除フォームを送信させる
     });
  });
</script>

コントローラー、ビューを作成する

先ほど削除ボタンのフォームで指定したactionを指定していきます。

web.php
Route::post('/deleteEvent', 'EventController@deleteEvent')->name('deleteEvent');

最後にコントローラーです。

EventController
public function deleteTask(Request $request) {
  // 送信されてきたidをEventテーブルに登録されているデータと紐付ける
  $task = DoneTask::find($request->input('id'));
  $task->delete();

  return redirect('/event');
}

実際に削除ボタンを押してみると、カレンダーとDBどちらからもデータが削除されてるかと思います。

これで完成です!

さいごに

今回でFullcalendarにデータの登録、更新、削除まで行うことができました!

Fullcalendarはまだまだアレンジがたくさんできるので、これからも公式マニュアルを見ながらアップデートしていきたいと思います。

最後まで読んでいただいてありがとうございました!

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

FormRequestクラスをテストする

動機

PHP/Laravelを触り始めて間もないが、HTTPリクエストのバリデーションでFormRequestが便利ということで使っていた。手軽だったのでHTTPテストでテストを書いていたが、非効率な箇所もあったのでFormRequest単体でのテストを書きたかった。

前提

  • PHPUnit 9.5.2
  • Laravel Framework 8.27.0

内容

テスト対象のクラスを定義する

$ php artisan make:request ExampleRequest

authorize() で真を返すようにし、 rules() で適当なバリデーションルールを追加する。

app/Http/Requests/ExampleRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ExampleRequest 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 [
            'id' => 'required',
            'name' => 'required',
            'email' => 'required'
        ];
    }
}

テストを書く

テストファイルを作成する。(UnitかFeatureかの議論はここでは取り扱わない)

$ php artisan make:test ExampleTest

方法は以下の通り。 Table Driven Test 風に書きたかったので、データプロバイダを定義している。

  • ExampleTestインスタンスを作成する
  • Validatorインスタンスを作成する。テストデータをテスト関数の引数から、ルールとメッセージはExampleTestインスタンスから渡す
  • バリデータを実行し、その結果を評価する
tests/Feature/ExampleTest.php
<?php

namespace Tests\Feature;

use Illuminate\Support\Facades\Validator;
use Tests\TestCase;
use App\Http\Requests\ExampleRequest;

class ExampleTest extends TestCase
{
    /**
     * @dataProvider validationProvider
     * @return void
     */
    public function testValidation($inData, $outFail, $outMessage)
    {
        $request = new ExampleRequest();
        $rules = $request->rules();
        $messages = $request->messages();
        $validator = Validator::make($inData, $rules, $messages);
        $result = $validator->fails();
        $this->assertEquals($outFail, $result);
        $messages = $validator->errors()->getMessages();
        $this->assertEquals($outMessage, $messages);
    }

    public function validationProvider()
    {
        return [
            'success' => [
                [
                    'id' => 1,
                    'name' => 'aaa',
                    'email' => 'aaaa@example.com',
                ],
                false,
                [],
            ],
            'empty all fields' => [
                [],
                true,
                [
                    'id' => ['The id field is required.'],
                    'name' => ['The name field is required.'],
                    'email' => ['The email field is required.'],
                ],
            ],
        ];
    }
}

注意点

HTTPテストをやめて書く場合、当然ながらそのテストで確認していた点はテストできないので注意(例えば、FormRequestが返すメッセージ構造とHTTPレスポンスの構造は異なるなど。)

参考

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

Laravel 学習メモ

関数など

{{ route('ルートの名前') }}

web.phpを参照して、該当のルートに繋げる。

{{ asset('cssやイメージ') }}

publicを参照して、そこをドキュメントルートとして該当のファイルを読み込む。
例えば、public/css/layout.cssというファイルを読み込む際には、
{{ asset('css/layout.css') }}
という記載にする。

store('public/image')

画像を保存する際に用いられることが多い。
storage/appを参照して、引数のディレクトリにファイルが保存される。
なお、public/imageはこの関数を実行することで自動で作成される。

with('key', 'value')

別のリンクへリダイレクトを行う際に、

return redirect('/')->with('flash_message', '投稿が完了しました');

と記載するとリダイレクトされるとともに、セッションデータを作成してくれる。
第一引数がセッションのキーで、第二引数がそのキーの値となる。

各設定

バリデーションを日本語に変更する

confing/app.phpのlocalenになっていて、これは
resources/lang/enのバリデーション使用しているという意味。
resources/lang/jaのディレクトリを作成して、その中に日本語に変更しているファイルを作成して、localjaに変更することで日本語のバリデーションに変更される。

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

Laravel ヘルパ関数 メモ

{{ route('ルートの名前') }}

web.phpを参照して、該当のルートに繋げる。

{{ asset('cssやイメージ') }}

publicを参照して、そこをドキュメントルートとして該当のファイルを読み込む。
例えば、public/css/layout.cssというファイルを読み込む際には、
{{ asset('css/layout.css') }}
という記載にする。

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

【Docker/Laravel/MySQL5.7】docker-compose upした際にMySQL5.7コンテナがすぐにExitedしてしまう

Hello everyone !

問題

docker-compose up -dでコンテナを作成&起動しようとしたところ

Starting app-mysql       ... done
Starting app-redis       ... done
Starting app-mailcatcher ... done
Starting app-app         ... done
Starting app-nginx       ... done

正常に実行できたかと思われましたがdocker psでステータスを確認すると

CONTAINER ID        IMAGE                 COMMAND                  CREATED              STATUS                       PORTS                              NAMES
88f28d9cab3a       app_nginx              "/docker-entrypoint.…"   About a minute ago   Up About a minute              0.0.0.0:80->80/tcp               app-nginx
068d2c2a95ca       app_app                 "docker-php-entrypoi…"  About a minute ago   Up About a minute              0.0.0.0:9000->9000/tcp           app-app
46819a975074       mysql:5.7               "docker-entrypoint.s…"  About a minute ago   Exited (1) About a minute ago                                   app-mysql
b48d4aa43dc9       redis:5.0.5-alpine      "docker-entrypoint.s…"  5 weeks ago          Up About a minute              0.0.0.0:6379->6379/tcp           app-redis
79f839320c2a       schickling/mailcatcher  "mailcatcher --no-qu…"  5 weeks ago          Up About a minute              1025/tcp, 0.0.0.0:1080->1080/tcp app-mailcatcher

mysql:5.7コンテナだけ起動後にすぐ終了していることがわかりました。

ログを確認する

docker-compose logs

上記のコマンドでログを確認したところ以下のエラー文を確認しました。

// 一部抜粋
app-mysql | 2021-02-20T00:29:51.663885Z 0 [ERROR] --initialize specified but the data directory has files in it. Aborting.

解決策

エラー文を直訳すると

--initialize(初期化)が指定されていますが、データディレクトリにファイルがあるため失敗

ということでしたのでデータディレクトリのファイルを削除します。
app/docker-compose.ymlでディレクトリを設定していたので確認。

volumes:
  - "./storage/mysql:/var/lib/mysql"

ファイルやディレクトリを削除するrmLinuxコマンドにオプションの
r(指定ディレクトリ内のファイルやディレクトリを全て削除)
f(警告メッセージを表示しない)をつけて実行します。

$ rm -rf storage/mysql

以上でデータディレクトリにファイルの削除ができたのでコンテナを再起動させます。

$ docker-compose restart

mysql:5.7コンテナが起動後に落ちていないことを確認できました。

Thank you for always reading my posts.
Have a good day !

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

laravel ファクトリとSedderでデータベースをテスト

はじめに

避けては通れないテスト作業。laravelでは簡単にテストができますので備忘録として投稿します。
モデルはModelsというフォルダにて格納しています。
スクショはモデルの設計図になります。
“スクリーンショット” 2021-02-19 23.32.29.jpg

投稿者の環境

MacBook Pro (13-inch, 2019, Two Thunderbolt 3 ports)
macOS Big Sur バージョン11.0.1
PHP 7.3.23
Laravel Framework 6.20.7

下準備

config\app.phpにある'faker_locale'を下記の様に修正して下さい。ダミーデータが日本語化されます。

app.php
'faker_locale' => 'ja_JP',

ファクトリの生成

ファクトリを作成することによりダミーデータを作成ができます。また、--model=Postオプションにより、ファクトリが生成するモデルの名前を指定できます。このオプションは、生成するファクトリファイルへ指定モデル名を事前に設定します。モデルを作成していたら設定をして下さい。

ターミナル
php artisan make:factory PostFactory --model=Post
Post.php
//階層 Models/Post.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    public function comments()
    {
        // 投稿は複数のコメントを持つ
        return $this->hasMany('App\Models\Comment');
    }

}

フェイカーの作成

マイグレーションファイルと照らし合わせながら作成します。カラムによりプロパティが変わりますのでこちらのチートシートを参考にして下さい。

sample_faker.php
$data[] = [
        'name'     => $faker->name,            // 名前
        'zip'      => $faker->postcode,        // 郵便番号
        'pref'     => $faker->prefecture,      // 都道府県
        'city'     => $faker->city,            // 市
        'address'  => $faker->streetAddress,   // 住所
        'phone'    => $faker->unique()->phoneNumber,     // 電話番号
        'email'    => $faker->unique()->safeEmail,       // メール
        'birthday' => $faker->dateTimeBetween('-80 years', '-20years')
                            ->format('Y-m-d'), // 生年月日 (20〜80年前の日付)
        'score'    => $faker->numberBetween(1, 100), // 1〜100の数字
        'text'     => $faker->realText(20),    // ダミーテキスト 20文字
    ];
PostFactory.php
<?php

/** @var \Illuminate\Database\Eloquent\Factory $factory */

use App\Models\Post; //Postモデルを参照してテストデータを作成。Modelsというフォルダに格納しているため
use Faker\Generator as Faker;
//  ↓ Post::classのPostはModelsフォルダのPostを参照
$factory->define(Post::class, function (Faker $faker) {
    return [
        'name' => $faker->name,    // 氏名
        'subject' => $faker->realText(16),    // realTextは日本語の投稿場合に必ず使用。
        'message' => $faker->realText(200),    // 200文字のテキスト
        'created_at' => $faker->date('Y-m-d H:i:s', 'now'),
        'updated_at' => $faker->date('Y-m-d H:i:s', 'now'),

    ];
});

create_posts_table.php
//Postsマイグレーションファイル
<?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->bigIncrements('id');
            $table->timestamps();
            $table->string('name');
            $table->string('subject');
            $table->text('message');

        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('posts');
    }
}

Seederファイルの作成

Seederを作成することによって前述したファクトリのダミーデータを呼び出すことができる様になります。
SeederというのはDBのテスト以外でもく初期値やダミーデータ投入のための仕組みになります。
下記のコマンドを実行するとdatabase/seeds/以下にファイルが生成されます。また、PostsTableSeeder.phpを下記の様に編集して下さい。

ターミナル
php artisan make:seeder PostsTableSeeder
PostsTableSeeder.php
<?php
use Illuminate\Database\Seeder;

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

        factory(App\Models\Post::class, 50) //Postモデルを参照して尚且つ50個のダミーデータを作成
            ->create();
    }
}

外部キーによるファクトリとSeederの設定

CommentにはPost_idという外部キーが割り当てられています。このままテストデータを作成しても

SQLSTATE[HY000]: General error: 1364 Field カラム名 doesn't have a default value

というエラーが発生します。そこで、リレーションを指定することにより外部キーにダミーデータを作成することができます。ファクトリを下記の様に編集します。公式HPリレーションと属性クロージャを参考にしました。

CommentFactory.php
<?php

/** @var \Illuminate\Database\Eloquent\Factory $factory */

use App\Models\Comment;
use Faker\Generator as Faker;

$factory->define(Comment::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'comment' => $faker->realText(200), 
        'post_id' => factory(App\Models\Post::class),//外部キーを参照
        'created_at' => $faker->date('Y-m-d H:i:s', 'now'),
        'updated_at' => $faker->date('Y-m-d H:i:s', 'now'),
    ];
});
CommentsTableSeeder.php
<?php

use Illuminate\Database\Seeder;

class CommentsTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        factory(App\Models\Comment::class, 50)
            ->create();
    }
}

最後に

database/seeds/DatabaseSeeder.phpを開いて、runメソッドに PostsTableSeeder を追加します。

DatabaseSeeder.php
<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        // $this->call(UsersTableSeeder::class);
        $this->call(PostsTableSeeder::class);
        $this->call(CommentsTableSeeder::class);
    }
}

ターミナル
$ composer dump-autoload
$ php artisan db:seed

ダミーデータを削除して再度作成したい場合
$ composer dump-autoload
$ php artisan migrate:refresh --seed

成功すると下記のスクショの様になりphpMyAdminにダミーデータが保存されます。
“スクリーンショット” 2021-02-20 0.27.37.jpg
“スクリーンショット” 2021-02-20 0.28.29.jpg

参考URL

データベースのテスト
参考Qiita
参考テスト

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

【PHP】Laravel6で同時ログイン制御(先勝ち)をする方法を考えてみた

この記事について

この間に投稿した記事でLaravelでは認証機能(ログイン機能)を設定するのは簡単だと説明しました。
【PHP】Laravel6で遊ぶ(認証機能のセットアップ)

認証機能で考えてみたいのが、『同時ログイン制御』です。
少し調べたところ、あまり同時ログイン制御に関する記事やサイトがないような感じです。
本記事では先勝ちで同時ログインを禁止する方法を検討してみます。

環境

OS:Windows10 Home
PHP:7.4.15(XAMPP)
Laravel:6.20.16

実現方法

昨日の記事でSessionの管理方法について触れてみました。
【PHP】Laravelのセッション管理を勉強する

この記事では、Session情報をDBで管理する方法を紹介し、Sessionsテーブルに以下の項目が管理できています。

カラム名 内容
id セッションID
user_id ログインしているユーザーのID(NULLの場合は未ログイン)
ip_address IPアドレス
user_agent ユーザーエージェント
payload いろいろなデータ(適当)
last_activity 最終行動時間(UNIX時間)

実現方法としては、ログイン処理後の遷移前にログインしようとしているユーザーのIDがSessionsテーブルに存在するかどうかを調べます。
存在しない場合はログインさせ、存在する場合はログイン画面に戻す処理で考えてみます。

修正するファイルについて

実現方法のイメージはできたので、どのファイルをいじればいいのかを考えてみます。
ログイン画面のHTMLを確認すると、FormのPOSTメソッドでactionが(ドメイン)/loginになっています。
その場合にどのような処理が走るかは以下のコマンドで確認できます。

command
php artisan route:list
result
+--------+----------+------------------------+------------------+------------------------------------------------------------------------+--------------+
| Domain | Method   | URI                    | Name             | Action                                                                 | Middleware   |
+--------+----------+------------------------+------------------+------------------------------------------------------------------------+--------------+
|        | GET|HEAD | /                      |                  | Closure                                                                | web          |
|        | 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    |
+--------+----------+------------------------+------------------+------------------------------------------------------------------------+--------------+

上記のようなRouteの情報はroute/web.phpファイルで確認することもできます。

route/web.php
<?php

/*
|--------------------------------------------------------------------------
| 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('/', function () {
    return view('welcome');
});

Auth::routes();

Route::get('/home', 'HomeController@index')->name('home');

ただ、認証機能をセットアップするとAuth::routes();で認証関係のRouteがまとめられているので、コマンドで調べました。

コマンドの結果に戻ります。
ログイン処理時には/loginにPOSTメソッドで遷移します。
コマンド結果を見てみると・・・

+--------+----------+------------------------+------------------+------------------------------------------------------------------------+--------------+
| Domain | Method   | URI                    | Name             | Action                                                                 | Middleware   |
+--------+----------+------------------------+------------------+------------------------------------------------------------------------+--------------+
|        | POST     | login                  |                  | App\Http\Controllers\Auth\LoginController@login  

これですね。
アクションとしては、LoginControllerloginメソッドに処理を渡していることが分かります。
なので、App\Http\Controllers\Auth\LoginController.phpを修正すればよさそうですね。

loginメソッドの確認

早速、LoginController.phpを確認してみます。

LoginController.php
<?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');
    }
}

あれ?loginメソッドは?ってなりました。
実際は上記ファイルにloginメソッドの詳細は記載されていません。
AuthenticatesUsersというのがポイントで、Illuminate/Foundation/Auth/AuthenticatesUsersに記載されています。
ちなみに、Illuminate/vendor/laravel/framework/src/Illuminateを指しています。

/vendor/laravel/framework/src/Illuminate/Foundation/Auth/AuthenticatesUsers.php
<?php

namespace Illuminate\Foundation\Auth;

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

trait AuthenticatesUsers
{
    use RedirectsUsers, ThrottlesLogins;

    /**
     * Show the application's login form.
     *
     * @return \Illuminate\Http\Response
     */
    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);

        return $this->authenticated($request, $this->guard()->user())
                ?: 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();

        return $this->loggedOut($request) ?: 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();
    }
}

長いですが、loginメソッドが記載されていましたね。
内容までは解説しませんが・・・

ただ、loginメソッドが上記ファイルに記載されているとは言っても、上記ファイルを編集することはおススメしません。
vendorフォルダにはComposerでインストールしたライブラリ等が含まれるので、影響範囲が大きいです。
処理の制御はControllerで行うのが基本になりますので、LoginControllerの方を修正することにします。

LoginControllerの修正

以下のように修正してみました。

LoginController.php
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request; //追加
use Illuminate\Support\Facades\Auth; //追加
use Illuminate\Support\Facades\DB; //追加


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{
        login as _login;
    }

    /**
     * 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');
    }

    //以下を追記
    public function login(Request $request){

        $response = $this->_login($request);

        $user_id = Auth::id();

        $count = DB::table("sessions")
        ->where("user_id", $user_id)
        ->count();

        if ($count === 0){
            return $response;
        }
        else{
            Auth::logout();
            \Session::flash("message", "他のユーザーがログインしています。時間を置いて再ログインしてください。");
            return view("auth.login");
        }
    }
}

解説

    use AuthenticatesUsers{
        login as _login;
    }

これによって、後述する自作のloginメソッドに向くようにしています。

    public function login(Request $request){

        $response = $this->_login($request);

        $user_id = Auth::id();

        $count = DB::table("sessions")
        ->where("user_id", $user_id)
        ->count();

        if ($count === 0){
            return $response;
        }
        else{
            Auth::logout();
            \Session::flash("message", "他のユーザーがログインしています。時間を置いて再ログインしてください。");
            return view("auth.login");
        }
    }

こちらが追記したloginメソッドです。
リネームした_loginメソッドを実行してreturnの前に分岐を入れています。

ここでは詳しい説明はしませんが、Authファザードを使ってログインしたユーザーのIDを取得して、DBファザードを使ってSQLの実行をしています。

既にログインされている場合は、自信をログアウトさせてログイン画面に遷移するようにしています。

\Session::flash("message", "他のユーザーがログインしています。時間を置いて再ログインしてください。");

ちなみに、フラッシュと言って、遷移先に1回だけメッセージを表示することができます。

ということで、メッセージを表示するlogin.blade.phpにメッセージ表示するよう追記します。

/resources/views/auth/login.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <!--追記-->
            @if (session('message'))
                <div class="alert alert-warning">
                    {{ session('message') }}
                </div>
            @endif
            <!--ここまで-->
            <div class="card">
                <div class="card-header">{{ __('Login') }}</div>

                <div class="card-body">
                    <form method="POST" action="{{ route('login') }}">
                        @csrf

                        <div class="form-group row">
                            <label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>

                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>

                                @error('email')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>

                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="current-password">

                                @error('password')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row">
                            <div class="col-md-6 offset-md-4">
                                <div class="form-check">
                                    <input class="form-check-input" type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}>

                                    <label class="form-check-label" for="remember">
                                        {{ __('Remember Me') }}
                                    </label>
                                </div>
                            </div>
                        </div>

                        <div class="form-group row mb-0">
                            <div class="col-md-8 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    {{ __('Login') }}
                                </button>

                                @if (Route::has('password.request'))
                                    <a class="btn btn-link" href="{{ route('password.request') }}">
                                        {{ __('Forgot Your Password?') }}
                                    </a>
                                @endif
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

検証結果

Chromeでログインしてみる(1人目)
image.png
1人目としてなので、ログインできてOKです!

Firefoxでログインしてみる(2人目)
image.png
2人目なのでログインできずに、未ログインの状態でログイン画面に戻っていますね!
メッセージも表示されていていい感じです!

完成?

上記内容でうまく同時ログイン制御ができています。
ただ、一つ考慮しないといけないことがあります。
この内容でうまく処理ができるのは「ログアウト処理をちゃんとしている」場合です。

よくあると思いますが、皆さんログアウト処理をせずにブラウザのタブを閉じたり、ブラウザ自体を閉じたりして作業を終了させること多いと思います。
その場合、sessionsテーブルのuser_is``に値が入ったままで残り続けます・・・
そうなると、一生ログインできなくなってしまいます・・・

もう1つの条件

ここで使うのがlast_activityカラムです。
このカラムには最終行動したUNIX時間が格納されます。

なので、例えば20分操作しなかった場合にログアウトしたとみなすものとします。
それならば、ログインできる条件を「自アカウントでログインしている、かつ、現在時刻と最終行動時間の差が1200秒以内であるユーザーが0人」とすれば良いと考えます。

なので、以下のようにLoginControllerloginメソッドに条件を追加します。

    public function login(Request $request){

        $response = $this->_login($request);

        $user_id = Auth::id();
        $time = time(); //現在時刻のUNIX時間

        $count = DB::table("sessions")
        ->where("user_id", $user_id)
        ->where("last_activity", ">", $time-1200) //条件(AND)追加
        ->count();

        if ($count === 0){
            return $response;
        }
        else{
            Auth::logout();
            \Session::flash("message", "他のユーザーがログインしています。時間を置いて再ログインしてください。");
            return view("auth.login");
        }
    }

こんな感じでしょうか。
ちなみに「20分操作しなかった場合にログアウトしたとみなす」とする場合はLaravelの.envおよび、config/session.phpSESSION_LIFETIMEも20分にして合わせておくのがベストです。
逆にそうしないと2人ログインできている状態にもできるので、同時ログイン制御の目的は達成できないですね。

最後に

後勝ちの同時ログイン制御はlogoutOtherDevicesを使えるので比較的楽に実装できそうですが、
先勝ちの場合はどうしようかと思って考えついた方法です。
もっと楽な方法、リスクが少ない方法があれば教えて欲しいです^^

参考記事

Laravelで同時ログイン数を制御をする

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

AWSのEC2へのデプロイ②(必要なパッケージのインストール)

AWSのEC2へのデプロイをしたいと思い備忘録のために手順を記録します。
初学者の為、ご指摘等ありましたらご連絡いただければ幸いです。

前回の記事はこちら
AWSのEC2へのデプロイ①(EC2インスタンス作成からログインまで)

前回の内容は言語関係ないのですが今回から必要なパッケージが言語によって異なるのでご注意ください。

はじめに

私自身この手順で無事にデプロイができましたが、途中でエラーが出たことによりいろいろな記事を参考にさせて頂きました。
この場を借りて御礼申し上げます。

そのため、私に知識がないばかりに私の環境に必要のない操作もあるかもしれません。
初学者のためご承知おきください。
お気づきの点ございましたらご連絡いただければとても喜びます。

環境

Version
PHP 7.4.14
Laravel 8.24.0
mysql 8.0.23
docker 20.10.2
docker-compose 1.27.4

EC2ではAmazon Linux 2を利用しています。

前提

  • AWSのアカウントを作成済みである
  • EC2のインスタンス作成済みでsshにログインできる

EC2インスタンスに接続

1.AWSのEC2の画面で下記画像のようにインスタンスから接続を押下します。

スクリーンショット 2021-02-13 22.40.27.png

2.下記画像のようにSSHクライアントを押下後、一番下の文字列をコピーします。

スクリーンショット 2021-02-13 22.45.31.png

3.コピーしたssh -i ~をターミナルで入力すると

ターミナル
Are you sure you want to continue connecting (yes/no/[fingerprint])?

と出るのでyesと入力します。

4.下記のような内容が出力されていればOKです。

ターミナル
[ec2-user@ip-×××-××-××-××× ~]$

gitのインストール

コマンドは全てEC2インスタンス内で行います。

1.下記コマンドでyumをupdateします。

ターミナル
$ sudo yum update -y

2.下記コマンドでgitをインストールします。

ターミナル
$ sudo yum install git

3.下記コマンドご自身のユーザー名とパスワードを設定します。

ターミナル
$ git config --global user.name [ユーザー名]
$ git config --global user.email [メールアドレス]

4.下記コマンドで確認します。

ターミナル
$ git config --list | grep user

5.下記のように出ていればOKです。

ターミナル
user.name=[ユーザー名]
user.email=[メールアドレス]

Dockerのインストール

コマンドは全てEC2インスタンス内で行います。
Dockerのインストールは公式のAmazonECSにおけるDockerの基本で確認しました。

1.下記コマンドで最新のDocker Engineパッケージをインストールします(Amazon Linux 2用のコマンドです。)

ターミナル
sudo amazon-linux-extras install docker

2.下記コマンドでDockerサービスを開始します。

ターミナル
sudo service docker start

3.下記コマンドでec2-userdockerグループに追加するとsudoコマンドを使用せずにDockerコマンドを実行できます。

ターミナル
sudo usermod -a -G docker ec2-user

4.一旦EC2インスタンスからログアウト(exit)して再ログインします。
※これをしなくて私はエラーになりましたので大事です。

5.下記コマンドでec2-usersudoコマンドを使用せずにDockerコマンドを実行できることを確認します。

ターミナル
docker info

下記のようなメッセージが出ればOKです。

ターミナル
Client:
 Debug Mode: false

Server:
 Containers: 0
  Running: 0
  Paused: 0
  Stopped: 0
 Images: 0
//省略

docker-composeのインストール

参考記事:M1 Mac + Laravel + Docker + AWS でポートフォリオを作るまで[初心者][未経験]

コマンドは全てEC2インスタンス内で行います。

1.下記コマンドでsuに入ります。

ターミナル
sudo -i

2.下記コマンドでcomposeをインストールします。

ターミナル
curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

3.下記コマンドで実行権限を付与します。

ターミナル
chmod +x /usr/local/bin/docker-compose

4.exitをして抜けます。

ターミナル
exit

5.下記コマンドでdocker-composeがインストールされているか確認します。

ターミナル
docker-compose --version

下記のようにバージョンが出てくればOKです。

ターミナル
docker-compose version 1.27.4, build 40524192

参考にした先ほどの記事でdocker-compose.ymlのバージョンを書き換えました。

docker-compose.yml
version: "3.3" 変更
services:
  app:
    build: ./infra/php
    volumes:
      - ./backend:/work
//省略

EC2インスタンス内でDockerを起動

コマンドは全てEC2インスタンス内で行います。

1.下記コマンドでgitからcloneします。

ターミナル
git clone [githubのURL]

2.下記コマンドでフォルダがあるか確認します。

ターミナル
ls

3.下記コマンドでディレクトリを移動します。

ターミナル
cd [ディレクトリ名]

4.下記コマンドでdockerコンテナを立ち上げます。
少し長いメッセージが出ます。

ターミナル
docker-compose up -d

下記のようなメッセージが出ればOKです。

ターミナル
Creating ×××_app_1      ... done
Creating ×××_web_1      ... done
Creating ×××_db_1       ... done
Creating phpmyadmin         ... done

5.下記コマンドでステータスを確認します。

ターミナル
docker-compose ps

Stateが全てupになっていればOKです。

PHPのインストール

参考記事:AWS EC2 AmazonLinux2 PHPをインストールする

コマンドは全てEC2インスタンス内で行います。

1.下記コマンドでインストールリポジトリを追加します。

ターミナル
sudo rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
ターミナル
sudo rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-7.rpm

2.下記コマンドで既存のPHPパッケージを削除します。(削除するものがなくても問題ないです)

ターミナル
sudo yum remove php

3.下記コマンドでPHPのパッケージをインストールします。

ターミナル
sudo yum install --enablerepo=remi,remi-php74 php php-devel php-mbstring php-pdo php-gd php-xml php-mcrypt

4.下記コマンドでバージョンを指定してインストールします。
途中で[y/d/N]とインストールしてもいいかの選択が出るのでyを押しEnterで進めます。

ターミナル
sudo amazon-linux-extras install php7.4

5.下記コマンドでその他の必要なパッケージをインストールします。
こちらも途中で[y/d/N]とインストールしてもいいかの選択が出るのでyを押しEnterで進めます。

ターミナル
sudo yum install --enablerepo=remi,amzn2extra-php7.4 php-xml php-mbstring

6.下記コマンドでyumにてインストールされたphpと名前がつくパッケージの一覧を表示し確認します。

ターミナル
yum list installed | grep php

下記のように表示されればOKです。

ターミナル
php-cli.x86_64                  7.4.7-1.amzn2                 @amzn2extra-php7.4
php-common.x86_64               7.4.7-1.amzn2                 @amzn2extra-php7.4
php-fpm.x86_64                  7.4.7-1.amzn2                 @amzn2extra-php7.4
php-json.x86_64                 7.4.7-1.amzn2                 @amzn2extra-php7.4
php-mbstring.x86_64             7.4.7-1.amzn2                 @amzn2extra-php7.4
php-mysqlnd.x86_64              7.4.7-1.amzn2                 @amzn2extra-php7.4
php-pdo.x86_64                  7.4.7-1.amzn2                 @amzn2extra-php7.4
php-xml.x86_64                  7.4.7-1.amzn2                 @amzn2extra-php7.4

7.無事に成功すれば下記コマンドでバージョンが表示されます。

ターミナル
php --version

MariaDBのインストール

コマンドは全てEC2インスタンス内で行います。

1.下記コマンドでMariaDBのインストールをします。

ターミナル
sudo yum -y install mysql56-server mysql56-devel mysql56 mariadb-server mysql-devel

2.下記コマンドでデータベースを起動します。

ターミナル
sudo systemctl start mariadb

下記コマンドで起動できたかを確認します。

ターミナル
sudo systemctl status mariadb

緑の文字でactive (running)と出ていればOKです。

3.データベースのrootパスワードを設定します。

まずは下記のコマンドを入力します。

ターミナル
sudo /usr/bin/mysql_secure_installation

次からどんどん質問が出てくるので答えていきます。

Enter current password for root (enter for none):→Enterキーを押す。
Set root password? [Y/n]→「Y」を入力してEnterキーを押す。
New password:→ご自身で決めたパスワードを入力する。(画面には表示されないけど入力できています)
Re-enter new password:→もう1度ご自身で決めたパスワードを入力する。
Remove anonymous users? [Y/n]→「Y」を入力してEnterキーを押す。
Disallow root login remotely? [Y/n]→「Y」を入力してEnterキーを押す。
Remove test database and access to it? [Y/n]→「Y」を入力してEnterキーを押す。
Reload privilege tables now? [Y/n]→「Y」を入力してEnterキーを押す。

4.データベースに接続を確認します。

下記コマンドでデータベースに接続できるか確認します。

ターミナル
mysql -u root -p

パスワードを求められるので入力しEnterキーを押します。

ターミナル
MariaDB [(none)]>

上記のように表示されれば接続できています。
抜け出したいときはexitで抜け出せます。

Laravel環境設定

参考記事:AWSにEC2上にdockerを使用したlaravelをデプロイ④(gitクローン〜デプロイ、マイグレーション)

コマンドは全てEC2インスタンス内で行います。

1.下記コマンドでコンテナ内に入ります。

ターミナル
docker-compose exec app bash

2.下記コマンドで.envファイルを作成します。
.envファイルはgitリポジトリにpushされないためです。

ターミナル
cp .env.example .env

3.下記コマンドでcomposerをインストールします。

ターミナル
composer install

4.下記コマンドでAPP_KEYを発行します。

ターミナル
php artisan key:generate

5.下記コマンドで権限を変更します。

ターミナル
chmod 777 storage/logs vendor
chmod 777 storage/framework/views
chmod 777 storage/framework/sessions

6.画像投稿がある場合は下記コマンドも必要みたいです。

ターミナル
php artisan storage:link
chown -R www-data:root .

7.下記コマンドでマイグレーションを実行します。

ターミナル
php artisan migrate
php aritsan db:seed

以上で無事にデプロイ ができました。

ただ、現在S3への画像の保存ができない状態なので解決出来次第修正致します。

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