20190709のlaravelに関する記事は7件です。

Carbonを使って投稿経過時間を表示する(Laravel)

About Carbon

Carbonについての説明は下記の記事に詳しく書かれています。

PHPで日付時刻処理を書くならCarbonを使うべき
phpの便利な日付、時刻オブジェクト Carbon を使用する。平成、令和などの和暦を表示させる
全217件!Carbonで時間操作する実例

概要

過去に作ったモジュールについて記事を書いてます。
instgramなどの投稿に、投稿から「〇〇分前」などと表示される時間経過の部分を作ります。
画像の「9分前」、「1秒前」の部分のことです。
image.png

コード

bladeテンプレートの投稿コンテンツにこれを書くだけでできます。

index.blade.php
<?php   //時間の差分を求める
$postedAt = new \Carbon\Carbon($message->created_at);
$now = \Carbon\Carbon::now();
$secondsSincePosted = $postedAt->diffInSeconds($now);
if($secondsSincePosted > 59){
    $minutesSincePosted = $postedAt->diffInMinutes($now);
    if($minutesSincePosted > 59){
        $hoursSincePosted = $postedAt->diffInHours($now);
        if($hoursSincePosted > 23){
            $daysSincePosted = $postedAt->diffInDays($now);
            if($daysSincePosted > 6){
                $yearsSincePosted = $postedAt->diffInYears($now);
                if($yearsSincePosted > 0){
                    echo $postedAt->format("Y年n月j日");
                }else{
                    echo $postedAt->format("n月j日");
                }
            }else{
                echo $daysSincePosted.'日前';
            }
        }else{
            echo $hoursSincePosted.'時間前';
        }
    }else{
        echo $minutesSincePosted.'分前';
    }
}else{
    echo $secondsSincePosted.'秒前';
}
?>

まとめ

Carbonはいいぞ。

Githubにも上げてます。
jdkfx/posted-date

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

LaradockでLaravel環境構築【手順書】

はじめに

laradockでlaravel環境を作る手順書です。
単純に動かすだけの手順でなく、下記点を含んでいます。

  • laradockで2つ目の環境を構築する時に困らないための設定事項
  • 設定しておくと開発時に多少楽になるための設定事項
  • 嵌った点の備忘録

前提環境

  • docker-composeが使用できること
    • windowsの場合Windows10 Proであること
    • docker-composeがインストールされていること
  • ターミナルを使用できること
    • windowsの場合gitbashやpower shell推奨
  • 海外のCDNサーバにブロックされていたりしないこと
    • 短期間に環境を作りまくったせいか一部サーバに一時的にブロックされたことがあります
    • この場合ネットワークを変えることで解決したりします(ポケットwifi等)
  • 2018年以前に取得したlaradockを使用しないこと
    • laradockの使用しているdocker-compose.ymlがv2である可能性があります

完成形

サーバ構成

  • nginx
    • + php-fpm
  • mysql
  • redis
  • APサーバ(workspace)

ディレクトリ構成

laradockとプロジェクトディレクトリ(laravel)を同階層に置く想定です

my_project
├── laravel
│   ├── app
│   ├── bootstrap
│   ├── config
│   └ ...
└── laradock
    ├── adminer
    ├── aerospike
    └ ...

手順

  1. laradock取得
  2. laradockの設定ファイル修正
  3. laradockビルド・アクセス
  4. laravelインストール・yarnインストール
  5. laravel設定ファイル修正
  6. ブラウザアクセス

1.laradock取得

適当な場所にプロジェクト用ディレクトリを作成し、laradockをダウンロード

mkdir ~/my_project
cd ~/my_project
git clone https://github.com/Laradock/laradock.git

2.laradockの設定ファイル修正

  • 設定ファイルの作成
    • .env作成
    • createdb.sql作成
cd ./laradock
cp -a ./env-example ./.env
cp -a ./mysql/docker-entrypoint-initdb.d/createdb.sql.example ./mysql/docker-entrypoint-initdb.d/createdb.sql
  • 下記ファイルを修正
.env
+ APP_CODE_PATH_HOST=../laravel/
- APP_CODE_PATH_HOST=../

+ APP_CODE_PATH_CONTAINER=/var/www/laravel/
- APP_CODE_PATH_CONTAINER=/var/www/

//mysql等のデータ本体の置き場所。これを修正しておかないと未来で困る。
+ DATA_PATH_HOST=~/.laradock/my_project/data
- DATA_PATH_HOST=~/.laradock/data

//コンテナ区別用prefix。これを修正しておかないと未来で困る。
+ COMPOSE_PROJECT_NAME=my_project-laradock
- COMPOSE_PROJECT_NAME=laradock

+ PHP_VERSION=7.3
- PHP_VERSION=7.2

//laravelがmysql8.0を使うにはユーザ設定に追加で修正が必要なため5系を指定
+ MYSQL_VERSION=5.7
- MYSQL_VERSION=latest
/nginx/sites/default.conf
+ server_name dev.my_project.com;
- server_name localhost;

+ root /var/www/laravel/public;
- root /var/www/public;
/mysql/docker-entrypoint-initdb.d/createdb.sql
+ CREATE DATABASE IF NOT EXISTS `project_db` COLLATE 'utf8_general_ci' ;
+ GRANT ALL ON `project_db`.* TO 'default'@'%' ;
- #CREATE DATABASE IF NOT EXISTS `dev_db_1` COLLATE 'utf8_general_ci' ;
- #GRANT ALL ON `dev_db_1`.* TO 'default'@'%' ;

// テスト用DB作成(ユーザは使いまわす想定)
+ CREATE DATABASE IF NOT EXISTS `test` COLLATE 'utf8_general_ci' ;
+ GRANT ALL ON `test`.* TO 'default'@'%' ;
- #CREATE DATABASE IF NOT EXISTS `dev_db_2` COLLATE 'utf8_general_ci' ;
- #GRANT ALL ON `dev_db_2`.* TO 'default'@'%' ;

3. laradockビルド・アクセス

下記コマンドを実行し、サーバを構築・ビルド

cd ~/my_project/laradock
docker-compose build workspace nginx mysql redis
docker-compose up -d nginx mysql redis

下記コマンドでworkspaceサーバへアクセス

docker-compose exec --user=laradock workspace bash

4. laravelインストール・yarnインストール

workspaceサーバ内で下記コマンド実行

laradock@~~~~:/var/www$ composer create-project laravel/laravel --prefer-dist
laradock@~~~~:/var/www$ cd laravel
laradock@~~~~:/var/www/laravel$ yarn install

5. laravel設定ファイル修正

.envを修正

.env
+ DB_HOST=mysql
- DB_HOST=127.0.0.1
+ DB_DATABASE=project_db
- DB_DATABASE=default
+ DB_USERNAME=default
- DB_USERNAME=homestead

6. ブラウザアクセス

hostsの修正

windowsの場合

メモ帳を管理者で実行し、下記ファイルを修正

C\Windows\System32\drivers\etc\hosts
+ 127.0.0.1       dev.my_project.com

macの場合

/private/etc/hosts
+ 127.0.0.1       dev.my_project.com

ブラウザアクセス

ブラウザを開き、http://dev.my_project.com へアクセス
laravelが表示されていれば完了です! :smiley:


おまけ

ありがちな失敗

docker-compose buildに失敗する

npm ERR! code EAI_AGAIN
npm ERR! errno EAI_AGAIN

上記のようなエラーが発生した場合、時間をおいて再ビルドすれば直ることがあります。

docker-compose up時にmysqlが起動しない

既にlaradockでプロジェクトを立ち上げたことがある場合、
そのプロジェクトとDATA_PATH_HOSTの場所被った場合立ち上げに失敗します。

DATA_PATH_HOSTを変えビルドしなおし(docker-compose build --no-cache mysql)をしたり、
既存のDATA_PATH_HOSTの中身を削除したりしてみてください。
(削除して大丈夫かはご自身でご判断ください)

docker-compose upが全体的に失敗する

過去にdocker等で仮想イメージを作ったことがある場合、
既存のものとポートが被ったりすることが多々あります。

workspace, nginx, mysql, redisのポートをずらしたりdockerを再起動したりビルドしなおしたり色々試してください。
*特に2018年までのlaradockにはCOMPOSE_PROJECT_NAMEの設定が無いため、コンテナ名が被ります

mysqlを誤って8.0で作成してしまった

laravelは最新版でもmysql8.0標準の暗号化方法(caching_sha2_password)をサポートしていません。(2019/6/28現在)
mysql8.0を作成してしまい、そこで作ってしまったユーザでログインしたい場合、
下記のようなsqlによりパスワード設定を更新してください。

ユーザ:'default'を、パスワード:'secret'で更新

ALTER USER 'default' IDENTIFIED WITH mysql_native_password BY 'secret';

Q.A

php5.4は使えないのか?

使えません。5.6以上となります。

windows10 homeでは使えないのか?

現在のところ使えません。
どうしても使いたい人はWSL2でググると道があります。

DBにつながるか確認したい

workspace内で下記コマンド打てばテーブル作成処理が走るので、試してみるといいと思います。

$ cd /var/www/laravel/
$ php artisan migrate

成功後は下記コマンドでだいたい元通りにできます

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

Laravel キーワード検索 複数

はじめに

Laravelアプリにて、複数条件の検索機能を実装しました!
文字とカテゴリーの2つで検索出来る仕様です!

始め(修正前)はコントローラー/モデルで下記のように、条件毎でメソッドを分け記述していました。(メソッド名は気にせず)

QuestionsController.php(修正前)
class QuestionsController extends Controller
{
    private $question;
    private $category;

    public function __construct(Question $question, Category $category)
    {
        $this->question = $question;
        $this->category = $category;
    }

   public function index(Request $request)
   {
        //カテゴリー一覧を表示するため、カテゴリーモデルでデータを連想配列で取得するメソッド
        $categoryNames = $this->category->カテゴリー名取得メソッド();

        $keyword    = $request->input('キーワード');
        $categoryId = $request->input('カテゴリー');

        if (isset($keyword) && isset($categoryId)) {
            $questions = $this->question->なんちゃらメソッド1($keyword, $categoryId);
        } elseif (isset($keyword)) {
            $questions = $this->question->なんちゃらメソッド2($keyword);
        } elseif (isset($categoryId)) {
            $questions = $this->question->なんちゃらメソッド3($categoryId);
        } else {
            $questions = $this->question->all();
        }

        return view('question.index', compact(['questions', 'categoryNames']));
    }
}

モデル

questiion.php(修正前)
class Question extends Model
{
    public function なんちゃらメソッド1($keyword, $categoryId)
    {
        return $this->with(['紐づくデータ'])->where('title', 'LIKE', '%' . $keyword . '%')
                                         ->where('category_id', '=', $categoryId)
                                         ->get();
    }

    public function なんちゃらメソッド2($keyword)
    {
        return $this->with(['紐づくデータ'])->where('title', 'LIKE', '%' . $keyword . '%')->get();
    }

    public function なんちゃらメソッド3($categoryId)
    {
        return $this->with(['紐づくデータ'])->where('category_id', '=', $categoryId)->get();
    }
}

今回は条件が2つなので上記の記述量で収まっていますが、検索条件が増えた場合、メソッドと条件分岐をその都度書き足す必要が出てきます。
めんどくさいですし、コントローラがファットになってしまいます。

複数条件での検索を探す中で、クロージャやorWhereなど色々出てきましたが、わからず。。
最終的に下記の記述に書き直しました!

QuestionsController.php(修正後)
class QuestionsController extends Controller
{

   <--- 略 --->

   public function index(Request $request)
    {
        $categoryNames = $this->category->カテゴリー名取得メソッド();

        $questions     = $this->question->なんちゃらメソッド($request);

        return view('question.index', compact(['questions', 'categoryNames']));
    }
}

モデル

Question.php(修正後)
class Question extends Model
{
    public function なんちゃらメソッド($request)
    {
        $keyword    = $request->input('キーワード');
        $categoryId = $request->input('カテゴリー');
        $query      = $this->with(['紐づくデータ', '紐づくデータ']);

        if (isset($keyword)) {
            $query->where('title', 'LIKE', '%' . $keyword . '%');
        }

        if (isset($categoryId)) {
            $query->where('category_id', '=', $categoryId);
        }

        return $query->get();
    }
}

inputでの値の抽出もモデルに記述し、なるべくコントローラーのコードを減らしました!
eagerロードで紐づく値を取得したものを$query変数に代入し、条件が入力されていればその条件にあったSQLの処理が走ります!

このように書けば、メソッド毎に処理を追うこともなくなり、条件が増えた場合の追加が楽、可読性向上などのメリットがあると思います!

改善点あればご教示お願い致します!

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

Laravel 検索機能 複数条件

はじめに

Laravelアプリにて、複数条件の検索機能を実装しました!
文字とカテゴリーの2つで検索出来る仕様です!

始め(修正前)はコントローラー/モデルで下記のように、条件毎でメソッドを分け記述していました。(メソッド名は気にせず)

QuestionsController.php(修正前)
class QuestionsController extends Controller
{
    private $question;
    private $category;

    public function __construct(Question $question, Category $category)
    {
        $this->question = $question;
        $this->category = $category;
    }

   public function index(Request $request)
   {
        //カテゴリー一覧を表示するため、カテゴリーモデルでデータを連想配列で取得するメソッド
        $categoryNames = $this->category->カテゴリー名取得メソッド();

        $keyword    = $request->input('キーワード');
        $categoryId = $request->input('カテゴリー');

        if (isset($keyword) && isset($categoryId)) {
            $questions = $this->question->なんちゃらメソッド1($keyword, $categoryId);
        } elseif (isset($keyword)) {
            $questions = $this->question->なんちゃらメソッド2($keyword);
        } elseif (isset($categoryId)) {
            $questions = $this->question->なんちゃらメソッド3($categoryId);
        } else {
            $questions = $this->question->all();
        }

        return view('question.index', compact(['questions', 'categoryNames']));
    }
}

モデル

questiion.php(修正前)
class Question extends Model
{
    public function なんちゃらメソッド1($keyword, $categoryId)
    {
        return $this->with(['紐づくデータ'])->where('title', 'LIKE', '%' . $keyword . '%')
                                         ->where('category_id', '=', $categoryId)
                                         ->get();
    }

    public function なんちゃらメソッド2($keyword)
    {
        return $this->with(['紐づくデータ'])->where('title', 'LIKE', '%' . $keyword . '%')->get();
    }

    public function なんちゃらメソッド3($categoryId)
    {
        return $this->with(['紐づくデータ'])->where('category_id', '=', $categoryId)->get();
    }
}

今回は条件が2つなので上記の記述量で収まっていますが、検索条件が増えた場合、メソッドと条件分岐をその都度書き足す必要が出てきます。
めんどくさいですし、コントローラがファットになってしまいます。

複数条件での検索を探す中で、クロージャやorWhereなど色々出てきましたが、わからず。。
最終的に下記の記述に書き直しました!

QuestionsController.php(修正後)
class QuestionsController extends Controller
{

   <--- 略 --->

   public function index(Request $request)
    {
        $categoryNames = $this->category->カテゴリー名取得メソッド();

        $questions     = $this->question->なんちゃらメソッド($request);

        return view('question.index', compact(['questions', 'categoryNames']));
    }
}

モデル

Question.php(修正後)
class Question extends Model
{
    public function なんちゃらメソッド($request)
    {
        $keyword    = $request->input('キーワード');
        $categoryId = $request->input('カテゴリー');
        $query      = $this->with(['紐づくデータ', '紐づくデータ']);

        if (isset($keyword)) {
            $query->where('title', 'LIKE', '%' . $keyword . '%');
        }

        if (isset($categoryId)) {
            $query->where('category_id', '=', $categoryId);
        }

        return $query->get();
    }
}

inputでの値の抽出もモデルに記述し、なるべくコントローラーのコードを減らしました!
eagerロードで紐づく値を取得したものを$query変数に代入し、条件が入力されていればその条件にあったSQLの処理が走ります!

このように書けば、メソッド毎に処理を追うこともなくなり、条件が増えた場合の追加が楽、可読性向上などのメリットがあると思います!

改善点あればご教示お願い致します!

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

AmazonLinux2でLaravelの開発環境構築

AWSでLaravelの開発環境を構築して行きたいと思います。

前提条件として、
すでにAWSでアカウントは作成済みとしています。
また、簡単なWEBアプリケーションは作成したことがある方を対象としています

EC2の起動

まずは、AWSのコンソールからEC2を起動します。
「インスタンスの作成」をクリックすると、Amazon マシンイメージ(AMI)の選択画面になるので、Amazon Linux 2 AMI (HVM),SSD Volume Type を「選択」します。

次にインスタンスタイプの選択画面になるので、好きなインスタンスタイプを選択します。t2.microが無料利用枠の対象なので、利用できる方はこちらのタイプが良いかと思います。
私は開発環境での利用なので、今回 t3.nano を選択しました。

インスタンスの詳細の設定については、基本的には自由に設定できますが、よくわからなければ一旦全てデフォルトのままでも問題ないかと思います。
私は、そこまで頻繁に利用しない開発環境ということもあるので、スポットインスタンスのリクエストにチェックを入れて、費用を安く抑えています。スポットインスタンスは、費用を安く抑えられる反面、連続稼働を保証していないので、本番環境ではお勧めできませんので注意ください。

ストレージの追加も任意ですが、ルートの8GiBのみで進みます。

セキュリティグループの設定は、一旦最小限のセキュリティグループを作成します。

タイプ プロトコル ポート範囲 ソース
SSH TCP 22 マイIP (ご自身の接続IP)
HTTP TCP 80 カスタム 0.0.0.0/0,::/0

ターミナルへのログインのために、SSHのポートをご自身のIPで設定。
HTTPは、どこからでも閲覧可能なように設定。自分しか確認できないようにしたい場合は、HTTPのソースについてもマイIPを設定してください。

最後に、ログイン用のキーを生成して終了です。

EC2の初期設定

EC2が起動したら、ec2-userとしてログインしてみましょう。
作成したキーを .ssh/ 以下に配置しておきます。

ssh ec2-user@ec2-**-**-**-***.ap-northeast-1.compute.amazonaws.com -i ./.ssh/(秘密キー)

Laravelをセットアップする前に、ざっとEC2の設定を行います。

とりあえず、パッケージを最新に更新

$ sudo yum update -y

ec2-userのままでも良いですが、実際に利用するユーザーを作成
ユーザー名をいつも利用しているユーザー名を利用してください。説明では munakata として進めます。

$ sudo su -
# useradd munakata
# passwd munakata
# usermod -G wheel munakata

munakata に sudo権限を付与します。

# visudo

root以下に追加

visudo
## Allow root to run any commands anywhere
root    ALL=(ALL)       ALL
munakata    ALL=(ALL)       ALL

munakataのホームディレクトリ(/home/munakata) 以下の
.ssh/authorized_keys に公開鍵をセット。

すでにご自身の公開鍵はお持ちかと思いますが、まだない方は作成してください。
SSHキー等で検索すれば、いくつか情報が出てくるかと思います。

一応、権限を記載しておきます。

権限 パス
700 ~/.ssh
600 ~/.ssh/authorized_keys

これで、munakataユーザーでログインする準備は完了です。
一度ログアウトして、実際に新しいユーザーでログインしてみましょう。

ssh munakata@ec2-**-**-**-***.ap-northeast-1.compute.amazonaws.com -i ./.ssh/(munakataの秘密キー)

ローカライズ設定を行います。

タイムゾーンを日本時間にセット

/etc/sysconfig/clock
$sudo vim /etc/sysconfig/clock

ZONE="Asia/Tokyo"
UTC=false

反映には再起動が必要なので、とりあえず日本時間にセット

$ sudo cp /usr/share/zoneinfo/Japan /etc/localtime

日本語設定

/etc/sysconfig/i18n
$ sudo vim /etc/sysconfig/i18n

LANG=ja_JP.UTF-8

ここまでで、ざっとEC2の初期設定が完了です。

必要なパッケージのインストール

ここからは、AmazonLinux2でLaravelを構築するために必要なパッケージをインストールしていきます。
なるべく特殊なことはせず、ある程度AWSで用意された標準的なもので構築したいと思います。

WEBサーバーとして、apache2.4 、PHPのバージョンは、PHP7.3 を使用します。

いきなりですが、AmazonLinux2で、yumを利用してPHPをインストールするとPHP5になってしまいます。
AmazonLinuxでは、yum install php72 でPHP7.2をインストールできたのですが、AmazonLinux2には用意されていません。
その代わりに、Extra Library が用意されているようです。
Extra Library は、amazon-linux-extras で利用可能です。

php7.3をインストールしてみます。

$ sudo amazon-linux-extras php7.3

一緒に必要なパッケージもインストールされます

php-cli.x86_64 7.3.6-1.amzn2.0.1 @amzn2extra-php7.3
php-common.x86_64 7.3.6-1.amzn2.0.1 @amzn2extra-php7.3
php-fpm.x86_64 7.3.6-1.amzn2.0.1 @amzn2extra-php7.3
php-json.x86_64 7.3.6-1.amzn2.0.1 @amzn2extra-php7.3
php-mysqlnd.x86_64 7.3.6-1.amzn2.0.1 @amzn2extra-php7.3
php-pdo.x86_64 7.3.6-1.amzn2.0.1 @amzn2extra-php7.3

確認してみましょう。

$ php -v 

amazon-linux-extras でパッケージをインストールすると、拡張モジュールに関しては、yumを使って適切なパッケージをインストールしてくれるようになります。便利ですね!

実際にインストール可能な拡張モジュールを確認してみましょう。

$ sudo yum list php* | grep php7.3

必要な拡張モジュールをインストールしていきます。必要に応じて各自検討ください。
php-xmlは、Laravelインストール時に、phpunitのインストールに必要になるようなので、事前にインストールしておきましょう。

$sudo yum install php-mbstring php-pecl-memcached php-gd php-apcu php-xml

次に、apache2.4をインストールします。

AmazonLinuxの場合は、2.4系を入れる場合は、 httpd24 でしたが、AmazonLinux2の場合は、httpd で、2.4系になるようです。

$ sudo yum install httpd

起動します。

$ sudo systemctl start httpd

コンソール上には何も出力されないので、実際に動いているか確認します。
下記のコマンドで、active (running) とか表示されているはずです。

$ sudo systemctl status httpd

実際にWEBブラウザから確認してみます。

起動したEC2に、パブリックDNSが割り振られているかと思うので、そのURLで確認すると、Apacche2.4のTestPageが表示されるかと思います。

IPv4 パブリックIPでも確認可能です。

ElasticIPを紐付けた方は、そちらのIP、もしくはRoute53で設定したドメインで確認してください。

PHPの設定

この時点で、Laravelのインストール自体は可能なのですが、PHP、Apacheの細かい設定もしていきましょう。
PHPの設定は、 /etc/php.ini で行います。

設定した値を確認できるように、phpinfoの表示ページを作成しておきましょう。

設定内容は、各自調整してください。
私がよく変更する箇所は、この辺です。

php.ini
# HTTPヘッダにPHPのバージョンを記載しない(一応セキュリティ的にOffにしておいたほうが良い)
# expose_php = On
expose_php = Off

# メモリ上限を引き上げる(結構デフォルトのメモリは少なめなので増やしておくことが多い)
# memory_limit = 128M
memory_limit = 256M


# POST送信の許容サイズを引き上げる
# post_max_size = 8M
post_max_size = 16M

# アップロードファイルの許容サイズを引き上げる(スマホの写真のサイズが大きくなっているので、2Mだとほぼ画像投稿できないので増やす)
# upload_max_filesize = 2M
upload_max_filesize = 16M

# timezoneの設定
# date.timezone =
date.timezone = Asia/Tokyo

設定を反映します。

モジュール版のPHPの場合、通常httpdを再起動すると、php.iniの内容が反映されるのですが、AmazonLinux2で、PHP7.3とhttpd2.4を構築した場合、デフォルトでSever APIが FPM/FastCGI となるため、httpdでは、php.iniの設定が反映されず、php-fpmの再起動が必要となります。

php-fpmについて詳しく知りたい方は、「php-fpm」等のキーワードでお調べください。

$ sudo systemctl restart php-fpm

Laravelインストール

DocumentRootにLaravelを配置することも可能なのですが、開発環境として構築するので、今回はVirtualHostの機能を利用して、ホームディレクトリに、htmlディレクトリを作成して、その配下にLaravelプロジェクトを配置します。

まずは、Composerをインストール

curl  -sS https://getcomposer.org/installer | php

composer.phar がダウンロードされるので、composer のコマンドで実行できるように、PATHが通っている場所へ移動させます。

sudo mv composer.phar /usr/local/bin/composer

これで composer が利用できるようになったので、Laravelをインストールします。

$ cd ~/html/
$ composer create-project --prefer-dist laravel/laravel blog

これで、blogというLaravelプロジェクトが構築されます。
ただし、今回利用している t3.nano などのインスタンスタイプだと、メモリが足らずにインストールの途中に下記のエラーが出てしまいます。

mmap() failed: [12] Cannot allocate memory

そこで、ハードディスクにswap領域を作成して、メモリ不足を補います。

最初に、現状確認

$ free
              total        used        free      shared  buff/cache   available
Mem:         470512      145504      118000         104      207008      290656

とりあえず、1G程度用意すればLaravelのインストールは可能なのでswapを作成します。

$ sudo dd if=/dev/zero of=/swapfile bs=1M count=1024
$ sudo chmod 600 /swapfile
$ sudo mkswap /swapfile
$ sudo swapon /swapfile

再度、freeコマンドでSwapが追加されていれば、OKです。

$ free
              total        used        free      shared  buff/cache   available
Mem:         470512      145504      118000         104      207008      290656
Swap:       1048572       27904     1020668

これで、やっと準備が整ったので、再度

$ composer create-project --prefer-dist laravel/laravel blog

あとは、Laravelのドキュメントに記載がある通りに設定をしていきます。

$ cd ~/html/blog
$ composer update
$ chmod -R 777 bootstrap/cache
$ chmod -R 777 storage
$ php artisan key:generate

基本的には、設定ファイルは、.envになりますが、開発環境専用にする場合は、下記のようにリネームします。

$ mv .env .env.development

Apache VirtualHost 設定

最後に、ApacheのVirtualHost設定を行います。

Virtual Hostの記述は、自動で設定が読み込まれる /etc/httpd/conf.d 配下にファイルを作成して記述します。
ファイル名は任意ですが、vhost.confで作成します。

$ sudo su -
# cd /etc/httpd/conf.d
# vim vhost.conf

一旦必要な記述を記載しますが、Virtual Hostの詳しい記述方法については、他で調べてみてください。
アクセス予定のドメインは、blog.munakata.net を仮定しています。適宜変更ください。

vhost.conf
<VirtualHost *:80>
    DocumentRoot /home/munakata/html/blog/public
    ServerName blog.munakata.net
    ServerAlias blog.munakata.net
    <Directory "/home/munakata/html/blog/public">

        #.htaccessを利用可能にする
        AllowOverride All

        # Laravelで利用する環境変数を development に設定
        SetEnv APP_ENV development

        #アクセス許可
        Require all granted


    </Directory>
</VirtualHost>

httpd再起動

$ sudo systemctl restart httpd

Route53で、設定したドメインを紐付けるか、ご自身のマシンのhostsを設定して、ブラウザでアクセスしてみてください。
Laravelのトップページが表示されていれば、一旦完了です。

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

Vueをマウントできなくて奮闘

プログラミング歴3か月くらいの初心者です、自分のメモ用。

bladeファイル内になどを記載して、コンポーネントを挿入しても

app.js:38008 [Vue warn]: Failed to mount component: template or render function not defined.

found in

---> <コンポーネント名>
<Root>

上記のエラーが発生する。

app.jsにもしっかり記載している。

Vue.component('my-component', require('./components/MyComponent.vue'));

いろいろな記事を見て、webpack.config.jsに追記だの devコマンドを実行したかだのいろいろ対処法が
書かれていたがどれでも解決できないところ以下の記事を見つけた。

https://stackoverflow.com/questions/49138501/vue-warn-failed-to-mount-component-template-or-render-function-not-defined-i

どうやら app.jsで

Vue.component('my-component', require('./components/MyComponent.vue').default);

最後に.defaultを追記するだけでした。(意味は知らない)

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

Laravel+Nuxt.jsでDocker開発環境構築からHerokuデプロイまで

はじめに

本記事では「フレームワークをインストールして、それをインターネットに公開する」という0から1までのフェーズについて、Laravel+Nuxt.jsによって「蔵書管理」システムを構築して解説したいと思います。

また、実際に構築したシステムは下記になります。
- Heroku: https://frozen-castle-47874.herokuapp.com/
- Github: https://github.com/kon-shou/bcm-qiita-example

Peek 2019-07-08 23-45.gif

目次

  1. システムアーキテクチャ
  2. Laravel/Nuxt.jsインストール
  3. Docker環境構築
  4. Nginx設定
  5. Typescript対応
  6. サーバーでのモデル/ビジネスロジック実装
  7. フロントでのモデル/ビジネスロジック実装
  8. Heroku設定

システムアーキテクチャ

下記の技術スタックを用います。

サーバーサイド: Laravel 5.8
フロントサイド: Nuxt.js 2.8.1 (SPA)
Webサーバー: Nginx
開発環境構築: docker
デプロイ: Heroku

また処理フローの図は下記になります。

activity.png

「ブラウザにNuxt.jsで生成したSPAを返し、そのSPAからAPIサーバーであるLaravelにAPIリクエストを、Nginxを介して行う」というフローです。

Laravel/Nuxt.jsインストール

まずはLaravelドキュメントに従って Laravelでプロジェクトを作成します。

composer create-project --prefer-dist laravel/laravel book-collection-management

作成したLaravelプロジェクトに client というディレクトリを新設して、そこにNuxt.jsドキュメントに従って Nuxt.jsをインストールします。

yarn create nuxt-app client ./client

コマンド実行後の選択肢は各自の要望に応じて設定してください。
自分の場合、下記のように設定しました。

? Project name client
? Project description My fantastic Nuxt.js project
? Author name kon-shou
? Choose the package manager Yarn
? Choose UI framework Buefy
? Choose custom server framework None (Recommended)
? Choose Nuxt.js modules Axios
? Choose linting tools ESLint, Prettier
? Choose test framework None
? Choose rendering mode Single Page App

コマンドによって ./client にNuxt.jsがインストールされましたが、このままだとプロジェクトの二重管理になるため、下記を行います。

  • ./client/node_module を削除
    • rm -rdf ./client/node_modules
  • ./client の git 管理を解除
    • rm -rdf ./client/.git/
  • ./client にある各種設定ファイルを laravel のルートに移動&更新

設定ファイルの更新は各自の環境に合わせて行ってください。
ただ下記の2つのファイルは特に重要なので、注記します。

  • .package.json
  • nuxt.config.js
    • srcDir: 'client/' を追記
    • nuxt build によって生成される index.html の出力先を public/dist に変更
    • 修正後のnuxt.config.js

index.html の出力先の変更は、ドキュメントルートである public 以下に配置することを意図しています。
詳細は「Nginx設定」で解説します。

その index.html の出力先の変更は https://github.com/nuxt/nuxt.js/issues/3217 に従って

nuxt.config.js
  generate: {
    dir: 'public/dist'
  }

を追記することで出力先を変更することができます。

以上を実行した後のディレクトリ構造は下記になります。

(root)
├── .editorconfig
├── .env
├── .env.example
├── .eslintrc.js
├── .git
├── .gitattributes
├── .gitignore
├── .idea
├── .prettierrc
├── .styleci.yml
├── app
├── artisan
├── bootstrap
├── client
│   ├── README.md
│   ├── assets
│   ├── components
│   ├── layouts
│   ├── middleware
│   ├── pages
│   ├── plugins
│   ├── static
│   └── store
├── composer.json
├── composer.lock
├── config
├── database
├── node_modules
├── nuxt.config.js
├── package.json
├── phpunit.xml
├── public
├── readme.md
├── resources
├── routes
├── server.php
├── storage
├── tests
├── vendor
├── webpack.mix.js
└── yarn.lock

Docker環境構築

次は、DockerでWebサーバーとDBを構築して docker-compose up -d でアプリが立ち上がるようにします。
追加するファイルは下記になります。

(root)
├── Dockerfile              => docker-compose.yml の app.build.dockerfile で参照される
├── docker
│   ├── entrypoint-app.sh   => docker-compose.yml の app.command で参照される
│   ├── nginx.conf          => docker-compose.yml の app.volumes で参照される
│   └── php-fpm.conf        => docker-compose.yml の app.volumes で参照される
├── docker-compose.yml
└── scripts
    └── provisioning.sh     => docker-compose.yml の app.command で参照される

docker-compose.ymlは下記になります。

docker-compose.yml
version: '3'

services:
  mysql:
    image: mysql:5.7
    volumes: # host/docker間で共有するデータを指定
      - "${HOME}/book-management_mysql:/var/lib/mysql"
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 1
      TZ: "Asia/Tokyo"
    ports:
      - "3306:3306"
  app:
    build: . # Dockerfileのディレクトリを指定
    user: ubuntu
    volumes: # host/docker間で共有するデータを指定
      - .:/srv
      - ./docker/nginx.conf:/etc/nginx/sites-enabled/bcm
      - ./docker/php-fpm.conf:/etc/php/7.2/fpm/pool.d/bcm.conf
    command: docker/entrypoint-app.sh # 起動処理を設定
    depends_on:
      - mysql
    links:
      - mysql
    ports:
      - "8000:8000"
    working_dir: /srv

docker-compose up -d の処理の順番は下記になります。

  1. mysqlコンテナの起動
  2. appコンテナの起動
    1. Dockerfile ( provisioning.sh ) からdocker imageの作成
    2. nginx.conf / php-fpm.conf のマウント
    3. entrypoint-app.sh の nginx / php-fpm の起動

各ステップについて解説します。

1. mysqlコンテナの起動

mysqlコンテナは、mysql5.7のイメージを元にして、パスワード省略及びタイムゾーンを東京にして、ホスト/dockerの3306番ポートでアクセスを可能にしています。

ホストからmysqlコンテナへのアクセスは mysql -h 127.0.0.01 -uroot で行えます。
またappコンテナからmysqlへのアクセスは docker-compose exec app bash でappコンテナに入り mysql -h mysql -uroot で行えます。

2. appコンテナの起動

appコンテナは、Dockerfileで必要なライブラリをインストールしたイメージを準備し、/srv にプロジェクトディレクトリをマウントし、ホスト/dockerの8000番ポートでアクセスを可能にしています。

2.1 Dockerfile ( provisioning.sh ) からdocker imageの作成

Dockerfileは下記になります。

Dockerfile
FROM ubuntu:18.04

COPY scripts/provisioning.sh /tmp/provisioning.sh
# provisioning.sh による必要ライブラリのインストール
RUN /tmp/provisioning.sh

# nginxの初期設定を削除
RUN rm /etc/nginx/sites-enabled/default
# php-fpmの初期設定を削除
RUN rm /etc/php/7.2/fpm/pool.d/www.conf

# ubuntuユーザーを追加
RUN useradd -m -s /bin/bash -u 1000 -g users ubuntu

RUN apt install sudo
# ubuntuでのsudoのパスワード要求をしないように
RUN echo "ubuntu ALL=(ALL:ALL) NOPASSWD:ALL" >> /etc/sudoers
RUN chown ubuntu:users /srv

Dockerfileにて provisioning.sh によってライブラリをインストールし、その後にユーザー関連の設定を行います。
provisioning.sh は下記になります。

provisioning.sh
#!/usr/bin/env bash

function package_install() {
  env DEBIAN_FRONTEND=noninteractive apt install -y $1
}

apt-get update -y

package_install php-fpm
package_install php-mysql
package_install php-imagick
package_install php-gd
package_install php-curl
package_install php-mbstring
package_install php-bcmath
package_install php-xml
package_install php-zip
package_install php-redis
package_install php-intl
package_install nginx
package_install composer

package_install mysql-client

package_install zip
package_install unzip
package_install jq
package_install git
package_install jq
package_install vim
package_install curl

もし、appコンテナで追加のライブラリが必要になったら、この provisioning.sh に追記し、改めてイメージをビルドするのが良いかと思います。

2.2 nginx.conf / php-fpm.conf のマウント

この2つの設定ファイルを配置することで、nginx/php-fpmが正しく起動できるようになります。

php-fpm.conf は下記になります。

php-fpm.conf
[bcm]
user = ubuntu
group = users

listen = /run/php/bcm.sock

listen.owner = www-data
listen.group = www-data

pm = dynamic
pm.start_servers = 1
pm.max_children = 4
pm.min_spare_servers = 1
pm.max_spare_servers = 2

request_terminate_timeout = 300

chdir = /srv

php-fpm.conf については 公式のマニュアル でオプションを逐一調べていくのが早いかと思います。

またソケットがどういうものか理解するのかは https://qiita.com/kuni-nakaji/items/d11219e4ad7c74ece748 の記事が非常に分かりやすく、参考にさせていただきました。

nginx.conf の詳細については次の「Nginx設定」にて解説します。

2.3 entrypoint-app.sh の nginx / php-fpm の起動

entrypoint-app.sh は下記になります。

entrypoint-app.sh
#!/bin/bash

sudo service php7.2-fpm start
sudo service nginx start

tail -f /dev/null

内容としては、php-fpm/nginxの起動と、dockerが落ちないようにする処理です。

これをdocker起動処理として行うことで docker-compose up -d だけでphp-fpm/nginxの起動をさせることができます。

なお tail -f /dev/null の詳細については http://kimh.github.io/blog/jp/docker/gothas-in-writing-dockerfile-jp/#hack_to_run_container_in_the_background のブログ記事が非常にわかりやすく、参考にさせていただきました。

Nginx設定

Laravel/Nuxt.jsのインストールによって、初回アクセス時にブラウザに返却される index.html と、その index.html からのAPIアクセスを受ける index.php を準備できるようになりました。

そこで、下記の前提条件を元にして、Nginxの設定を行いたいと思います。

  • ドキュメントルートは /public とする
  • nuxt build によって生成される index.html 、及びその index.html から呼ばれる js ファイルは public/dist/ 以下に配置する
  • ブラウザに返された index.html からのAPIリクエストは public/index.php にルーティングする

この前提条件を満たす nginx.conf は下記になります。

nginx.conf
server {
    # Nginxが待ちうけるポートを指定
    listen 8000 default_server;

    # ドキュメントルートを指定
    root /srv/public;

    # {URL}/ の場合に {URL}/index.html を返す
    # index index.html (デフォルト)

    # /{任意の文字列} に前方一致するURLの場合に
    # 1. /srv/public/{任意の文字列} に一致するファイルが存在すればそれを返し、存在しなければ
    # 2. /dist/index.html?$query_string にリダイレクトする
    location / {
        try_files $uri /dist/index.html?$query_string;
    }

    # {任意の文字列1}/_nuxt/{任意の文字列2}に一致するURLの場合に
    # 1. /srv/public/{任意の文字列1}/_nuxt/{任意の文字列2} に一致するファイルが存在すればそれを返し、存在しなければ
    # 2. /dist/_nuxt/{任意の文字列2} にリダイレクトする
    location ~ /(_nuxt)/(.+)$ {
        try_files $uri /dist/$1/$2;
    }

    # {任意の文字列1}/api/{任意の文字列2}に一致するURLの場合に
    # 1. /srv/public/{任意の文字列1}/api/{任意の文字列2} に一致するファイルが存在すればそれを返し、存在しなければ
    # 2. /index.php?$query_string にリダイレクトする
    location ~ /api/ {
        try_files $uri /index.php?$query_string;
    }

    # {任意の文字列}.phpに一致するURLの場合に
    # 1. /srv/public/{任意の文字列}.php に一致するファイルが存在すれば、そのリクエストをFastCGIに渡し、存在しなければ404を返す
    # 2. /index.php?$query_string にリダイレクトする
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/run/php/bcm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

実際のアクセスでどういう処理になるかは下記になります。

順番 どういうアクセスか Nginxはどういう処理をするか
1 初回アクセス URLが / に一致するため srv/public/dist/index.html をブラウザに返却する
2 index.html<script> タグに記述された _nuxt/xxxx.js を取得するアクセス URLが /(_nuxt)/(.+)$ に一致するため srv/public/dist/_nuxt/xxxx.js をブラウザに返却する
3 index.htmlからの /api/xxx というAPIアクセス URLが /api/ に一致するため srv/public/index.php を経て、Laravelで実装したレスポンスをブラウザに返却する

この nginx.conf でルーティングがどうなるか読み解く鍵は下記かと思います。

  • locationの前方一致と正規表現の優先順位の違い
  • try_files の挙動

https://heartbeats.jp/hbblog/2012/04/nginx05.html の記事が非常に分かりやすく、参考にさせていただきました。

Typescript対応

必ずしも必要ではありませんが、Nuxt.jsの場合ではTypescriptの導入が容易であるため、ついでに導入します。

公式の導入手順 に従っていけば導入できます。

  • 必要ライブラリのインストール
yarn add -D @nuxt/typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser
yarn add ts-node vue-class-component vue-property-decorator
touch tsconfig.json
  • nuxt.config.js の修正

  • npm run dev 後に tsconfig.json の修正

  • lint対応

  • .vue ファイルをTypescript対応

    • 修正後のindex.vue ( フロントでのモデル/ビジネスロジック実装 で新規でpage/componentを作るのでここでの修正は必要ありませんが、参考までに)

以上を実行すれば、Typescript でフロントを実装できると思います。

サーバーでのモデル/ビジネスロジック実装

サーバで実装するのは下記の5つになります。

  • Bookモデル
  • migrationファイル
  • Bookレポジトリ
  • Bookコントローラ
  • ルーティング
(root)
├── app
│   ├── Eloquent
│   │   └── Book.php    => Bookモデル
│   ├── Http
│   │   ├── Controllers
│   │   │   └── BookController.php  => Bookコントローラ
│   └── Repository
│       └── BookRepository.php      => Bookレポジトリ
├── database
│   └── migrations
│       └── 2019_06_12_150818_create_books_table.php    => migrationファイル
└── routes
   └── api.php  => ルーティング

また、本の登録のAPIリクエストがフロントから来た場合の、フローは下記になります。
activity_server.png

それぞれについて解説します。

Bookモデル

Book.php
<?php

namespace App\Eloquent;

use Illuminate\Database\Eloquent\Model;

/**
 * Class Book
 * @package App\Eloquent
 *
 * @property int $id
 * @property int $title
 */
class Book extends Model
{
}

本には「ID」と「タイトル」のみ存在するものとして、Bookモデルを定義します。
今回の場合、最低限の記述で良いかと思います

migrationファイル

database/migrations/2019_06_12_150818_create_books_table.php
<?php

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

class CreateBooksTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('books', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('title');
            $table->timestamps();
        });
    }

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

BookモデルはActiveRecordであるため、モデルのプロパティと同様のカラムを持たせています。

Bookレポジトリ

BookRepository.php
<?php

namespace App\Repository;

use App\Eloquent\Book;
use Illuminate\Support\Collection;

class BookRepository
{
    public function find(int $id): ?Book
    {
        return Book::query()->find($id);
    }

    public function list(): Collection
    {
        return Book::query()->get();
    }

    public function create(string $title): Book
    {
        $book = new Book();
        $book->title = $title;

        $book->save();

        return $book;
    }

    public function delete(Book $book)
    {
        return Book::query()->find($book->id)->delete();
    }
}

コントローラで直接にデータを更新するのでなく、レポジトリでデータの更新を行い、コントローラはレポジトリのメソッドを呼び出すようにします。

このようにすることで「データが持たせ方が変わった場合(例: Bookの格納をRDSでなくNoSQLに変更する)に、修正範囲を限定させることが出来る」等々のメリットがあります。

Bookコントローラ

BookController.php
<?php

namespace App\Http\Controllers;

use App\Repository\BookRepository;
use Dotenv\Exception\ValidationException;
use Illuminate\Http\Request;

class BookController extends Controller
{
    private $bookRepository;

    public function __construct(BookRepository $bookRepository)
    {
        $this->bookRepository = $bookRepository;
    }

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

    public function create(Request $request)
    {
        $title = $request->get('title');
        if (!$title) {
            throw new ValidationException('titleは必須です');
        }

        return $this->bookRepository->create($title);
    }

    public function delete(int $id)
    {
        $book = $this->bookRepository->find($id);
        if (!$book) {
            throw new ValidationException('削除対象のbookが存在しません');
        }

        $this->bookRepository->delete($book);

        return [];
    }
}

コントローラでは、基本的にレポジトリのメソッドを呼び出すことで、Bookモデルの取得/登録/削除を行います。

また、BookRepositoryはコンストラクタでDIを行うことで、コントローラとレポジトリの依存関係を薄くしています。
こうすることで「ユニットテストでは、特別に用意したBookRepositoryを呼び出すことができ、ユニットテストの書きやすさが向上する」等々のメリットが有ります。

ルーティング

api.php
<?php

use Illuminate\Http\Request;

Route::group(['prefix' => 'book'], function ($route) {
    $route->get('/', 'BookController@list');
    $route->post('/create', 'BookController@create');
    $route->delete('/delete/{id}', 'BookController@delete');
});

本の取得/登録/削除の3つのエンドポイントを用意し、それぞれからコントローラの対応するメソッドを呼びます。

確認

実際にAPIリクエストによってDBが更新されるか確認するためには以下の手順を踏みます。

  1. .env の更新
  2. MySQL に database を追加
  3. migration実行

今回の場合だと mysqlコンテナの設定に合わせて、下記のように .env を更新します。

.env
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=

.env を更新した後に php artisan config:cache としてやることで .env の変更を反映させます。

その後、appコンテナから mysqlに接続 ( docker-compose exec app mysql -h mysql -uroot ) し

create database laravel;

とすることで laravel データベースを作成します。

そして php artisan migrate と実行することで、migrationが実行できるかと思います。

以上の準備をしたあとで、

curl http://localhost:8000/api/book/create -X POST -H "Content-Type: application/json" -d '{"title":"book_test"}'

と叩くことで、更新が行われ、そのレスポンスが帰ってくることが確認できるかと思います。

フロントでのモデル/ビジネスロジック実装

フロントでは下記の6つを実装します。

  • Bookモデル
  • Bookレポジトリ
  • plugins/dependency.ts
  • index.d.ts
  • Axios の proxy
  • index.vue
(root)
├── client
│   ├── domain
│   │   └── Book
│   │       ├── Book.ts
│   │       └── BookRepository.ts
│   ├── pages
│   │   ├── index.vue
│   ├── plugins
│   │   └── dependency.ts
│   └── static
├── index.d.ts
└── nuxt.config.js

フロントにおけるBook取得/登録のフローは下記です。

activity_front.png

なおフロントにレポジトリ層を持たせる設計については、拙作ではありますが こちらのスライド にて詳細を解説していますので、ご参考ください。

Bookモデル

Book.ts
import _ from 'lodash'

export default class Book {
  constructor(protected properties: { [key: string]: any }) {}

  get id(): string {
    return _.get(this.properties, 'id')
  }

  get title(): string {
    return _.get(this.properties, 'title')
  }
}

サーバーと同様のBookモデルを定義します。
これによって new Book({id: xxx, titile: yyy}) という形式でモデルを作ることができ、このモデルで型宣言を行うことができるようになります。

Bookレポジトリ

BookRepository.ts
import _ from 'lodash'
import { AxiosInstance } from 'axios'
import Book from '~/domain/Book/Book'

export default class BookRepository {
  private axios: AxiosInstance

  constructor(axios: AxiosInstance) {
    this.axios = axios
  }

  public async listBooks(): Promise<Book[]> {
    const response = await this.axios.get('/book/')
    return _.map(response.data, bookData => new Book(bookData))
  }

  public async createBook(title: string): Promise<Book> {
    const response = await this.axios.post('/book/create', {
      title: title
    })
    return new Book(response.data)
  }

  public async deleteBook(book: Book) {
    await this.axios.delete(`/book/delete/${book.id}`)
  }
}

こちらもサーバーと同様にBookRepositoryを実装します。
メリットについてもサーバーと同様です。

コンストラクタで axiso を注入をしていますが、これは後述する plugins/dependency.ts にて注入を行っています。
注入によって axios の context を維持しつつAPIリクエストを投げられるようになります。

plugins/dependency.ts

dependency.ts
import BookRepository from '~/domain/Book/BookRepository'

export default (context, inject) => {
  const bookRepository = new BookRepository(context.$axios)

  inject('bookRepository', bookRepository)
}

BookRepositoryに context.$axios を渡し、生成された bookRepository$bookRepository として Vue インスタンス内で呼び出せるように登録しています。

このプラグインを nuxt.config.ts にて呼び出します。

nuxt.config.ts
  plugins: ['~/plugins/dependency'],

なお、NuxtにおけるInjectionについては公式ドキュメントhttps://tech.cydas.com/entry/nuxt-inject の記事が非常にわかりやすく、参考にさせていただきました。

index.d.ts

dependency.ts にて $bookRepository を登録しましたが、このままだと Typescript のチェックでエラーが出るので型定義ファイルに $bookRepository に追加します。

index.d.ts
import BookRepository from '~/domain/Book/BookRepository'

declare module 'vue/types/vue' {
  interface Vue {
    $bookRepository: BookRepository
  }
}

Axios の proxy

BookRepository にて axios を利用していますが、このままだと 例えば本の登録では、Nginxがリッスンしている http://localhost:8000/api/book/create でなくhttp://localhost:3000/book/create に対してリクエストをしてしまうので、下記の修正を行います。

  • axios.baseURL の指定

下記を追記することで、urlのプレフィックスとして /api を付与します。

  axios: {
    baseURL: '/api'
  },
  • @nuxtjs/proxy の利用

nuxt の proxy-module を利用して、axiosからのリクエストを proxy します。

今回の場合は .env にAPIサーバーのURLを追記して、それを nuxt.config.ts に呼び出すことにします。

まず .env に下記を追加して

.env
API_BASE_URL=http://localhost:8000/api

次に nuxt.config.ts を修正します。

nuxt.config.ts
import NuxtConfiguration from '@nuxt/config'

require('dotenv').config()

const config: NuxtConfiguration = {
...
  modules: [
    // Doc: https://buefy.github.io/#/documentation
    'nuxt-buefy',
    // Doc: https://axios.nuxtjs.org/usage
    '@nuxtjs/axios',
    '@nuxtjs/eslint-module',
    '@nuxtjs/proxy'
  ],
...
}

if (process.env.API_BASE_URL) {
  config.proxy = [process.env.API_BASE_URL]
}

export default config

これによって axios のリクエストが http://localhost:8000/api に行われるようになります。

index.vue

最後に index.vue の修正を行います。

index.vue では主に下記の3つの処理を行います。

  • asynData() で $bookRepository.listBooks() を実行し、本の一覧を取得
  • 登録ボタンクリックで $bookRepository.createBook(this.title) を実行し、本を登録し、その後に一覧を再取得
  • 削除ボタンクリックで $bookRepository.deleteBook(book) を実行し、本を削除し、その後に一覧を再取得
index.vue
<template>
  <section>
    <table class="table">
      <tr>
        <th>ID</th>
        <th>タイトル</th>
      </tr>
      <tr v-for="book in books" :key="book.id">
        <td>{{ book.id }}</td>
        <td>{{ book.title }}</td>
        <td>
          <button
            type="button"
            class="button is-primary"
            @click.prevent="deleteBook(book)"
          >
            削除する
          </button>
        </td>
      </tr>
    </table>

    <section class="modal-card-body">
      <b-field label="本のタイトル">
        <b-input v-model="title" type="input" placeholder="タイトル" />
      </b-field>
      <button
        type="button"
        class="button is-primary"
        @click.prevent="createBook()"
      >
        登録する
      </button>
    </section>

    <p v-if="error">{{ error }}</p>
  </section>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import Book from '~/domain/Book/Book'

@Component({
  async asyncData({ app }) {
    const books = await app.$bookRepository.listBooks()

    return {
      books
    }
  }
})
export default class extends Vue {
  private books

  private title = ''
  private error = ''

  async createBook() {
    try {
      await this.$bookRepository.createBook(this.title)

      this.books = await this.$bookRepository.listBooks()
      this.title = ''
    } catch (e) {
      this.error = e
    }
  }

  async deleteBook(book: Book) {
    try {
      await this.$bookRepository.deleteBook(book)

      this.books = await this.$bookRepository.listBooks()
    } catch (e) {
      this.error = e
    }
  }
}
</script>

確認

以上のフロント/サーバーでの実装が完了した後に

  • docker-compose up -d
  • npm run dev

を実行すれば、 http://localhost:3000/ にてシステムが動作してることが確認できるかと思います。

Screenshot from 2019-07-09 00-22-41.png

Heroku設定

herokuの設定としては、下記の5つを実装します

  • infra/nginx/nginx.conf / Procfile の追加
  • heroku buildpack の追加
  • Clear DB の設定
  • app/Providers/AppServiceProvider.php の修正
  • 環境変数の設定
(root)
├── Procfile
├── app
│   └── Providers
│       └── AppServiceProvider.php
└── infra
    └── heroku
        └── nginx.conf

なお heroku create によるherokuとの連携は完了してる前提です。

infra/nginx/nginx.conf / Procfile の追加

herokuで Nginx を使うために infra/nginx/nginx.confherokuの公式ドキュメント に従って、新しく作成します。

  • location ディレクティブのみを使う
  • fastcgi_passheroku-fcgi を指定する

という点を反映させると、下記のようになると思います。

nginx.conf
location / {
    try_files $uri /public/dist/index.html?$query_string;
}

location ~ /_nuxt/(.+)$ {
    try_files $uri /public/dist/_nuxt/$1;
}

location ~ /api/ {
    try_files $uri /public/index.php?$query_string;
}

location ~ \.php$ {
    try_files $uri /public/index.php =404;
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass heroku-fcgi;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}

また、上記の nginx.conf を利用するように、下記の Procfile を追加します。

Procfile
web: vendor/bin/heroku-php-nginx -C infra/heroku/nginx.conf

Procfileの役割については 公式ドキュメント に記載が有ります。

heroku buildpack の追加

heroku にデプロイは git push heroku master で行いますが、その push 後の起動処理を行うためにビルドパックを追加します。

heroku buildpacks:add heroku/nodejs
heroku buildpacks:add heroku/php

これによって、フロントのビルドや必要ライブラリのインストールを実行されます。

Clear DB の設定

heroku でも mysql を利用するために ClearDB を利用したいと思います。

まず下記コマンドを叩きます。

heroku addons:add cleardb

すると CLEARDB_DATABASE_URL という変数が heroku config に設定されると思います。
この CLEARDB_DATABASE_URL は下記のように読み解けます。

mysql://{DB_USERNAME}:{DB_PASSWORD}@{DB_HOST}/{DB_DATABASE}?reconnect=true

そこで下記のようにHerokuの環境変数にセットすることで、laravelからDBアクセスを行えるようになります。

heroku config:set DB_HOST={DB_HOST}
heroku config:set DB_DATABASE={DB_DATABASE}
heroku config:set DB_USERNAME={DB_USERNAME}
heroku config:set DB_PASSWORD={DB_PASSWORD}

以上で、本来ならば Mysql接続が完了してほしいところですが、laravel の想定してる Mysql のバージョンとHerokuのMysql のバージョンが異なっているためか、migration 実行時にエラーが発生します。

そのため、下記のように修正を行います。

AppServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Schema::defaultStringLength(191);
    }
}

こちらの対応は https://qiita.com/beer_geek/items/6e4264db142745ea666f を参考にさせていただきました。

確認

以上の準備が完了した後に

  1. git push heroku master
  2. heroku run php artisan migrate

を実行すれば、実際にブラウザで動作していることが確認できると思います。

終わりに

以上で、Lareavel+Nuxt.jsで作ったwebアプリをHerokuにデプロイさせることが出来ました。

記事の内容で誤ってる点や追記が必要な点も多いと思いますが、その際にはコメントや編集リクエストでご指摘いただければと思います。

この記事がWeb開発における0から1へのフェーズに学習する方の参考になれば幸いです。

参考

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