20210213のlaravelに関する記事は16件です。

[Laravel]Gitを使わずアプリケーションを簡単にバックアップする

はじめに

パッケージlaravel-backupを使用すれば、ソースファイルやDBのバックアップが可能になるという記事を見て面白そうだと思い、バックアップするまでの一連の流れを記事にしました。デフォルトでは、バックアップの保存先はローカルに設定されていますが任意の場所に保存も可能なため、おまけとしてS3での保存方法も記載しています。

環境

  • PHP 7.3
  • Laravel 8.27

※PHPやLaravelのバージョンによって使用するlaravel-backupのバージョンが異なってきます。
詳しくはこちらを参照してください。

インストールとセットアップ

ここではlaravel-backupをインストールした後に、バックアップの設定を行います。

インストール

laravel-backupをインストールします。

$ composer require spatie/laravel-backup

設定ファイル作成

設定ファイルconfig/backup.phpを作成するために、下記コマンドを実行してください。

$ php artisan vendor:publish --provider="Spatie\Backup\BackupServiceProvider"
Copied File [/vendor/spatie/laravel-backup/config/backup.php] To [/config/backup.php]
Copied Directory [/vendor/spatie/laravel-backup/resources/lang] To [/resources/lang/vendor/backup]
Publishing complete.

実行結果として
config/backup.phpresources/lang/vendor/backupが生成されます。
ちなみにresources/lang/vendor/backup配下にはバックアップ失敗などで通知メッセージを送る際の言語ファイルが用意されています。言語ファイルは20カ国以上あり、日本語翻訳バージョンも用意されています。

バックアップ保存先の変更

バックアップの保存先はconfig/backup.phpdisksで設定されています。
今回はデフォルトのlocalで進めます。

config/backup.php
<?php
return [
    'backup' => [
        (省略)
        'destination' => [
            (省略)
            /*
             * The disk names on which the backups will be stored.
             */
            'disks' => [
                'local', //ここで設定
            ],
        ],
        (省略)
    ],
];

disksに設定のlocalはconfig/filesystems.phpのdisksと対応しています。

config/filesystems.php
<?php
return [
    (省略)
    'disks' => [
        'local' => [
            'driver' => 'local',
            'root' => storage_path('app'),
        ],
        'public' => [
            'driver' => 'local',
            'root' => storage_path('app/public'),
            'url' => env('APP_URL').'/storage',
            'visibility' => 'public',
        ],
        's3' => [
            'driver' => 's3',
            'key' => env('AWS_ACCESS_KEY_ID'),
            'secret' => env('AWS_SECRET_ACCESS_KEY'),
            'region' => env('AWS_DEFAULT_REGION'),
            'bucket' => env('AWS_BUCKET'),
            'url' => env('AWS_URL'),
            'endpoint' => env('AWS_ENDPOINT'),
        ],
        (省略)
    ],
];

通知先の設定

バックアップ実行時に通知あり/なしを指定できます(コマンドにて)。通知を指定した際に送信元、通知先が未設定だとエラーが発生しますので必ず設定する必要があります。設定ファイルはconfig/backup.phpになります。

config/backup.php
'notifications' => [

    // 省略

    'mail' => [
        // 通知先のメールアドレス
        'to' => 'your@example.com',

        // 送信元
        'from' => [
            'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
            'name' => env('MAIL_FROM_NAME', 'Example'),
        ],
    ],

通知設定についてはこちらの記事が参考になると思います。
参考:Laravelメール送信機能の実装でmailtrapやGmailのSMTPサーバーを使ってみる

バックアップ実行

バックアップのコマンドを確認したあとに、実行したいと思います。

バックアップのコマンドの確認

$ php artisan list

backup
  backup:clean         Remove all backups older than specified number of days in config.
  backup:list          Display a list of all backups.
  backup:monitor       Monitor the health of all backups.
  backup:run           Run the backup.

バックアップ(全て)

$ php artisan backup:run

Starting backup...
Dumping database LaravelBackup...
Determining files to backup...
Zipping 228 files and directories...
Created zip containing 228 files and directories. Size is 618.99 KB
Copying zip to disk named local...
Successfully copied zip to disk named local.
Backup completed!

成功していれば、storage/app/〇〇にzipファイルが作成されています。(〇〇は.envファイルのAPP_NAMEが入るはず)

バックアップ(DBのみ)

$ php artisan backup:run --only-db

バックアップ(ファイルのみ)

$ php artisan backup:run --only-files

バックアップ(通知無効)

$ php artisan backup:run --disable-notifications

バックアップ(DBのみ通知無効)

$ php artisan backup:run --only-db --disable-notifications

バックアップ(ファイルのみ通知無効)

$ php artisan backup:run --only-files --disable-notifications

おまけ

ローカル(storage/app/)に長期で保存すると容量の問題等から、別の場所に保存する方が良いかもしれません。今回、新しい保存先としてAWSのS3で設定する方法も紹介します。

※AWS,S3バケットの準備方法は省略しています。
詳しく知りたい方はこちらを参照すると良いと思います。
参考:Laravelをバックアップ!定期的にDBやファイルをAWSへ保存する

AWS情報を設定ファイルに記載

AWSで取得した情報を.envファイルに下記を追加

.env
AWS_DEFAULT_REGION=****** // リージョンを記載
AWS_BUCKET=****** // バケット名を記載
AWS_ACCESS_KEY_ID=****** //アクセスキーを記載
AWS_SECRET_ACCESS_KEY=****** //シークレットアクセスキーを記載

ストレージパッケージをインストール

S3へアップロードできるようにパッケージをインストールします。

composer require league/flysystem-aws-s3-v3:"^1.0"

保存先の変更

localからs3に変更してあげます。

config/backup.php
'destination' => [
            (省略)
            /*
             * The disk names on which the backups will be stored.
             */
            'disks' => [
                // 'local',
                's3',
            ],
        ],

バックアップ実行

$ php artisan backup:run --only-files

Determining files to backup...
Zipping 231 files and directories...
Created zip containing 231 files and directories. Size is 2.29 MB
Copying zip to disk named s3...
Successfully copied zip to disk named s3.
Backup completed!

S3を確認すると・・・
スクリーンショット 2021-02-13 21.36.52.png
きちんとバックアップ取れていました!

参考先

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

Attempt to read property on nullの解決

LaravelでAttempt to read property on nullが出た話

記事内容

本記事ではLaravelで自作の認証機能を実装していた際に、本題のエラーが出たので、その解決方法を書き下ろします。

開発環境

Laravel 8.22.1
PHP:8.0.1
MySQL:8.0.23
MacOS:11.1 ( Big Sur )

やろうとしていること

ビューで入力フォームと、データベースにcompaniesテーブルを作成し、
ユーザーから入力された値と、テーブルに格納されているデータ(カラム:company_id)と一致するものがあれば、ページを遷移させる。
一致するものがなければ、簡単なエラーメッセージを返すというもの。

テーブル

○○_create_companies_table.php
<?php

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

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

今回の認証に利用したのはcompany_idカラムのみの簡単なもの。

Blade

login.blade.php(一部)
<div class="cpylogin-block">
  <form action="/company/login" method="post">
    <label for="company_id">会社/団体ID</label>
    <input type="text" name="company_id" value="{{ old('company_id') }}" placeholder="ID" required>
    <div class="btn-area">
      <button>ログイン</button>
    </div>
    {{ csrf_field() }}
  </form>

フォームの入力値も1つのみ。inputタグのname属性をcompany_idとする。
ちなみにloginブレードはコントローラの記述より、route/web.phpから呼び出しています。

Controller

LoginController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Models\Company;

class CpyLoginController extends Controller
{
    //
    public function index(Request $request)
    {
        return view('pages.cpylogin');
    }

    public function post(Request $request)
    {
        // Companyモデルからcompany_idで情報を検出→オブジェクト化
        $company = Company::where('company_id', $request->company_id)->first();
        $msg = ['msg' => '入力されたIDは存在しません'];

        if ($request->company_id === $company->company_id) {
            return redirect()->route('usrlogin');
        }
        else {
            return view('pages.cpylogin', $msg);
        }
    }
}

・LoginControllerのpostメソッドで認証機能が動くように記述。
Authを使っていない上に、よくあるuserモデルでのログイン機能ではないことをあらかじめ把握しておいてください。

・if文のところで、フォームのinputタグに入力された値がcompanyモデルのcompany_idカラムに保存されているデータと一致するものがあれば、usrloginビューを表示する。

実行

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

Attempt to read property "company_id" on null とエラーになる。
日本語訳すると、「“company_id” プロパティをnullなのに読み込もうとしている。」の意味(少しズレてるかも)。

原因

エラーの原因はControllerの中の、companyモデルを呼び出す記述にありました。

LoginController.php
--- 省略 ---

    public function post(Request $request)
    {
        // Companyモデルからcompany_idで情報を検出→オブジェクト化
        $company = Company::where('company_id', $request->company_id)->first();

--- 省略 ---

    }

\$company変数の定義を間違えていました。
\$company変数を、\$request->company_idと、フォームで入力された値と紐付けるのではなく、
companyテーブルに存在する全てのデータをオブジェクト化したものにするべきでした。

解決策

ということで、\$company変数の定義を下記のように書き換えると解決しました。

LoginController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Models\Company;

class CpyLoginController extends Controller
{
    //
    public function index(Request $request)
    {
        return view('pages.cpylogin');
    }

    public function post(Request $request)
    {
        $validate_rule = $request->validate([
            'company_id' => ['required']
        ]);

        // Companyモデルからcompany_idで情報を検出→オブジェクト化
        $company = Company::all()->first();
        $msg = ['msg' => '入力されたIDは存在しません'];

        if ($request->company_id === $company->company_id) {
            return redirect()->route('usrlogin');
        }
        else {
            return view('pages.cpylogin', $msg);
        }
    }
}

\$company変数をCompanyモデルから全て取り出しオブジェクト化したデータと定義することで、うまくいきました。

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

VueとLaravelでStripeElementsを使ってクレジットカード情報を更新する方法

毎日投稿59日目

毎日記事を更新しているのですが、意外とネタは尽きないものです。

さて今回は、タイトルの通りStripeでクレジットカードの変更を行っていきます。

StripeのAPIについてはそこまで詳しく説明は致しません!

恐らくクレジットカードの情報を変更したいなと思った方は、Stripeの基礎的な部分は理解していると思うので。

イメージとしてはこんな感じです。

無題.png

また、HTMLやCSSの記述が多いのでここらへんはコピペしてください。

話はこの辺にして早速説明を見ていきましょう!

StripeElementsの設置

Stripeでクレジットカードの情報を変更するには、カード番号、有効期限、セキュリティコードを入力してもらいトークンを作成します。

そのトークンを使ってクレジットカードの情報を変更します。

とりあえずStripeElementsの設置

Stripe.vue
<template>
   <div class="group">
      <label for="card-element"> クレジットカード情報 </label>
      <div id="card-element"></div>
   </div>
</template>

<script>
export default {
  data() {
    return {
      stripe: null,
      card: null,
      token: ''
    }
  },
  async mounted() {
    this.stripe = window.Stripe(process.env.VUE_APP_STRIPE_PUBLIC_KEY)
    const elements = this.stripe.elements()
    this.card = await elements.create('card', {
      hidePostalCode: true,
      style: {
        base: {
          iconColor: '#666EE8',
          color: '#31325F',
          lineHeight: '40px',
          fontWeight: 300,
          fontFamily: 'Helvetica Neue',
          fontSize: '15px',

          '::placeholder': {
            color: '#CFD7E0'
          }
        }
      }
    })
    this.card.mount('#card-element')
  },
}
</script>

これでクレジットカードを入力する要素が表示されると思います。

必要項目の入力

次に、住所や名前を入力してもらうフォームを作成します。

ここに関しては先ほどの記述を一旦消してコピペしてください。

CSSの記述も多いので。

Stripe.vue
<template>
  <div class="creditcard-change">
    <div class="cell">
          <form>
            <div class="group">
              <label for="card-element"> クレジットカード情報 </label>
              <div id="card-element"></div>
            </div>
            <div class="group">
              <label>
                <span>氏名</span>
                <input
                  id="name"
                  name="name"
                  type="text"
                  class="field"
                  placeholder="YOSHIHIRO FUJIWARA"
                  autocomplete="name"
                />
              </label>
              <label>
                <span>メールアドレス</span>
                <input
                  id="email"
                  v-model="email"
                  type="email"
                  name="email"
                  class="field"
                  placeholder="zaemonia@example.com"
                  autocomplete="email"
                />
              </label>
            </div>
            <div class="group">
              <label>
                <span>郵便番号</span>
                <input
                  id="postal-code"
                  name="address_line1"
                  type="text"
                  class="field"
                  placeholder="1234567"
                  maxlength="7"
                  autocomplete="postal-code"
                />
              </label>
              <label>
                <span>都道府県</span>
                <input
                  id="address-state"
                  name="address_state"
                  type="text"
                  class="field"
                  placeholder="神奈川県"
                  autocomplete="address-level1"
                />
              </label>
              <label>
                <span>市区町村</span>
                <input
                  id="address-city"
                  name="address_city"
                  type="text"
                  class="field"
                  placeholder="相模原市緑区二本松"
                  autocomplete="address-level2"
                />
              </label>
              <label>
                <span>番地</span>
                <input
                  id="address-line1"
                  name="address-line1"
                  type="text"
                  class="field"
                  placeholder="1-34-10"
                  autocomplete="address-line1"
                />
              </label>
              <label>
                <span>マンション・アパート名</span>
                <input
                  id="address-line2"
                  name="address-line2"
                  type="text"
                  class="field"
                  placeholder="マンションズタワー502"
                  autocomplete="address-line2"
                />
              </label>
            </div>
            <b-button
              class="recaptcha-button-v3"
              type="is-success"
              disabled="true"
              style="width: 100%; padding: 25px 0"
              @click="submit"
            >
              クレジットカードを変更する
            </b-button>
          </form>
        </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      stripe: null,
      card: null,
      token: ''
    }
  },
  async mounted() {
    this.stripe = window.Stripe(process.env.VUE_APP_STRIPE_PUBLIC_KEY)
    const elements = this.stripe.elements()
    this.card = await elements.create('card', {
      hidePostalCode: true,
      style: {
        base: {
          iconColor: '#666EE8',
          color: '#31325F',
          lineHeight: '40px',
          fontWeight: 300,
          fontFamily: 'Helvetica Neue',
          fontSize: '15px',

          '::placeholder': {
            color: '#CFD7E0'
          }
        }
      }
    })
    this.card.mount('#card-element')
  },
  methods: {
    submit() {
      const options = {
        name: document.getElementById('name').value,
        email: document.getElementById('email').value,
        address_line1: document.getElementById('address-line1').value,
        address_line2: document.getElementById('address-line2').value,
        address_city: document.getElementById('address-city').value,
        address_state: document.getElementById('address-state').value,
        address_zip: document.getElementById('postal-code').value,
        address_country: 'JP'
      }
      Object.keys(options).forEach((value) => {
        if (value === '') {
          alert('未入力の項目があります')
        }
      })
      this.stripe.createToken(this.card, options).then((result) => {
        // エラーの場合
        if (result.error) {
          alert(result.error.message)
          // 成功の場合
        } else {
          this.token = result.token.id
        }
      })
    },
  }
}
</script>

<style scoped>
.creditcard-change {
  padding: 120px 0;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  min-height: 100%;
  width: 550px;
  margin: 0 auto;
}
.creditcard-change > div {
  flex: 0 0 100%;
}
/* クレジットカード入力欄 */
.cell {
  margin-top: 30px;
  background: #e6ebf1;
  padding: 30px;
}
.group {
  background: white;
  box-shadow: 0 7px 14px 0 rgba(49, 49, 93, 0.1), 0 3px 6px 0 rgba(0, 0, 0, 0.08);
  border-radius: 4px;
  margin-bottom: 20px;
}

.cell label {
  position: relative;
  color: #8898aa;
  font-weight: 300;
  height: 100%;
  line-height: 40px;
  margin-left: 20px;
  display: flex;
  flex-direction: row;
}

.group label:not(:last-child) {
  border-bottom: 1px solid #f0f5fa;
}

label > span {
  width: 120px;
  text-align: right;
  margin-right: 30px;
}

.field {
  background: transparent;
  font-weight: 300;
  border: 0;
  color: #31325f;
  outline: none;
  flex: 1;
  padding-right: 10px;
  padding-left: 10px;
  cursor: text;
}

.field::-webkit-input-placeholder {
  color: #cfd7e0;
}

.field::-moz-placeholder {
  color: #cfd7e0;
}

.outcome {
  float: left;
  width: 100%;
  padding-top: 8px;
  min-height: 24px;
  text-align: center;
}
</style>

b-button初めて気になった方はこちらの記事をご覧ください。

初心者必見!サイト制作は楽してなんぼ。CSSフレームワークBuefyの紹介!!

Buefyはほんとにおススメなのでぜひ使ってください!

クレジットの更新

submitで取得したresult.token.idをバックエンドに送ってください。

適当にaxiosとかで。

そしたら、Laravelのコントローラーでクレジットカードを更新します。

StripeController.php
        public function creditUpdate(Request $request) {
        // ユーザーのクレジットカード情報の更新
        require_once(__DIR__.'/../../../vendor/autoload.php');

        $secret_key = config('app.STRIPE_SECRET_KEY');

        $stripe = new \Stripe\StripeClient($secret_key);

        // カスタマーID、トークンの取得
        $customer_id = $request->id;
        $token = $request->token;

        $customer = $stripe->customers->retrieve(
            $customer_id,
            []
        );

        // クレジットを登録していない場合、処理を終了
        if(!$customer) {
            return response()->json([
                'message' => 'The customer is undefiend',
            ], 400);
        }

        // ソースの作成
        $new_card = $stripe->customers->createSource(
            $customer_id,
            ['source' => $token]
        );
        $customer->default_source = $new_card->id;
        $customer->save();

        return response()->json([
            'data' => $new_card,
            'message' => 'Updating customer`s paymentmethods is success',
        ], 200);
    }

$stripe->customers->createSourceに先ほど取得したトークンをソースとして登録することでクレジットカードの情報を更新することができます。

save()忘れないように!!

実際に変更をされているかはStripeの画面から確認して見て下さい!

いかがだったでしょうか??

記述が多いので嫌かもしれないですが、本当にコピペしてもらって結構です。

むしろコピペしてください!

また、クレジットカードの情報を取得したい場合はStripeの公式ドキュメントを参照してください!

https://stripe.com/docs/api

以上、「VueとLaravelでStripeElementsを使ってクレジットカード情報を更新する方法」でした!

良ければ、LGTM、コメントお願いします。

また、何か間違っていることがあればご指摘頂けると幸いです。

他にも初心者さん向けに記事を投稿しているので、時間があれば他の記事も見て下さい!!

Thank you for reading

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

laravel6でS3に画像アップロード&削除

S3に画像をアップロードする方法

LaravelでAWS S3へ画像をアップロードする
こちらの記事を参考にして無事S3に画像を保存できました

S3の画像の削除

※先程の記事の一部を引用し、削除コードを書いていきます
参考記事のコード = PostsController.php
実装コード = exampleController.php

削除ボタンを押すと、このコントローラが走るようにルーティング

exampleController.php
public function delete(Request $request) {
        $image = $request->image_path;
        $s3_delete = Storage::disk('s3')->delete($image);
        $db_delete = Post::where('image',$image)->delete();
        return redirect('hogehoge');
    }

これでDBとS3から画像を削除!
とおもいきや、Dbからは削除されるのにS3からは削除できていない

S3から削除できない原因

PostsController.php
$post->image_path = Storage::disk('s3')->url($path);

参考記事ではこのように、パスにURL関数を使いそれをDBに保存しているので、DBにはURLを含んだパスが保存されています。

S3はvi3kAwK0z0X19XU3LWcz63101t0rYilRHiWG9MmC.jpg

DBはhttps://example.s3-ap-northeast-1.amazonaws.com/myprefix/vi3kAwK0z0X19XU3LWcz63101t0rYilRHiWG9MmC.jpg
のように保存されているため、
exampleController.php$imageはこうなっています。

exampleController.php
$image = https://example.s3-ap-northeast-1.amazonaws.com/myprefix/vi3kAwK0z0X19XU3LWcz63101t0rYilRHiWG9MmC.jpg;

なのでS3に「そんなものないよ!」と言われているので削除ができないということでした。

解決策

参考記事をこのように変更します。

PostsController.php
public function create(Request $request)
  {
      $post = new Post;
      $form = $request->all();

      //s3アップロード開始
      $image = $request->file('image');
      // バケットの`myprefix`フォルダへアップロード
      $path = Storage::disk('s3')->putFile('myprefix', $image, 'public');
      // アップロードした画像のフルパスを取得
+     $post->image_path = $path; //追加
-     $post->image_path = Storage::disk('s3')->url($path); //削除

      $post->save();

      return redirect('posts/create');
  }

こうすることで、S3とDBに保存される名前を一致できたのでexampleController.phpを内容は変更せず削除ができるようになりました。

View

srcにhttps://example.s3-ap-northeast-1.amazonaws.com/を追加すると表示することができました!

<img src="https://example.s3-ap-northeast-1.amazonaws.com/{{ $my_image['image'] }}" >

参考

LaravelでAWS S3へ画像をアップロードする
LaravelでS3のファイル操作をする方法まとめ

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

ゼロからDockerでLaravel+Nuxt TypeScript+MySQLの環境構築をしてトップページを表示するまで【備忘録】

前提

ローカル環境

  • macOS Catalina10.15.5
  • Docker for mac 2.3.0.4

バージョン

  • PHP...7.3
  • Laravel...7.30.1
  • Node.js 14.6
  • nuxt/types 2.14
  • MySQL 5.7

その他

  • 最終的なディレクトリ構成
Terminal
$ tree -a -L 2 .
.
├── .env
├── .env.example
├── .gitignore
├── .idea
│   ├── modules.xml
│   ├── myapp.iml
│   └── workspace.xml
├── docker
│   ├── nginx
│   ├── node
│   └── php
├── docker-compose.yml
├── frontApp
│   ├── .editorconfig
│   ├── .git
│   ├── .gitignore
│   ├── .nuxt
│   ├── .prettierrc
│   ├── README.md
│   ├── assets
│   ├── components
│   ├── layouts
│   ├── middleware
│   ├── node_modules
│   ├── nuxt.config.js
│   ├── package.json
│   ├── pages
│   ├── plugins
│   ├── static
│   ├── store
│   ├── tsconfig.json
│   └── yarn.lock
└── laravel
    ├── .editorconfig
    ├── .env
    ├── .env.example
    ├── .gitattributes
    ├── .gitignore
    ├── .styleci.yml
    ├── README.md
    ├── app
    ├── artisan
    ├── bootstrap
    ├── composer.json
    ├── composer.lock
    ├── config
    ├── database
    ├── package.json
    ├── phpunit.xml
    ├── public
    ├── resources
    ├── routes
    ├── storage
    ├── tests
    ├── vendor
    └── webpack.mix.js

プロジェクト用ディレクトリを用意

Terminal
$ mkdir -p ~/workspace/myapp
$ cd ~/workspace/myapp
$ mkdir -p laravel docker/{php,nginx,node}
$ mkdir docker/nginx/{conf.d,logs}

.envの準備

先に.env.exampleを編集してからコピーして.envをつくる

myapp
$ touch .env.example .gitignore

.env.exampleと.gitignoreを編集

.env.example
PROJECT_PATH=./laravel
TZ=Asia/Tokyo
WEB_PORT=10080
DB_PORT=13306
DB_TESTING_PORT=13307
DB_CONNECTION=mysql
DB_NAME=myapp
DB_USER=willrewrite
DB_PASS=willrewrite
DB_ROOT_PASS=willrewrite
MAILHOG_PORT=18025
MAIL_HOST=mail
MAIL_PORT=1025

COMPOSE_HTTP_TIMEOUT=70
FRONT_PORT=3000
.gitignore
.env
.idea
# その他個別に追加

.env.exampleをコピー

myapp
$ cp .env.example .env 

コピー後、個別のプロジェクトに合わせて.envの環境変数を上書きする

必要なファイルを作成

myapp
$ touch docker-compose.yml \
docker/php/{php.ini,Dockerfile} \
docker/nginx/{conf.d/default.conf,Dockerfile} \
docker/node/Dockerfile

各ファイルを編集

docker-compose.yml
version: "3"

services:
  web:
    build:
      context: .
      dockerfile: ./docker/nginx/Dockerfile
      args:
        - TZ=${TZ}
    volumes:
      - ./docker/nginx/logs:/etc/nginx/logs
      - ./docker/nginx/conf.d:/etc/nginx/conf.d
      - ${PROJECT_PATH}:/var/www/laravel
    ports:
      - ${WEB_PORT}:80
    links:
      - app
    depends_on:
      - app

  app:
    build:
      context: .
      dockerfile: ./docker/php/Dockerfile
      args:
        - TZ=${TZ}
    volumes:
      - ${PROJECT_PATH}:/var/www/laravel
    links:
      - db_master
    environment:
      - DB_CONNECTION=${DB_CONNECTION}
      - DB_HOST=db_master
      - DB_DATABASE=${DB_NAME}
      - DB_USERNAME=${DB_USER}
      - DB_PASSWORD=${DB_PASS}
      - TZ=${TZ}
      - MAIL_HOST=${MAIL_HOST}
      - MAIL_PORT=${MAIL_PORT}

  db_master:
    image: mysql:5.7
    environment:
      - MYSQL_DATABASE=${DB_NAME}
      - MYSQL_USER=${DB_USER}
      - MYSQL_PASSWORD=${DB_PASS}
      - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASS}
      - TZ=${TZ}
    ports:
      - ${DB_PORT}:3306

  front_app:
    build:
      context: .
      dockerfile: ./docker/node/Dockerfile
      args:
        - TZ=${TZ}
    volumes:
      - ./frontApp:/var/www/frontApp
    ports:
      - ${FRONT_PORT}:3000
    links:
      - web
    depends_on:
      - web

Nginxの設定

docker/nginx/Dockerfile
FROM nginx:1.15.7-alpine
# timezone
ARG TZ
COPY ./docker/nginx/conf.d/ /etc/nginx/conf.d/
COPY ./docker/nginx/logs/ /etc/nginx/logs/
COPY ./laravel/ /var/www/laravel/
RUN apk update && apk --update add tzdata && \
  cp /usr/share/zoneinfo/${TZ} /etc/localtime && \
  apk del tzdata && \
  rm -rf /var/cache/apk/*
docker/nginx/conf.d/default.conf
server {
    listen       0.0.0.0:80;
    # server_nameで設定した名前をローカルマシンの/etc/hostsにも設定する
    server_name  www.example.org example.org;
    charset      utf-8;
    client_max_body_size 3M;

    root /var/www/laravel/public;

    index index.php;

    location / {
        access_log  /etc/nginx/logs/access.log main;
        error_log   /etc/nginx/logs/error.log;
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        fastcgi_pass  app:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include       fastcgi_params;
    }
}

PHPの設定

docker/php/Dockerfile
FROM php:7.3.0RC6-fpm-alpine3.8
# timezone
ARG TZ
WORKDIR /var/www/laravel
COPY ./docker/php/php.ini /usr/local/etc/php/
COPY ./laravel/ /var/www/laravel/

RUN apk upgrade --update && apk add --no-cache \
  coreutils freetype-dev jpeg-dev libjpeg-turbo-dev libpng-dev libmcrypt-dev \
  git vim unzip tzdata \
  libltdl && \
  docker-php-ext-install pdo_mysql mysqli mbstring && \
  docker-php-ext-install -j$(nproc) iconv && \
  docker-php-ext-configure gd \
  --with-freetype-dir=/usr/include/ \
  --with-jpeg-dir=/usr/include/ && \
  docker-php-ext-install -j$(nproc) gd && \
  cp /usr/share/zoneinfo/${TZ} /etc/localtime && \
  apk del tzdata && \
  rm -rf /var/cache/apk/*

RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
docker/php/php.ini
log_errors = on
error_log = /var/log/php/php-error.log
upload_max_filesize = 3M
memory_limit = -1
post_max_size = 100M
max_execution_time = 900
max_input_vars = 100000
default_charset = UTF-8

[Date]
date.timezone = ${TZ}
[mbstring]
mbstring.internal_encoding = "UTF-8"
mbstring.language = "Japanese"

Nodeの設定

docker/node/Dockerfile
FROM node:14.6.0-alpine
RUN mkdir -p /var/www/frontApp
# timezone
ARG TZ
WORKDIR /var/www/frontApp
COPY ./frontApp/ /var/www/frontApp/

RUN apk update && \
    apk upgrade && \
    apk add --no-cache make gcc g++ python && \
    yarn install

EXPOSE 3000

ENTRYPOINT ["yarn", "run", "dev"]

nuxt-tsのプロジェクト作成

myapp
$ yarn create nuxt-app frontApp

yarn create v1.22.4
[1/4] ?  Resolving packages...
[2/4] ?  Fetching packages...
[3/4] ?  Linking dependencies...
[4/4] ?  Building fresh packages...
warning Your current version of Yarn is out of date. The latest version is "1.22.5", while you're on "1.22.4".
info To upgrade, run the following command:
$ curl --compressed -o- -L https://yarnpkg.com/install.sh | bash
success Installed "create-nuxt-app@3.5.2" with binaries:
      - create-nuxt-app

create-nuxt-app v3.5.2
✨  Generating Nuxt.js project in frontApp
? Project name: frontApp
? Programming language: TypeScript
? Package manager: Yarn
? UI framework: None
? Nuxt.js modules: Axios - Promise based HTTP client
? Linting tools: Prettier
? Testing framework: None
? Rendering mode: Universal (SSR / SSG)
? Deployment target: Server (Node.js hosting)
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Continuous integration: None
? Version control system: Git

一旦サーバを立ち上げ画面確認

myapp
$ cd frontApp
$ yarn run dev
  • localhost:3000にアクセスして下記の画面を確認したあと、command + Cでサーバストップ

top.jpg

  • コンテナ起動時に備えpackage.jsonを修正
frontApp/package.json
{
  "name": "frontApp",
  "version": "1.0.0",
  "private": true,
  "scripts": {
-   "dev": "nuxt-ts",
+   "dev": "HOST=0.0.0.0 PORT=3000 nuxt-ts",
    "build": "nuxt-ts build",
-   "start": "nuxt-ts start",
+   "start": "HOST=0.0.0.0 PORT=3000 nuxt-ts start",
    "generate": "nuxt-ts generate"
  },
  // 以下略
}

laravelディレクトリ直下にLaravelのプロジェクト作成

myapp
$ docker-compose run app composer create-project --prefer-dist laravel/laravel .

./laravelディレクトリ以下が次のような構成になっていればOK

myapp
$ tree -a -L 1 laravel
laravel
├── .editorconfig
├── .env
├── .env.example
├── .gitattributes
├── .gitignore
├── .styleci.yml
├── README.md
├── app
├── artisan
├── bootstrap
├── composer.json
├── composer.lock
├── config
├── database
├── package.json
├── phpunit.xml
├── public
├── resources
├── routes
├── server.php
├── storage
├── tests
├── vendor
└── webpack.mix.js

コンテナをバックグラウンドで起動

myapp
$ docker-compose up -d --build

Laravelの設定ファイルをキャッシュ等

myapp
$ docker-compose exec app ash
$ php artisan -V   // バージョンが表示されればOK
$ php artisan config:cache
$ php artisan migrate
$ chmod -R a+rw storage/ bootstrap/cache
$ exit

画面確認

  • 再度localhost:3000にアクセスし、TOP画面が表示されれば成功 top.jpg

Nuxt、Laravel間での通信を確認

Laravel側の設定

laravel/routes/api.php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

/*
 * 中略
 */
+ Route::get('/', function () {
+    return 'Hello';
+ });

Nuxt側の設定

myapp
$ cd frontApp
$ yarn add @nuxtjs/proxy @nuxtjs/dotenv
frontApp/nuxt.config.js
+ const environment = process.env.NODE_ENV || 'development'
+ require('dotenv').config()

export default {
// (中略)

  modules: [
    // https://go.nuxtjs.dev/axios
    '@nuxtjs/axios',
+   '@nuxtjs/proxy',
+   '@nuxtjs/dotenv',
  ],

+ proxy: {
+   '/api':
+     environment === 'development'
+       ? process.env.API_URL
+       : 'https://www.risk-exam.site',
+ },

+ axios: {
+   baseURL: process.env.API_URL,
+   browserBaseURL: process.env.API_BROWSER_URL,
+   credentials: true,
+ },

}
myapp/frontApp
$ touch .env
frontApp/.env
API_URL=http://web/api
API_BROWSER_URL=http://localhost:10080/api
frontApp/pages/index.vue
<template>
  <div class="container">
    <div>
      <Logo />
      <h1 class="title">
         {{ greet }} // デフォルトの「frontApp」から変更
      </h1>
      <div class="links">
        <a
          href="https://nuxtjs.org/"
          target="_blank"
          rel="noopener noreferrer"
          class="button--green"
        >
          Documentation
        </a>
        <a
          href="https://github.com/nuxt/nuxt.js"
          target="_blank"
          rel="noopener noreferrer"
          class="button--grey"
        >
          GitHub
        </a>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  async asyncData({ app }): Promise<object> {
    const greet: string = await app.$axios.$get('/').catch((err) => err)
    return { greet }
  }
})
</script>
// 以下略
  • localhost:3000にアクセスして、タイトルがHelloに変わっていたらOK

hello.jpg

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

LaravelのDB接続、というか .env でハマった話

Laravel 5.8 から PostgreSQL に接続しようとして .env に必要な情報を記述し、接続テストをすると
"password authentication failed"
というエラーになってつながらない。

同じ設定でPDOを直接使って接続したら普通に接続できる。
数時間悩んで試行錯誤して、原因が判明。
パスワードに "#" が含まれていて .env に

DB_PASSWORD=abc#123

のように書いていたので、 "#"以降がコメントと見なされていた。

DB_PASSWORD="abc#123"

とダブルクォートで囲んだら接続できた。
アホ過ぎるorz

教訓

"#" を含む可能性がある文字列(パスワード、何かのキーなど)を .env に書くときはダブルクォートで囲んでおいた方が無難。

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

ゼロからDockerでLaravel+Nuxt-ts+MySQLの環境構築をしてトップページを表示するまで【備忘録】

前提

ローカル環境

  • macOS Catalina10.15.5
  • Docker for mac 2.3.0.4

バージョン

  • PHP...7.3
  • Laravel...7.30.1
  • Node.js 14.6
  • nuxt/types 2.14
  • MySQL 5.7

その他

  • 最終的なディレクトリ構成
Terminal
$ tree -a -L 2 .
.
├── .env
├── .env.example
├── .gitignore
├── .idea
│   ├── modules.xml
│   ├── myapp.iml
│   └── workspace.xml
├── docker
│   ├── nginx
│   ├── node
│   └── php
├── docker-compose.yml
├── frontApp
│   ├── .editorconfig
│   ├── .git
│   ├── .gitignore
│   ├── .nuxt
│   ├── .prettierrc
│   ├── README.md
│   ├── assets
│   ├── components
│   ├── layouts
│   ├── middleware
│   ├── node_modules
│   ├── nuxt.config.js
│   ├── package.json
│   ├── pages
│   ├── plugins
│   ├── static
│   ├── store
│   ├── tsconfig.json
│   └── yarn.lock
└── laravel
    ├── .editorconfig
    ├── .env
    ├── .env.example
    ├── .gitattributes
    ├── .gitignore
    ├── .styleci.yml
    ├── README.md
    ├── app
    ├── artisan
    ├── bootstrap
    ├── composer.json
    ├── composer.lock
    ├── config
    ├── database
    ├── package.json
    ├── phpunit.xml
    ├── public
    ├── resources
    ├── routes
    ├── storage
    ├── tests
    ├── vendor
    └── webpack.mix.js

プロジェクト用ディレクトリを用意

Terminal
$ mkdir -p ~/workspace/myapp
$ cd ~/workspace/myapp
$ mkdir -p laravel docker/{php,nginx,node}
$ mkdir docker/nginx/{conf.d,logs}

.envの準備

先に.env.exampleを編集してからコピーして.envをつくる

myapp
$ touch .env.example .gitignore

.env.exampleと.gitignoreを編集

.env.example
PROJECT_PATH=./laravel
TZ=Asia/Tokyo
WEB_PORT=10080
DB_PORT=13306
DB_TESTING_PORT=13307
DB_CONNECTION=mysql
DB_NAME=myapp
DB_USER=willrewrite
DB_PASS=willrewrite
DB_ROOT_PASS=willrewrite
MAILHOG_PORT=18025
MAIL_HOST=mail
MAIL_PORT=1025

COMPOSE_HTTP_TIMEOUT=70
FRONT_PORT=3000
.gitignore
.env
.idea
# その他個別に追加

.env.exampleをコピー

myapp
$ cp .env.example .env 

コピー後、個別のプロジェクトに合わせて.envの環境変数を上書きする

必要なファイルを作成

myapp
$ touch docker-compose.yml \
docker/php/{php.ini,Dockerfile} \
docker/nginx/{conf.d/default.conf,Dockerfile} \
docker/node/Dockerfile

各ファイルを編集

docker-compose.yml
version: "3"

services:
  web:
    build:
      context: .
      dockerfile: ./docker/nginx/Dockerfile
      args:
        - TZ=${TZ}
    volumes:
      - ./docker/nginx/logs:/etc/nginx/logs
      - ./docker/nginx/conf.d:/etc/nginx/conf.d
      - ${PROJECT_PATH}:/var/www/laravel
    ports:
      - ${WEB_PORT}:80
    links:
      - app
    depends_on:
      - app

  app:
    build:
      context: .
      dockerfile: ./docker/php/Dockerfile
      args:
        - TZ=${TZ}
    volumes:
      - ${PROJECT_PATH}:/var/www/laravel
    links:
      - db_master
    environment:
      - DB_CONNECTION=${DB_CONNECTION}
      - DB_HOST=db_master
      - DB_DATABASE=${DB_NAME}
      - DB_USERNAME=${DB_USER}
      - DB_PASSWORD=${DB_PASS}
      - TZ=${TZ}
      - MAIL_HOST=${MAIL_HOST}
      - MAIL_PORT=${MAIL_PORT}

  db_master:
    image: mysql:5.7
    environment:
      - MYSQL_DATABASE=${DB_NAME}
      - MYSQL_USER=${DB_USER}
      - MYSQL_PASSWORD=${DB_PASS}
      - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASS}
      - TZ=${TZ}
    ports:
      - ${DB_PORT}:3306

  front_app:
    build:
      context: .
      dockerfile: ./docker/node/Dockerfile
      args:
        - TZ=${TZ}
    volumes:
      - ./frontApp:/var/www/frontApp
    ports:
      - ${FRONT_PORT}:3000
    links:
      - web
    depends_on:
      - web

Nginxの設定

docker/nginx/Dockerfile
FROM nginx:1.15.7-alpine
# timezone
ARG TZ
COPY ./docker/nginx/conf.d/ /etc/nginx/conf.d/
COPY ./docker/nginx/logs/ /etc/nginx/logs/
COPY ./laravel/ /var/www/laravel/
RUN apk update && apk --update add tzdata && \
  cp /usr/share/zoneinfo/${TZ} /etc/localtime && \
  apk del tzdata && \
  rm -rf /var/cache/apk/*
docker/nginx/conf.d/default.conf
server {
    listen       0.0.0.0:80;
    # server_nameで設定した名前をローカルマシンの/etc/hostsにも設定する
    server_name  www.example.org example.org;
    charset      utf-8;
    client_max_body_size 3M;

    root /var/www/laravel/public;

    index index.php;

    location / {
        access_log  /etc/nginx/logs/access.log main;
        error_log   /etc/nginx/logs/error.log;
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        fastcgi_pass  app:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include       fastcgi_params;
    }
}

PHPの設定

docker/php/Dockerfile
FROM php:7.3.0RC6-fpm-alpine3.8
# timezone
ARG TZ
WORKDIR /var/www/laravel
COPY ./docker/php/php.ini /usr/local/etc/php/
COPY ./laravel/ /var/www/laravel/

RUN apk upgrade --update && apk add --no-cache \
  coreutils freetype-dev jpeg-dev libjpeg-turbo-dev libpng-dev libmcrypt-dev \
  git vim unzip tzdata \
  libltdl && \
  docker-php-ext-install pdo_mysql mysqli mbstring && \
  docker-php-ext-install -j$(nproc) iconv && \
  docker-php-ext-configure gd \
  --with-freetype-dir=/usr/include/ \
  --with-jpeg-dir=/usr/include/ && \
  docker-php-ext-install -j$(nproc) gd && \
  cp /usr/share/zoneinfo/${TZ} /etc/localtime && \
  apk del tzdata && \
  rm -rf /var/cache/apk/*

RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
docker/php/php.ini
log_errors = on
error_log = /var/log/php/php-error.log
upload_max_filesize = 3M
memory_limit = -1
post_max_size = 100M
max_execution_time = 900
max_input_vars = 100000
default_charset = UTF-8

[Date]
date.timezone = ${TZ}
[mbstring]
mbstring.internal_encoding = "UTF-8"
mbstring.language = "Japanese"

Nodeの設定

docker/node/Dockerfile
FROM node:14.6.0-alpine
RUN mkdir -p /var/www/frontApp
# timezone
ARG TZ
WORKDIR /var/www/frontApp
COPY ./frontApp/ /var/www/frontApp/

RUN apk update && \
    apk upgrade && \
    apk add --no-cache make gcc g++ python && \
    yarn install

EXPOSE 3000

ENTRYPOINT ["yarn", "run", "dev"]

nuxt-tsのプロジェクト作成

myapp
$ yarn create nuxt-app frontApp

yarn create v1.22.4
[1/4] ?  Resolving packages...
[2/4] ?  Fetching packages...
[3/4] ?  Linking dependencies...
[4/4] ?  Building fresh packages...
warning Your current version of Yarn is out of date. The latest version is "1.22.5", while you're on "1.22.4".
info To upgrade, run the following command:
$ curl --compressed -o- -L https://yarnpkg.com/install.sh | bash
success Installed "create-nuxt-app@3.5.2" with binaries:
      - create-nuxt-app

create-nuxt-app v3.5.2
✨  Generating Nuxt.js project in frontApp
? Project name: frontApp
? Programming language: TypeScript
? Package manager: Yarn
? UI framework: None
? Nuxt.js modules: Axios - Promise based HTTP client
? Linting tools: Prettier
? Testing framework: None
? Rendering mode: Universal (SSR / SSG)
? Deployment target: Server (Node.js hosting)
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Continuous integration: None
? Version control system: Git

一旦サーバを立ち上げ画面確認

myapp
$ cd frontApp
$ yarn run dev
  • localhost:3000にアクセスして下記の画面を確認したあと、command + Cでサーバストップ

top.jpg

  • コンテナ起動時に備えpackage.jsonを修正
frontApp/package.json
{
  "name": "frontApp",
  "version": "1.0.0",
  "private": true,
  "scripts": {
-   "dev": "nuxt-ts",
+   "dev": "HOST=0.0.0.0 PORT=3000 nuxt-ts",
    "build": "nuxt-ts build",
-   "start": "nuxt-ts start",
+   "start": "HOST=0.0.0.0 PORT=3000 nuxt-ts start",
    "generate": "nuxt-ts generate"
  },
  // 以下略
}

laravelディレクトリ直下にLaravelのプロジェクト作成

myapp
$ docker-compose run app composer create-project --prefer-dist laravel/laravel .

./laravelディレクトリ以下が次のような構成になっていればOK

myapp
$ tree -a -L 1 laravel
laravel
├── .editorconfig
├── .env
├── .env.example
├── .gitattributes
├── .gitignore
├── .styleci.yml
├── README.md
├── app
├── artisan
├── bootstrap
├── composer.json
├── composer.lock
├── config
├── database
├── package.json
├── phpunit.xml
├── public
├── resources
├── routes
├── server.php
├── storage
├── tests
├── vendor
└── webpack.mix.js

コンテナをバックグラウンドで起動

myapp
$ docker-compose up -d --build

Laravelの設定ファイルをキャッシュ等

myapp
$ docker-compose exec app ash
$ php artisan -V   // バージョンが表示されればOK
$ php artisan config:cache
$ php artisan migrate
$ chmod -R a+rw storage/ bootstrap/cache
$ exit

画面確認

  • 再度localhost:3000にアクセスし、TOP画面が表示されれば成功 top.jpg

Nuxt、Laravel間での通信を確認

Laravel側の設定

laravel/routes/api.php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

/*
 * 中略
 */
+ Route::get('/', function () {
+    return 'Hello';
+ });

Nuxt側の設定

myapp
$ cd frontApp
$ yarn add @nuxtjs/proxy @nuxtjs/dotenv
frontApp/nuxt.config.js
+ const environment = process.env.NODE_ENV || 'development'
+ require('dotenv').config()

export default {
// (中略)

  modules: [
    // https://go.nuxtjs.dev/axios
    '@nuxtjs/axios',
+   '@nuxtjs/proxy',
+   '@nuxtjs/dotenv',
  ],

+ proxy: {
+   '/api':
+     environment === 'development'
+       ? process.env.API_URL
+       : 'https://www.risk-exam.site',
+ },

+ axios: {
+   baseURL: process.env.API_URL,
+   browserBaseURL: process.env.API_BROWSER_URL,
+   credentials: true,
+ },

}
myapp/frontApp
$ touch .env
frontApp/.env
API_URL=http://web/api
API_BROWSER_URL=http://localhost:10080/api
frontApp/pages/index.vue
<template>
  <div class="container">
    <div>
      <Logo />
      <h1 class="title">
         {{ greet }} // デフォルトの「frontApp」から変更
      </h1>
      <div class="links">
        <a
          href="https://nuxtjs.org/"
          target="_blank"
          rel="noopener noreferrer"
          class="button--green"
        >
          Documentation
        </a>
        <a
          href="https://github.com/nuxt/nuxt.js"
          target="_blank"
          rel="noopener noreferrer"
          class="button--grey"
        >
          GitHub
        </a>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  async asyncData({ app }): Promise<object> {
    const greet: string = await app.$axios.$get('/').catch((err) => err)
    return { greet }
  }
})
</script>
// 以下略
  • localhost:3000にアクセスして、タイトルがHelloに変わっていたらOK

hello.jpg

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

ゼロからDockerでLaravel+Nuxt TypeScript+MySQLの環境構築をしてトップページを表示するまで【備忘録】

前提

ローカル環境

  • macOS Catalina10.15.5
  • Docker for mac 2.3.0.4

バージョン

  • PHP...7.3
  • Laravel...7.30.1
  • Node.js 14.6
  • nuxt/types 2.14
  • MySQL 5.7

その他

  • 最終的なディレクトリ構成
Terminal
$ tree -a -L 2 .
.
├── .env
├── .env.example
├── .gitignore
├── .idea
│   ├── modules.xml
│   ├── myapp.iml
│   └── workspace.xml
├── docker
│   ├── nginx
│   ├── node
│   └── php
├── docker-compose.yml
├── frontApp
│   ├── .editorconfig
│   ├── .git
│   ├── .gitignore
│   ├── .nuxt
│   ├── .prettierrc
│   ├── README.md
│   ├── assets
│   ├── components
│   ├── layouts
│   ├── middleware
│   ├── node_modules
│   ├── nuxt.config.js
│   ├── package.json
│   ├── pages
│   ├── plugins
│   ├── static
│   ├── store
│   ├── tsconfig.json
│   └── yarn.lock
└── laravel
    ├── .editorconfig
    ├── .env
    ├── .env.example
    ├── .gitattributes
    ├── .gitignore
    ├── .styleci.yml
    ├── README.md
    ├── app
    ├── artisan
    ├── bootstrap
    ├── composer.json
    ├── composer.lock
    ├── config
    ├── database
    ├── package.json
    ├── phpunit.xml
    ├── public
    ├── resources
    ├── routes
    ├── storage
    ├── tests
    ├── vendor
    └── webpack.mix.js

プロジェクト用ディレクトリを用意

Terminal
$ mkdir -p ~/workspace/myapp
$ cd ~/workspace/myapp
$ mkdir -p laravel docker/{php,nginx,node}
$ mkdir docker/nginx/{conf.d,logs}

.envの準備

先に.env.exampleを編集してからコピーして.envをつくる

myapp
$ touch .env.example .gitignore

.env.exampleと.gitignoreを編集

.env.example
PROJECT_PATH=./laravel
TZ=Asia/Tokyo
WEB_PORT=10080
DB_PORT=13306
DB_TESTING_PORT=13307
DB_CONNECTION=mysql
DB_NAME=myapp
DB_USER=willrewrite
DB_PASS=willrewrite
DB_ROOT_PASS=willrewrite
MAILHOG_PORT=18025
MAIL_HOST=mail
MAIL_PORT=1025

COMPOSE_HTTP_TIMEOUT=70
FRONT_PORT=3000
.gitignore
.env
.idea
# その他個別に追加

.env.exampleをコピー

myapp
$ cp .env.example .env 

コピー後、個別のプロジェクトに合わせて.envの環境変数を上書きする

必要なファイルを作成

myapp
$ touch docker-compose.yml \
docker/php/{php.ini,Dockerfile} \
docker/nginx/{conf.d/default.conf,Dockerfile} \
docker/node/Dockerfile

各ファイルを編集

docker-compose.yml
version: "3"

services:
  web:
    build:
      context: .
      dockerfile: ./docker/nginx/Dockerfile
      args:
        - TZ=${TZ}
    volumes:
      - ./docker/nginx/logs:/etc/nginx/logs
      - ./docker/nginx/conf.d:/etc/nginx/conf.d
      - ${PROJECT_PATH}:/var/www/laravel
    ports:
      - ${WEB_PORT}:80
    links:
      - app
    depends_on:
      - app

  app:
    build:
      context: .
      dockerfile: ./docker/php/Dockerfile
      args:
        - TZ=${TZ}
    volumes:
      - ${PROJECT_PATH}:/var/www/laravel
    links:
      - db_master
    environment:
      - DB_CONNECTION=${DB_CONNECTION}
      - DB_HOST=db_master
      - DB_DATABASE=${DB_NAME}
      - DB_USERNAME=${DB_USER}
      - DB_PASSWORD=${DB_PASS}
      - TZ=${TZ}
      - MAIL_HOST=${MAIL_HOST}
      - MAIL_PORT=${MAIL_PORT}

  db_master:
    image: mysql:5.7
    environment:
      - MYSQL_DATABASE=${DB_NAME}
      - MYSQL_USER=${DB_USER}
      - MYSQL_PASSWORD=${DB_PASS}
      - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASS}
      - TZ=${TZ}
    ports:
      - ${DB_PORT}:3306

  front_app:
    build:
      context: .
      dockerfile: ./docker/node/Dockerfile
      args:
        - TZ=${TZ}
    volumes:
      - ./frontApp:/var/www/frontApp
    ports:
      - ${FRONT_PORT}:3000
    links:
      - web
    depends_on:
      - web

Nginxの設定

docker/nginx/Dockerfile
FROM nginx:1.15.7-alpine
# timezone
ARG TZ
COPY ./docker/nginx/conf.d/ /etc/nginx/conf.d/
COPY ./docker/nginx/logs/ /etc/nginx/logs/
COPY ./laravel/ /var/www/laravel/
RUN apk update && apk --update add tzdata && \
  cp /usr/share/zoneinfo/${TZ} /etc/localtime && \
  apk del tzdata && \
  rm -rf /var/cache/apk/*
docker/nginx/conf.d/default.conf
server {
    listen       0.0.0.0:80;
    # server_nameで設定した名前をローカルマシンの/etc/hostsにも設定する
    server_name  www.example.org example.org;
    charset      utf-8;
    client_max_body_size 3M;

    root /var/www/laravel/public;

    index index.php;

    location / {
        access_log  /etc/nginx/logs/access.log main;
        error_log   /etc/nginx/logs/error.log;
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        fastcgi_pass  app:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include       fastcgi_params;
    }
}

PHPの設定

docker/php/Dockerfile
FROM php:7.3.0RC6-fpm-alpine3.8
# timezone
ARG TZ
WORKDIR /var/www/laravel
COPY ./docker/php/php.ini /usr/local/etc/php/
COPY ./laravel/ /var/www/laravel/

RUN apk upgrade --update && apk add --no-cache \
  coreutils freetype-dev jpeg-dev libjpeg-turbo-dev libpng-dev libmcrypt-dev \
  git vim unzip tzdata \
  libltdl && \
  docker-php-ext-install pdo_mysql mysqli mbstring && \
  docker-php-ext-install -j$(nproc) iconv && \
  docker-php-ext-configure gd \
  --with-freetype-dir=/usr/include/ \
  --with-jpeg-dir=/usr/include/ && \
  docker-php-ext-install -j$(nproc) gd && \
  cp /usr/share/zoneinfo/${TZ} /etc/localtime && \
  apk del tzdata && \
  rm -rf /var/cache/apk/*

RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
docker/php/php.ini
log_errors = on
error_log = /var/log/php/php-error.log
upload_max_filesize = 3M
memory_limit = -1
post_max_size = 100M
max_execution_time = 900
max_input_vars = 100000
default_charset = UTF-8

[Date]
date.timezone = ${TZ}
[mbstring]
mbstring.internal_encoding = "UTF-8"
mbstring.language = "Japanese"

Nodeの設定

docker/node/Dockerfile
FROM node:14.6.0-alpine
RUN mkdir -p /var/www/frontApp
# timezone
ARG TZ
WORKDIR /var/www/frontApp
COPY ./frontApp/ /var/www/frontApp/

RUN apk update && \
    apk upgrade && \
    apk add --no-cache make gcc g++ python && \
    yarn install

EXPOSE 3000

ENTRYPOINT ["yarn", "run", "dev"]

nuxt-tsのプロジェクト作成

myapp
$ yarn create nuxt-app frontApp

yarn create v1.22.4
[1/4] ?  Resolving packages...
[2/4] ?  Fetching packages...
[3/4] ?  Linking dependencies...
[4/4] ?  Building fresh packages...
warning Your current version of Yarn is out of date. The latest version is "1.22.5", while you're on "1.22.4".
info To upgrade, run the following command:
$ curl --compressed -o- -L https://yarnpkg.com/install.sh | bash
success Installed "create-nuxt-app@3.5.2" with binaries:
      - create-nuxt-app

create-nuxt-app v3.5.2
✨  Generating Nuxt.js project in frontApp
? Project name: frontApp
? Programming language: TypeScript
? Package manager: Yarn
? UI framework: None
? Nuxt.js modules: Axios - Promise based HTTP client
? Linting tools: Prettier
? Testing framework: None
? Rendering mode: Universal (SSR / SSG)
? Deployment target: Server (Node.js hosting)
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Continuous integration: None
? Version control system: Git

一旦サーバを立ち上げ画面確認

myapp
$ cd frontApp
$ yarn run dev
  • localhost:3000にアクセスして下記の画面を確認したあと、command + Cでサーバストップ

top.jpg

  • コンテナ起動時に備えpackage.jsonを修正
frontApp/package.json
{
  "name": "frontApp",
  "version": "1.0.0",
  "private": true,
  "scripts": {
-   "dev": "nuxt-ts",
+   "dev": "HOST=0.0.0.0 PORT=3000 nuxt-ts",
    "build": "nuxt-ts build",
-   "start": "nuxt-ts start",
+   "start": "HOST=0.0.0.0 PORT=3000 nuxt-ts start",
    "generate": "nuxt-ts generate"
  },
  // 以下略
}

laravelディレクトリ直下にLaravelのプロジェクト作成

myapp
$ docker-compose run app composer create-project --prefer-dist laravel/laravel .

./laravelディレクトリ以下が次のような構成になっていればOK

myapp
$ tree -a -L 1 laravel
laravel
├── .editorconfig
├── .env
├── .env.example
├── .gitattributes
├── .gitignore
├── .styleci.yml
├── README.md
├── app
├── artisan
├── bootstrap
├── composer.json
├── composer.lock
├── config
├── database
├── package.json
├── phpunit.xml
├── public
├── resources
├── routes
├── server.php
├── storage
├── tests
├── vendor
└── webpack.mix.js

コンテナをバックグラウンドで起動

myapp
$ docker-compose up -d --build

Laravelの設定ファイルをキャッシュ等

myapp
$ docker-compose exec app ash
$ php artisan -V   // バージョンが表示されればOK
$ php artisan config:cache
$ php artisan migrate
$ chmod -R a+rw storage/ bootstrap/cache
$ exit

画面確認

  • 再度localhost:3000にアクセスし、TOP画面が表示されれば成功 top.jpg

Nuxt、Laravel間での通信を確認

Laravel側の設定

laravel/routes/api.php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

/*
 * 中略
 */
+ Route::get('/', function () {
+    return 'Hello';
+ });

Nuxt側の設定

myapp
$ cd frontApp
$ yarn add @nuxtjs/proxy @nuxtjs/dotenv
frontApp/nuxt.config.js
+ const environment = process.env.NODE_ENV || 'development'
+ require('dotenv').config()

export default {
// (中略)

  modules: [
    // https://go.nuxtjs.dev/axios
    '@nuxtjs/axios',
+   '@nuxtjs/proxy',
+   '@nuxtjs/dotenv',
  ],

+ proxy: {
+   '/api':
+     environment === 'development'
+       ? process.env.API_URL
+       : 'https://www.risk-exam.site',
+ },

+ axios: {
+   baseURL: process.env.API_URL,
+   browserBaseURL: process.env.API_BROWSER_URL,
+   credentials: true,
+ },

}
myapp/frontApp
$ touch .env
frontApp/.env
API_URL=http://web/api
API_BROWSER_URL=http://localhost:10080/api
frontApp/pages/index.vue
<template>
  <div class="container">
    <div>
      <Logo />
      <h1 class="title">
         {{ greet }} // デフォルトの「frontApp」から変更
      </h1>
      <div class="links">
        <a
          href="https://nuxtjs.org/"
          target="_blank"
          rel="noopener noreferrer"
          class="button--green"
        >
          Documentation
        </a>
        <a
          href="https://github.com/nuxt/nuxt.js"
          target="_blank"
          rel="noopener noreferrer"
          class="button--grey"
        >
          GitHub
        </a>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  async asyncData({ app }): Promise<object> {
    const greet: string = await app.$axios.$get('/').catch((err) => err)
    return { greet }
  }
})
</script>
// 以下略
  • localhost:3000にアクセスして、タイトルがHelloに変わっていたらOK

hello.jpg

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

Laravelで将棋アプリを作りたい(第一回)

Laravalを使って将棋の動きを実現してみたくなったので、「トンカチと釘」感が半端ないが、やる気に任せてやってみる。
今回は プロジェクト作成 ~ 盤面作成まで。

環境
macOS Catalina
Laravel Installer 3.0.1

目次

1.プロジェクト作成
2.コントローラーで型作成
3.ビューとcssで盤面作成
4.王将を置いてみる

1. プロジェクト作成

ターミナル
$ laravel new shogi-app

アプリ起動確認

ターミナル
$ cd shogi-app
$ php artisan serve

http://127.0.0.1:8000/ でアプリが起動している。
Laravel起動画面

ついでにGitHubで新しいリポジトリ作成
GitHubリポジトリ作成案内画像

ターミナル
$ git add .
$ git commit -m 'first commit'
$ git branch -M main
$ git remote add origin {作成したリポジトリのURL}.git
$ git push -u origin main

2. コントローラーで型作成

ターミナル
$ php artisan make:controller ShogiController

indexメソッドに書いてみる。

app/Http/Controllers/ShogiController.php
    public function index()
    {
        $appName = '将棋アプリ';
        return view('shogi/index', compact('appName'));
    }

今は$appNameを定義してビューに渡すだけ。

ルーティング追加

route/web.php
Route::get('shogi', 'App\Http\Controllers\ShogiController@index');

これで、http://127.0.0.1:8000/shogiへのアクセスでShogiControllerindexメソッドが呼び出される。

3. ビューとcssで盤面作成

今回はbladeテンプレートを使用してビューを作成
resources/views 配下に新たに shogiフォルダを作成。

resources/views/shogi/index.blade.php
<!DOCTYPE html>
<html>
<head>
    <title>将棋アプリ</title>
    <link rel="stylesheet" href="{{ asset('css/style.css') }}">
</head>
<body>
    <p>{{ $appName }}</p>
    <div class="board">
        <?php 
        for ($c = 1; $c < 10; $c++) {
            echo '<div class="column column' . $c . '" id="column' . $c . '">';
            for ($r = 9; $r > 0; $r--) {
                echo '<p class="row square' . $r . $c . '" id="square' . $r . $c . '">' . $r . $c . '</p>';
            }
            echo '</div>';
        } 
        ?>
    </div>
</body>
</html>

CSSはpublic配下に配置

public/css/style.css
.board {
    margin: 0 200px;
}

.row {
    display: table-cell;
    vertical-align: middle;
    text-align: center;
    border: solid 1px black;
    width: 60px;
}

.column {
    width: 500px;
    display: table;
    table-layout: fixed;
    height: 60px;
}

これで9マス×9マスの棋譜のようなものができた。
まるで棋譜

4. 王将を置いてみる

下記サイトから、将棋駒の素材入手&縮小

サイズを調整した王将の画像をpublic/image配下に配置

コントローラーで王将の初期値を渡す。

app/Http/Controller/ShogiController
    public function index()
    {
        $appName = '将棋アプリ';
        $MyKing = 59; // 追加
        return view('shogi/index', compact('appName', 'MyKing')); // ビューにも渡す
    }

ビューでは、王将の位置の時だけ画像のついたボタンを表示させる。

resources/views/shogi/index.blade.php
<!DOCTYPE html>
<html>
<head>
    <title>将棋アプリ</title>
    <link rel="stylesheet" href="{{ asset('css/style.css') }}">
</head>
<body>
    <p>{{ $appName }}</p>
    <div class="board">
        <?php 
        for ($c = 1; $c < 10; $c++) {
            echo '<div class="column column' . $c . '" id="column' . $c . '">';
            for ($r = 9; $r > 0; $r--) {
                if ($r . $c == $MyKing) {
                    echo '<p class="row square' . $r . $c . '" id="square' . $r . $c . '"><button><img src="image/my_king_50.png" alt="自分の王将"></button></p>';
                } else {
                    echo '<p class="row square' . $r . $c . '" id="square' . $r . $c . '">' . $r . $c . '</p>';
                }
            }
            echo '</div>';
        } 
        ?>
    </div>
</body>
</html>

王将を置いた

今回はここまで。

ターミナル
$ git add .
$ git commit -m "盤面作成"
$ git push origin main
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【?初心者】Laravelで問題解決を試みたポートフォリオ

logo.png

概要

車両を保有している人と保有していない人を結ぶアプリケーション。大手カーシェアリングサービスのように車両を会社からドライバーに貸し出す(BtoB)のでなく、個人間で気軽に貸し借りができるサービス(CtoC)をコンセプトとした。
[→アプリケーション リンク]
スクリーンショット 2021-02-13 15.35.44.png

制作背景

交通インフラの整備や若者の車離れの影響を受け、自動車の国内需要は1990年ごろをピークとして年々減少している。これに伴い、大手のBtoBカーシェアリングサービス(タイムズ)の会員数は過去5年間で約4倍以上伸びている。対して、今後伸びてくると予想されていたCtoCのカーシェアリング業界は伸び悩んでいるというギャップが生じている。この現象に疑問を持ったため、原因をトヨタの問題解決の手法を用いて考え、このギャップを解決するサービスの開発を行った。
[→制作背景詳細 リンク]

システム概要

このサービスのユーザーは3パターンに分けられる。
A. ドライバー (車を借りたい人)
B. オーナー (車を貸したい人)
C. 管理者 (Car.マッチング株式会社:自動車保険会社)
systemflow.png

操作説明

各ユーザーの操作方法について下記に示す。
[→操作詳細説明 リンク]

A. ドライバーの基本操作

①ログイン
②希望の条件(日時や乗車人数)で検索
③該当したオーナーとチャットでの交流
④車両を借りることへの不安がなくなれば、契約を結んでもらえるようオーナーへ依頼
driver.gif


B. オーナーの基本操作

①ログイン
②車両を貸し出せる日程を登録
③ドライバーから依頼があればチャットにて交流
④オーナーとドライバーが契約条件に合意した場合、契約フォームに入力
⑤確認メール送信


C. 管理者(Car.マッチング株式会社:自動車保険会社)の基本操作

①ドライバーとオーナー間で契約が結ばれた後、DBへ内容が保存されるため、それらを表示
②この登録情報を見て自動車の保険の加入手続き・料金の支払い手続きに移行
admin.png

こだわり

私はCtoCのカーシェアリング業界が伸びていない原因は主に下記の3点に起因すると考える。
①初期登録操作のハードルが高い
②条件に合う車両を探す作業が煩わしい
③全く交流がない他人から車両を借りることへの不安がある

これらを解決するために下記にこだわって開発した。

A. 初期登録の簡略化
初期の登録はなるべくシンプルにしておき、ドライバーとオーナーの交渉が成立してから、更に必要な情報を入力させる。ナンバープレート情報や料金の設定など初期登録の際に煩わしさを感じるものは契約時に入力させる。

B. 操作がシンプル
マルチログイン機能を実装してドライバーとオーナーの操作を分け、余分な機能は付けず、直感的に分かりやすい構成を心がける。
* ドライバーの操作: 初期登録 → 検索 → チャットによるオーナーとの交渉
* オーナーの操作 : 初期登録→ 貸出日程登録 → チャット → 契約詳細入力

C. LINEのような手軽さで個人間でチャットが可能(非同期通信)
アプリ内のチャット機能を、スマートフォンユーザーのおよそ80%が利用しているLINEの操作に合わせることで、誰でも容易にチャット機能を使って交流できる。また、非同期通信を行うことによって画面の遷移無しで、相手のメッセージを受け取ることができる。

上記とは色合いが異なるが、その他にもユーザーと管理者を思いやった機能も追加した。

D. 契約内容のエビデンスとしてメールの自動送信
オーナーが契約内容を入力後、確認メールが自動でドライバー・オーナー・Carマッチング(自動車保険会社)へと送付される。これにより、ドライバーとオーナーとの間で契約した内容を失念しても、再度確認することができる。また、料金や内容の相違でトラブルが起きた際に、エビデンスとして示すこともできる。

E. 管理者用の契約内容閲覧機能
ドライバーとオーナー間で契約が結ばれた後、DBへその内容が保存されるため、その内容を表示する機能。その内容を見て自動車の保険の加入手続き・料金の支払い手続きに移行する。

使用技術

バックエンド
PHP 7.2.34 / Laravel 6.20.5

フロントエンド
HTML / CSS / javascript / jQuery 3.2.1 / Vue.js(現在学習中のため今後組み込む)

インフラ
mysql 8.0.22 / AWS(EC2,S3)

その他の使用技術
Pusher / git(gitHub) / Visual Studio Code / draw.io / Gmail

AWS構成図

aws.png

DB設計

・ ER図

finaltable.png

・ 各種テーブル

テーブル名 定義
owners
(オーナー)
オーナーの登録情報
drivers
(ドライバー)
ドライバーの登録情報
owner_schedules
(オーナースケジュール)
ドライバーの車両貸出可能な日程
chats
(チャット)
会話の内容
contracts
(コントラクト)
契約確定後、契約内容を格納

苦労したところ

①DB設計

ER図を添付していますが、実際のアプリケーションとテーブル名やカラム名に相違があります。テーブルの命名規則の理解が曖昧ままDB設計をしており、キャメルケースを用いた記述をしてしまいました。開発終盤に気付きましたが、影響範囲が広すぎて修正が困難になりました。この失敗を二度と繰り返さないよう再度学習を行い、Qiitaにまとめています。初期設計の重要性を身に染みて学びました。[→Qiita記事 リンク]

②Laravelのマルチログイン機能

ドライバーとオーナーで初期の登録方法やログインした後の挙動が変わるため、マルチログイン機能を実装する必要がありました。Laravelでは基本のログイン機能がついていますが、これを応用して2通りのログイン機能を作りました。変更が必要なファイルが多く、それぞれの関連性が分かっていなかったため、苦労しました。ここで3日以上も悩んだことで認証やミドルウェア・ルーティングについての知識を身に着けることができました。[→Qiita記事 リンク]

③AWSでのデプロイ

web系企業ではクラウドはAWSが主流になっていますのでAWSを用いて本番環境を構築しました。初めてのVimコマンドの操作に慣れていないことや、ディレクトリの階層も頭に入っていなかったことで、configファイルや.envファイルを探したり編集するだけでかなり難航しました。VPCの生成から数えると30〜40時間以上かけてなんとかデプロイしました。多く悩んだ分、仮想サーバーやIPアドレスの知識・コマンドの操作などを身に着けることができました。

④websocket通信を用いたチャットの非同期通信

外部API(pusher)を用いたリアルタイムチャットの実装に挑戦しました。実装に必要な作業をなるべく最小単位に分割して進めていきました。非同期でのメッセージの受信はできるようになったものの、ドライバー側からのメッセージなのかオーナー側からのメッセージなのかを分岐させることができません。少し手詰まり状態にありますが、今も諦めずにトライしています。

⑤LaravelのCollection型の条件分岐(ユーザーからのフィードバック)

知人にアプリケーションの操作トライを依頼した際に、「検索後、該当するものが無かった時にコメントが出ないので分からない」とアドバイスを受けました。これを受け、検索する前・検索して成功した時・検索して失敗した時と3つの条件で表示を分岐させました。この時にLaravelのCollection型の扱いについて理解が少なく、空値判定がうまくいかず長時間悩んでしまいましたので、学習してQiitaにまとめました。[→Qiita記事 リンク]

最後に

転職活動のポートフォリオとして製作しています。実務未経験者が独学で作ったため、内容に誤っている点があるかと思います。お手柔らかにお願いいたします。今後実務を経験して、過去の実力を振り返られるように記録として執筆しました。LGTMで応援いただけると励みになります。

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

【ポートフォリオ】初心者?がLaravelで問題解決を試みた

logo.png

概要

車両を保有している人と保有していない人を結ぶアプリケーション。大手カーシェアリングサービスのように車両を会社からドライバーに貸し出す(BtoB)のでなく、個人間で気軽に貸し借りができるサービス(CtoC)をコンセプトとした。
※悪意のあるイタズラが多発したため、アプリリンクは貼っていません。
スクリーンショット 2021-02-13 15.35.44.png

制作背景

交通インフラの整備や若者の車離れの影響を受け、自動車の国内需要は1990年ごろをピークとして年々減少している。これに伴い、大手のBtoBカーシェアリングサービス(タイムズ)の会員数は過去5年間で約4倍以上伸びている。対して、今後伸びてくると予想されていたCtoCのカーシェアリング業界は伸び悩んでいるというギャップが生じている。この現象に疑問を持ったため、原因をトヨタの問題解決の手法を用いて考え、このギャップを解決するサービスの開発を行った。
[→制作背景詳細 リンク]

システム概要

このサービスのユーザーは3パターンに分けられる。
A. ドライバー (車を借りたい人)
B. オーナー (車を貸したい人)
C. 管理者 (Car.マッチング株式会社:自動車保険会社)
systemflow.png

操作説明

各ユーザーの操作方法について下記に示す。

A. ドライバーの基本操作

①ログイン
②希望の条件(日時や乗車人数)で検索
③該当したオーナーとチャットでの交流
④車両を借りることへの不安がなくなれば、契約を結んでもらえるようオーナーへ依頼
driver.gif


B. オーナーの基本操作

①ログイン
②車両を貸し出せる日程を登録
③ドライバーから依頼があればチャットにて交流
④オーナーとドライバーが契約条件に合意した場合、契約フォームに入力
⑤確認メール送信
ownerQiita2.gif


C. 管理者(Car.マッチング株式会社:自動車保険会社)の基本操作

①ドライバーとオーナー間で契約が結ばれた後、DBへ内容が保存されるため、それらを表示
②この登録情報を見て自動車の保険の加入手続き・料金の支払い手続きに移行
admin.png

こだわり

私はCtoCのカーシェアリング業界が伸びていない原因は主に下記の3点に起因すると考える。
①初期登録操作のハードルが高い
②条件に合う車両を探す作業が煩わしい
③全く交流がない他人から車両を借りることへの不安がある

これらを解決するために下記にこだわって開発した。

A. 初期登録の簡略化
初期の登録はなるべくシンプルにしておき、ドライバーとオーナーの交渉が成立してから、更に必要な情報を入力させる。ナンバープレート情報や料金の設定など初期登録の際に煩わしさを感じるものは契約時に入力させる。

B. 操作がシンプル
マルチログイン機能を実装してドライバーとオーナーの操作を分け、余分な機能は付けず、直感的に分かりやすい構成を心がける。
* ドライバーの操作: 初期登録 → 検索 → チャットによるオーナーとの交渉
* オーナーの操作 : 初期登録→ 貸出日程登録 → チャット → 契約詳細入力

C. LINEのような手軽さで個人間でチャットが可能(非同期通信)
アプリ内のチャット機能を、スマートフォンユーザーのおよそ80%が利用しているLINEの操作に合わせることで、誰でも容易にチャット機能を使って交流できる。また、非同期通信を行うことによって画面の遷移無しで、相手のメッセージを受け取ることができる。

上記とは色合いが異なるが、その他にもユーザーと管理者を思いやった機能も追加した。

D. 契約内容のエビデンスとしてメールの自動送信
オーナーが契約内容を入力後、確認メールが自動でドライバー・オーナー・Carマッチング(自動車保険会社)へと送付される。これにより、ドライバーとオーナーとの間で契約した内容を失念しても、再度確認することができる。また、料金や内容の相違でトラブルが起きた際に、エビデンスとして示すこともできる。

E. 管理者用の契約内容閲覧機能
ドライバーとオーナー間で契約が結ばれた後、DBへその内容が保存されるため、その内容を表示する機能。その内容を見て自動車の保険の加入手続き・料金の支払い手続きに移行する。

使用技術

バックエンド
PHP 7.2.34 / Laravel 6.20.5

フロントエンド
HTML / CSS / javascript / jQuery 3.2.1 / Vue.js(現在学習中のため今後組み込む)

インフラ
mysql 8.0.22 / AWS(EC2,S3)

その他の使用技術
Pusher / git(gitHub) / Visual Studio Code / draw.io / Gmail

AWS構成図

aws.png

DB設計

・ ER図

finaltable.png

・ 各種テーブル

テーブル名 定義
owners
(オーナー)
オーナーの登録情報
drivers
(ドライバー)
ドライバーの登録情報
owner_schedules
(オーナースケジュール)
ドライバーの車両貸出可能な日程
chats
(チャット)
会話の内容
contracts
(コントラクト)
契約確定後、契約内容を格納

苦労したところ

①DB設計

ER図を添付していますが、実際のアプリケーションとテーブル名やカラム名に相違があります。テーブルの命名規則の理解が曖昧ままDB設計をしており、キャメルケースを用いた記述をしてしまいました。開発終盤に気付きましたが、影響範囲が広すぎて修正が困難になりました。この失敗を二度と繰り返さないよう再度学習を行い、Qiitaにまとめています。初期設計の重要性を身に染みて学びました。[→Qiita記事 リンク]

②Laravelのマルチログイン機能

ドライバーとオーナーで初期の登録方法やログインした後の挙動が変わるため、マルチログイン機能を実装する必要がありました。Laravelでは基本のログイン機能がついていますが、これを応用して2通りのログイン機能を作りました。変更が必要なファイルが多く、それぞれの関連性が分かっていなかったため、苦労しました。ここで3日以上も悩んだことで認証やミドルウェア・ルーティングについての知識を身に着けることができました。[→Qiita記事 リンク]

③AWSでのデプロイ

web系企業ではクラウドはAWSが主流になっていますのでAWSを用いて本番環境を構築しました。初めてのVimコマンドの操作に慣れていないことや、ディレクトリの階層も頭に入っていなかったことで、configファイルや.envファイルを探したり編集するだけでかなり難航しました。VPCの生成から数えると30〜40時間以上かけてなんとかデプロイしました。多く悩んだ分、仮想サーバーやIPアドレスの知識・コマンドの操作などを身に着けることができました。

④websocket通信を用いたチャットの非同期通信

外部API(pusher)を用いたリアルタイムチャットの実装に挑戦しました。実装に必要な作業をなるべく最小単位に分割して進めていきました。非同期でのメッセージの受信はできるようになったものの、ドライバー側からのメッセージなのかオーナー側からのメッセージなのかを分岐させることができません。少し手詰まり状態にありますが、今も諦めずにトライしています。

⑤LaravelのCollection型の条件分岐(ユーザーからのフィードバック)

知人にアプリケーションの操作トライを依頼した際に、「検索後、該当するものが無かった時にコメントが出ないので分からない」とアドバイスを受けました。これを受け、検索する前・検索して成功した時・検索して失敗した時と3つの条件で表示を分岐させました。この時にLaravelのCollection型の扱いについて理解が少なく、空値判定がうまくいかず長時間悩んでしまいましたので、学習してQiitaにまとめました。[→Qiita記事 リンク]

最後に

転職活動のポートフォリオとして製作しています。実務未経験者が独学で作ったため、内容に誤っている点があるかと思います。お手柔らかにお願いいたします。今後実務を経験して、過去の実力を振り返られるように記録として執筆しました。LGTMで応援いただけると励みになります。

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

【ポートフォリオ】初心者?がLaravelでカーシェアアプリ開発

logo.png

概要

車両を保有している人と保有していない人を結ぶアプリケーション。大手カーシェアリングサービスのように車両を会社からドライバーに貸し出す(BtoB)のでなく、個人間で気軽に貸し借りができるサービス(CtoC)をコンセプトとした。
※悪意のあるイタズラが多発したため、アプリリンクは貼っていません。
スクリーンショット 2021-02-13 15.35.44.png

制作背景

交通インフラの整備や若者の車離れの影響を受け、自動車の国内需要は1990年ごろをピークとして年々減少している。これに伴い、大手のBtoBカーシェアリングサービス(タイムズ)の会員数は過去5年間で約4倍以上伸びている。対して、今後伸びてくると予想されていたCtoCのカーシェアリング業界は伸び悩んでいるというギャップが生じている。この現象に疑問を持ったため、原因をトヨタの問題解決の手法を用いて考え、このギャップを解決するサービスの開発を行った。
[→制作背景詳細 リンク]

システム概要

このサービスのユーザーは3パターンに分けられる。
A. ドライバー (車を借りたい人)
B. オーナー (車を貸したい人)
C. 管理者 (Car.マッチング株式会社:自動車保険会社)
systemflow.png

操作説明

各ユーザーの操作方法について下記に示す。

A. ドライバーの基本操作

①ログイン
②希望の条件(日時や乗車人数)で検索
③該当したオーナーとチャットでの交流
④車両を借りることへの不安がなくなれば、契約を結んでもらえるようオーナーへ依頼
driver.gif


B. オーナーの基本操作

①ログイン
②車両を貸し出せる日程を登録
③ドライバーから依頼があればチャットにて交流
④オーナーとドライバーが契約条件に合意した場合、契約フォームに入力
⑤確認メール送信
ownerQiita2.gif


C. 管理者(Car.マッチング株式会社:自動車保険会社)の基本操作

①ドライバーとオーナー間で契約が結ばれた後、DBへ内容が保存されるため、それらを表示
②この登録情報を見て自動車の保険の加入手続き・料金の支払い手続きに移行
admin.png

こだわり

私はCtoCのカーシェアリング業界が伸びていない原因は主に下記の3点に起因すると考える。
①初期登録操作のハードルが高い
②条件に合う車両を探す作業が煩わしい
③全く交流がない他人から車両を借りることへの不安がある

これらを解決するために下記にこだわって開発した。

A. 初期登録の簡略化
初期の登録はなるべくシンプルにしておき、ドライバーとオーナーの交渉が成立してから、更に必要な情報を入力させる。ナンバープレート情報や料金の設定など初期登録の際に煩わしさを感じるものは契約時に入力させる。

B. 操作がシンプル
マルチログイン機能を実装してドライバーとオーナーの操作を分け、余分な機能は付けず、直感的に分かりやすい構成を心がける。
* ドライバーの操作: 初期登録 → 検索 → チャットによるオーナーとの交渉
* オーナーの操作 : 初期登録→ 貸出日程登録 → チャット → 契約詳細入力

C. LINEのような手軽さで個人間でチャットが可能(非同期通信)
アプリ内のチャット機能を、スマートフォンユーザーのおよそ80%が利用しているLINEの操作に合わせることで、誰でも容易にチャット機能を使って交流できる。また、非同期通信を行うことによって画面の遷移無しで、相手のメッセージを受け取ることができる。

上記とは色合いが異なるが、その他にもユーザーと管理者を思いやった機能も追加した。

D. 契約内容のエビデンスとしてメールの自動送信
オーナーが契約内容を入力後、確認メールが自動でドライバー・オーナー・Carマッチング(自動車保険会社)へと送付される。これにより、ドライバーとオーナーとの間で契約した内容を失念しても、再度確認することができる。また、料金や内容の相違でトラブルが起きた際に、エビデンスとして示すこともできる。

E. 管理者用の契約内容閲覧機能
ドライバーとオーナー間で契約が結ばれた後、DBへその内容が保存されるため、その内容を表示する機能。その内容を見て自動車の保険の加入手続き・料金の支払い手続きに移行する。

使用技術

バックエンド
PHP 7.2.34 / Laravel 6.20.5

フロントエンド
HTML / CSS / javascript / jQuery 3.2.1 / Vue.js(現在学習中のため今後組み込む)

インフラ
mysql 8.0.22 / AWS(EC2,S3)

その他の使用技術
Pusher / git(gitHub) / Visual Studio Code / draw.io / Gmail

AWS構成図

aws.png

DB設計

・ ER図

finaltable.png

・ 各種テーブル

テーブル名 定義
owners
(オーナー)
オーナーの登録情報
drivers
(ドライバー)
ドライバーの登録情報
owner_schedules
(オーナースケジュール)
ドライバーの車両貸出可能な日程
chats
(チャット)
会話の内容
contracts
(コントラクト)
契約確定後、契約内容を格納

苦労したところ

①DB設計

ER図を添付していますが、実際のアプリケーションとテーブル名やカラム名に相違があります。テーブルの命名規則の理解が曖昧ままDB設計をしており、キャメルケースを用いた記述をしてしまいました。開発終盤に気付きましたが、影響範囲が広すぎて修正が困難になりました。この失敗を二度と繰り返さないよう再度学習を行い、Qiitaにまとめています。初期設計の重要性を身に染みて学びました。[→Qiita記事 リンク]

②Laravelのマルチログイン機能

ドライバーとオーナーで初期の登録方法やログインした後の挙動が変わるため、マルチログイン機能を実装する必要がありました。Laravelでは基本のログイン機能がついていますが、これを応用して2通りのログイン機能を作りました。変更が必要なファイルが多く、それぞれの関連性が分かっていなかったため、苦労しました。ここで3日以上も悩んだことで認証やミドルウェア・ルーティングについての知識を身に着けることができました。[→Qiita記事 リンク]

③AWSでのデプロイ

web系企業ではクラウドはAWSが主流になっていますのでAWSを用いて本番環境を構築しました。初めてのVimコマンドの操作に慣れていないことや、ディレクトリの階層も頭に入っていなかったことで、configファイルや.envファイルを探したり編集するだけでかなり難航しました。VPCの生成から数えると30〜40時間以上かけてなんとかデプロイしました。多く悩んだ分、仮想サーバーやIPアドレスの知識・コマンドの操作などを身に着けることができました。

④websocket通信を用いたチャットの非同期通信

外部API(pusher)を用いたリアルタイムチャットの実装に挑戦しました。実装に必要な作業をなるべく最小単位に分割して進めていきました。非同期でのメッセージの受信はできるようになったものの、ドライバー側からのメッセージなのかオーナー側からのメッセージなのかを分岐させることができません。少し手詰まり状態にありますが、今も諦めずにトライしています。

⑤LaravelのCollection型の条件分岐(ユーザーからのフィードバック)

知人にアプリケーションの操作トライを依頼した際に、「検索後、該当するものが無かった時にコメントが出ないので分からない」とアドバイスを受けました。これを受け、検索する前・検索して成功した時・検索して失敗した時と3つの条件で表示を分岐させました。この時にLaravelのCollection型の扱いについて理解が少なく、空値判定がうまくいかず長時間悩んでしまいましたので、学習してQiitaにまとめました。[→Qiita記事 リンク]

最後に

転職活動のポートフォリオとして製作しています。実務未経験者が独学で作ったため、内容に誤っている点があるかと思います。お手柔らかにお願いいたします。今後実務を経験して、過去の実力を振り返られるように記録として執筆しました。

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

LaravelでFullcalendarに登録した内容を更新する方法

はじめに

前回に続き、今回はFullcalendarで登録した情報を更新する方法についてご説明します。
LaravelでFullcalendarを実装する方法

また、今回はカレンダー上の登録したイベントをクリックするとモーダルが出てきて、そのモーダル上で更新ができるようにします。モーダルでは更新前の情報も確認できるようにしたいと思います。

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

Fullcalendarはバージョンによって記述方法が異なるので注意してください。
v4の記事を参考にしてもうまくいかないことが多かったです。

なお、フォルダ名とうは登録時に作成したものをそのまま使用しておりますのでご了承ください。

更新用のモーダルを用意する

モーダルはJavaScriptのプアグインであるMicromodal.jsを使用しました。
モーダルに関しては自作のものではなく、プラグインを利用した方が利点が多いというツイートを見かけたので、今回は自作をせずにプラグインを使用しています。

Micoromodal.jsのダウンロードは以下から行えます。
(https://micromodal.now.sh/)

今回はCDNで読み込ませました。

event.blade.php
<!-- Micromodal.js -->
<script src="https://unpkg.com/micromodal/dist/micromodal.min.js"></script>

続いて、モーダル用のbladeを作成し、更新フォームを作っていきます。
こちらはこの後@includeでevent.blade.php内で読み込ませるので、直接event.blade.php内に書いても問題ないです。

また、headerに関してはMicromodal.jsの公式ドキュメントを参考にしています。
フォームはmainタグ内に書いていきます。

moda.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="">
          </form> 
            </main>
        </div>
    </div>
</div>

通常だとvalueに更新前の情報を入れることで、フォームで確認できるのですが、今回はJSで指定するので空にしています。

続いて、カレンダー上のイベントをクリックした際に、モーダルが出てくるように設定していきます。
カレンダー上のイベントをクリックした時の挙動は、Fullcalendarの公式ドキュメントを確認すると、eventClickというプロパティが用意されているようなので、こちらを前回作成したJSに追加していきます。
eventClick

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

     // ここから追加
    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');
     }
    });
  });
</script>

infoの中には更新前の情報が入っているので、その情報をそれぞれ先ほど指定したフォーム内のid属性をgetElementByIdで取得し、value値に指定しています。
また、startに関しては、console.log(info.start)で見てみると、_isAMomentObject: true, _i: "2021-02-06", _f: "YYYY-MM-DD", _isUTC: true, _pf:となっており、_iの箇所に日付が格納されているのがわかると思います。
そのため、info.start._iとすることで日付を取得するようにしています。

これで、更新前の情報が表示されるようになりました。

また、event.blade.php内でmodal.blade.phpを忘れずに読み込んでください。
私は前回作成した新規登録用のフォームの真下に書きました。

event.blade.php
@include('modal')

コントローラー、ビューを追加する

先ほどaction="{{ route('editEvent') }}で指定したルートを設定します。

web.php
Route::post('/editEvent', 'EventController@editEvent');

続いてコントローラーです。

EventController.php
use App\Models\Event; //冒頭で宣言する

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

        $event->save();

        return redirect('/home');
    }

以上で更新用モーダルの完成です!

さいごに

今回は、前回作成したFullcalendarの更新処理を、モーダルを利用して作ってみました。
また次回以降は登録したデータの削除方法についても書いていきたいと思います!

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

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

【Laravel初心者】一覧表示の実装

Laravel初学者です。
オリジナルアプリを作成しているのでその過程を記事にしています。

理解が曖昧なところも多いため、ご指摘等ありましたらご連絡いただければ幸いです。

今回は一覧表示の実装について備忘録のため記録として残します。

環境

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

ルーティング

routes/web.php
Route::get('/', [GameController::class, 'index']);

私の場合は/(ルート)を一覧表示として実装したかったので上記のようなルーティングになります。
/(ルート)にアクセスがあった時にGameControllerのindexアクションが動きますという意味です。

モデル

Models/Game.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Game extends Model
{
    use HasFactory;

    protected $table = "games";
    protected $fillable = [
        "id",
        "user_id",
        "name",
        "describe",
        "play_time",
        "players_minimum",
        "players_max",
        "image_path",
        "updated_at",
        "created_at",
    ];

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

こちらは今回の一覧表示機能にだけ必要というものではないですが。
$fillableで指定して保存を許可します。
$fillableについてはこちらの記事が分かりやすかったです。
ありがとうございます。

コントローラー

app/Http/Controllers/GameController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Game;
use Illuminate\Support\Facades\Auth;
use Storage;

class GameController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */

    //下記がindexアクション
    public function index()
    {
        $games = Game::all();
        return view('game.index', compact('games'));
    }
}

$gamesGame::all();でGameモデルから全てのデータを取ってきて代入しています。全てとるので複数形の$gamesになってます。

プログラミング学び始めのとき、複数形なのか単数形なのか分からなかったんですよね。
allで全てとる=複数のデータ=つまり複数形なので普通に考えれば簡単ですよね。

その後return viewindexのviewを返す動きになってます。
compact('games')と記述することでview側で$gamesという変数が使えます。
逆にcompact書かないとエラーになりますので注意です。

compactについてはこちらの記事が分かりやすかったです。
ありがとうございます。

ビュー

resources/views/game/index.blade.php
<section class="page-section bg-light" id="portfolio">
            <div class="container">
                <div class="text-center">
                    <h2 class="section-heading text-uppercase">新着ボードゲーム</h2>
                    <h3 class="section-subheading text-muted">Lorem ipsum dolor sit amet consectetur.</h3>
                </div>

                <div class="row">
                    @foreach($games as $game)
                    @if($game->image_path)
                    <div class="col-lg-4 col-sm-6 mb-4">
                        <div class="portfolio-item" style="text-align:center">
                            <a class="portfolio-link" href="{{ route('game.show', $game->id) }}">
                                <img src="{{ $game->image_path }}" height="200" width="200">
                            </a>
                            <div class="portfolio-caption" style="margin-top:10px">
                                <div class="portfolio-caption-heading">{{ $game->name }}</div>
                                <div class="portfolio-caption-subheading text-muted">プレイ時間:{{ $game->play_time }}分</div>
                                <div class="portfolio-caption-subheading text-muted">人数:{{ $game->players_minimum }}~{{ $game->players_max }}人</div>
                            </div>
                        </div>
                    </div>
                    @endif
                    @endforeach
                </div>

            </div>
        </section>
resources/views/game/index.blade.php
@foreach($games as $game)
    //この中に処理を書く
@endforeach

上記のようにforeachで繰り返し処理をしています。
コントローラーがデータを$gamesに入れてviewに渡してくれているのでそれをforeachの中では$gameとして一つずつ取り出します。

つまり一つずつ呼び出すためには

resources/views/game/index.blade.php
{{ $game->name }}

上記のように$gamenameというように記述します。

こちらで一覧表示の実装は完了です。

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

Laravelでnpm runしたときのメモ

Vueテンプレートを使って、Laravelの認証画面を作ろうとして詰まった。

  • Laravelに laravel/uiを導入
  • vueテンプレートを指定
  • npmインストール

この流れでやると失敗した。

こちらの記事の内容も試してみたが、エラーは解決しなかった。
laravelでnpm run devを実行すると「cross-env: not found」というエラーが出る件対応したった

npm installまではうまくいくが、npm run devでエラーになる

npm ERR! code 1
npm ERR! path /var/www/html/larabel
npm ERR! command failed
npm ERR! command sh -c cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js

npm ERR! A complete log of this run can be found in:
npm ERR!     /root/.npm/_logs/2021-02-12T22_41_21_472Z-debug.log
npm ERR! code 1
npm ERR! path /var/www/html/laravel
npm ERR! command failed
npm ERR! command sh -c npm run development

npm ERR! A complete log of this run can be found in:
npm ERR!     /root/.npm/_logs/2021-02-12T22_41_21_507Z-debug.log

1. webpack.mix.jsの修正

mix.js('resources/js/app.js', 'public/js')
    .vue() // → 削除
    .sass('resources/sass/app.scss', 'public/css');

npm 再インストール

rm -rf node_modules/
rm -rf package-lock.json

npm install
npm run dev

これで解決した。

98% after emitting SizeLimitsPlugin

 DONE  Compiled successfully in 24793ms                               10:47:18 PM

       Asset      Size   Chunks             Chunk Names
/css/app.css   179 KiB  /js/app  [emitted]  /js/app
  /js/app.js  1.41 MiB  /js/app  [emitted]  /js/app
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel8.xのプロジェクトをXREAで無料公開する

概要

Laravel8+MySQLで作ったプロジェクトを無料でホスティングしたいなーと思って調べていたら、XREA(エクスリア)というレンタルサーバーを見つけました。無料プランながらシンプルで使いやすく、Laravelアプリの公開もかなり簡単にできたので、こちらに手順をまとめておきます。
Laravel以前にかなり基本的な部分から書いてありますので、わかる部分は適当に読み飛ばして使ってください。

前提

  • Laravel8系のプロジェクトができている
  • githubなどにソースコードが上がっている
  • (MacOS)

手順

1. バリュードメインのユーザー登録、XREAのアカウント作成

2. ドメインの購入(特にこだわりがなければ必要ありません)

3. XREAで各種設定
- 3.1 SSH接続の許可
- 3.2 DBの作成
- 3.3 サイト設定の変更 (ドメインを購入していない場合のみ)
- 3.4 ドメイン設定(ドメインを購入した場合のみ)
- 3.5 サイト設定(ドメインを購入した場合のみ)

4. SSHログイン
- 4.1 シェルをbashに変更
- 4.2 composerのセットアップ
- 4.3 node,npmのバージョンアップ

5. プロジェクトの設定
- 5.1 git cloneで持ってくる
- 5.2 composer、npm周り
- 5.3 .envの設定
- 5.4 マイグレーション等

6. シンボリックリンクの設定

変に詰まらなければ20分くらいで終わると思います。

1. バリュードメインのユーザー登録、XREAのアカウント作成

https://www.xrea.com/signup/

こちらにアクセスして手順通りにバリュードメインとXREAの登録をします。

  • バリュードメイン
    image.png

  • XREA
    test2.png

2021年2月現在、XREAに登録すると1年間無料で.shopのドメインがついてきます。次の2.ドメインの購入を行わない方は、ここで決めたアカウント名がそのままURLになります。
希望のサーバー選択はおそらく無料プランでは選べないようになっているので、そのまま登録してください。

2. ドメインの購入

希望のドメインがある方は、バリュードメインのドメイン登録から、希望のドメインの検索をして購入します。

https://www.value-domain.com/domain/search/
image.png

現在は.tokyoドメインが安く、(取られてなければ)初年度25円で取得できるみたいです。
支払いもクレジットカードからコンビニ決済まで対応しているので結構気軽に買えます。
image.png
購入後は登録者情報の入力など手順通りに進めてください。

3. XREAでの各種設定

XREA登録時にメールが届いているので確認します。
test.png
ログインに必要な情報が記載してあるので、これを使ってXREAにログインします。
https://cp.xrea.com/account/login/

XREAのパスワードはバリュードメインに自分で登録したものとは違うので注意してください。
また、XREAでは独自のパスワードが設定できないため、このパスワードはどこかにメモしておくといいと思います。
test.png
ログイン後の画面です。
test.png
※もし旧コントロールパネルが表示されている場合は、右上の新コンパネ切替をONにして新コントロールパネルに移行します。
image.png

3.1 SSH接続の許可

メニューのサイト設定からツール/セキュリティーを開き、下の方にあるSSH接続IP許可のボタンをクリックします。
これでSSH接続が可能になります。
image.png

3.2 DBの作成

メニューのデータベースから、データベースの新規作成をクリックします。
test.png
無料プランでは、XREAのユーザー名がそのままDB名になります。
パスワードは後ほど.envのDB情報に入力するので覚えておいてください。
 

3.3 サイト設定の変更 (ドメインを購入していない場合のみ)

ついてきた.shopのドメインを使う場合は、以下の設定をしてください。

メニューのサイト設定から、wwwのついている方のサイトについての設定を変更します。
test.png
PHPのバージョンが7.3以上でないとLaravel8は動かないため、バージョンを7.4にします。
test.png

3.4 ドメイン設定 (ドメインを購入した場合のみ)

購入したドメインを使う場合は以下の設定をしてください。

メニューのドメイン設定からドメイン設定の新規設定をクリックし、先ほど取得したドメインを設定します。
Frame 10.png

3.5 サイト設定 (ドメインを購入した場合のみ)

次にメニューのサイト設定から先ほど設定したドメインについて設定します。
ドメイン名に取得したドメイン、SSLは無料SSL、PHPは7.4を選んでください(Laravel8はPHP7.3以上が必要なため)。
test.png

4. SSHログイン

XREAの画面の右上に、ユーザー名とホスト名が表示されています。
test.png
コマンドラインで以下のように入力します。

$ ssh {user}@{host}

$ ssh hoge1234@s202.xrea.com

パスワードの入力を要求されるので、XREAのパスワード(先ほど送られてきたメールに記載してあるもの)をコピペで入力します。

[hoge1234@s202 ~]$

このように表示されればログイン成功です。

4.1 シェルをbashへ変更

デフォルトのシェルはrbashになっています。このままではcdなどのコマンドが使えないため、bashに変更します。

$ chsh
hoge1234 のシェルを変更します。
新しいシェル [/bin/rbash]: /bin/bash
パスワード:
シェルを変更しました。
$ exit
ログアウト
Connection to s202.xrea.com closed.

パスワードはSSHログイン時と同じです。
bashに変更したあと一度ログアウトして再度入ると変更が反映されており、cdなどのコマンドが使えるようになっています。

4.2 composerのセットアップ

Download Composerより、以下のコマンドを実行してください。
ただし、CLI版PHPを使うためコマンドがphp74cliとなっていることに注意してください。

また、2行目ではハッシュ値のチェックによりオリジナルファイルかどうかを確認していますが、このハッシュ値はcomposer-setup.phpの変更に伴って変わります。下記のハッシュがそのとき使えるとは限らないので、下記のコピペではなく上記のリンクから公式のコマンド(ハッシュ値)を持ってきて実行してください。

$ php74cli -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
$ php74cli -r "if (hash_file('sha384', 'composer-setup.php') === '756890a4488ce9024fc62c56153228907f1545c228516cbf63f885e036d37e9a59d27d63f46af1d4d07ee0f76181c7d3') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
$ php74cli composer-setup.php
$ php74cli -r "unlink('composer-setup.php');"

コマンドを実行し終えたらcomposerを叩いてみます。

$ php74cli -d register_argc_argv=1 ~/composer.phar --version
Composer version 2.0.9 2021-01-27 16:09:27

実行コマンドが長いのでエイリアスに登録しておきます。(パスの通し方がイマイチわかりませんでした…)
viで.bash_profileに以下を記載します。

.bash_profile
alias composer="php74cli -d register_argc_argv=1 ~/composer.phar"

反映します。

$ source ~/.bash_profile
$ composer --version
Composer version 2.0.9 2021-01-27 16:09:27

これでcomposerのセットアップは終わりです。

4.3 node,npmのバージョンアップ

node,npmも入っているもののかなり古いので、バージョンアップします。

$ node -v
v6.17.1

$ npm -v
3.10.10

node.js Downloads

現在のLatestはnode: 14.15.5,npm: 6.14.11だったので、とりあえずそれを持ってきます。

# curlで圧縮ファイルを取ってくる
$ curl -sSL -O https://nodejs.org/dist/v14.15.5/node-v14.15.5-linux-x64.tar.xz

# 解凍
$ tar xvf ./node-v14.15.5-linux-x64.tar.xz

# 圧縮ファイルの削除
$ rm node-v14.15.5-linux-x64.tar.xz

#ディレクトリを用意する
$ mkdir ~/.local/lib

# 移動する
$ mv ./node-v14.15.5-linux-x64 ~/.local/lib/

パスを通すために.bash_profileに以下を追記します。

.bash_profile
export PATH="${HOME}/.local/lib/node-v14.15.5-linux-x64/bin:${PATH}"

反映します。

$ source ~/.bash_profile
$ node -v
v14.15.5

$ npm -v
6.14.11

nodeとnpmのバージョンアップもできました。

5. プロジェクトの設定

5.1 git cloneで持ってくる

ホームディレクトリのpublic_html内には、XREAのサイト設定にあるサイトごとにディレクトリが用意されています。(ドメイン購入をした場合は、元からついている.shopのディレクトリに加えて購入したドメインのディレクトリがあると思います。)

$ cd ~/public_html/
$ ls
log  hoge1234.shop

XREAではデフォルトでgitが使えます。
使いたいドメインのディレクトリに入り、git cloneします。

$ cd hoge1234.shop
$ git clone https://github.com/hoge/fuga.git

5.2 composer, npm周り

ここはローカルでの作業とあまり変わりません。
cloneしたプロジェクトのディレクトリに入り、下記コマンドを実行します。

# composerのライブラリのインストール
$ composer install

# npmパッケージのインストール
$ npm install

# LaravelMixでアセットをコンパイル
$ npm run dev

5.3 .envの設定

.envをサーバーに合わせて設定していきます。

$ cp .env.example .env
$ vi .env

以下のように変更します。

.env
+ APP_NAME={appname} (自分で決めたappの名前)
+ APP_ENV=production
APP_KEY=
+ APP_DEBUG=false
+ APP_URL=https://{domain} (使うドメイン)

LOG_CHANNEL=stack
+ LOG_LEVEL=error

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
+ DB_DATABASE=hoge1234 (3.2 DBの作成 で決めたDB名、ユーザー名と同じ)
+ DB_USERNAME=hoge1234 (ユーザー名)
+ DB_PASSWORD={password} (3.2 DBの作成 で決めたパスワード)

5.4 migration等

こちらもローカルでの作業と変わりません。
ただし、artisanコマンドを使用する際はphp74cli artisanとなることに注意してください。

# keyの生成
$ php74cli artisan key:generate

# migration
$ php74cli artisan migrate

# seederがある場合
$ php74cli artisan db:seed

ローカルで動かす時と同じようにコマンドを叩いてください。

6. シンボリックリンクの設定

Laravelプロジェクトの準備はできたので、参照先の設定をします。
ここもついてくる.shopを使う方とドメインを購入した方とで少し違うので注意してください。

現在のディレクトリ構成は以下のようになっていると思います。

public_html
    |
    |-- {domain}
           |
           |-- {appname}
                   |
                   |-- app
                   |-- bootstrap
                   |-- config
                   |-- database
                   |-- ...
                   |-- ...
                   |-- public
                   |-- ...

.shopを使う場合、hoge1234.shopにアクセスするとwww.hoge1234.shopにリダイレクトされ、~/public_html/をルートとしてファイルを参照するようになっています。

~/public_html/を参照した際に、~/public_html/{domain}/{appname}/public/のファイルを代わりに参照するようにします。

$ ln -s ~/public_html/{domain}/{appname}/public/index.php ~/public_html/index.php
$ ln -s ~/public_html/{domain}/{appname}/public/.htaccess ~/public_html/.htaccess
$ ln -s ~/public_html/{domain}/{appname}/public/js ~/public_html/js
$ ln -s ~/public_html/{domain}/{appname}/public/css ~/public_html/css
$ ln -s ~/public_html/{domain}/{appname}/public/img ~/public_html/img
$ ln -s ~/public_html/{domain}/{appname}/public/mix-manifest.json ~/public_html/mix-manifest.json
$ ln -s ~/public_html/{domain}/{appname}/public/robots.txt ~/public_html/robots.txt
$ ln -s ~/public_html/{domain}/{appname}/public/favicon.ico  ~/public_html/favicon.ico

購入したドメインを使う場合、fuga1234.comにアクセスすると~/public_html/fuga1234.com/をルートとしてファイルを参照するようになっています。
~/public_html/fuga1234.com/を参照した際に、~/public_html/{domain}/{appname}/public/のファイルを代わりに参照するようにします。

$ ln -s ~/public_html/{domain}/{appname}/public/index.php ~/public_html/{domain}/index.php
$ ln -s ~/public_html/{domain}/{appname}/public/.htaccess ~/public_html/{domain}/.htaccess
$ ln -s ~/public_html/{domain}/{appname}/public/js ~/public_html/{domain}/js
$ ln -s ~/public_html/{domain}/{appname}/public/css ~/public_html/{domain}/css
$ ln -s ~/public_html/{domain}/{appname}/public/img ~/public_html/{domain}/img
$ ln -s ~/public_html/{domain}/{appname}/public/mix-manifest.json ~/public_html/{domain}/mix-manifest.json
$ ln -s ~/public_html/{domain}/{appname}/public/robots.txt ~/public_html/{domain}/robots.txt
$ ln -s ~/public_html/{domain}/{appname}/public/favicon.ico  ~/public_html/{domain}/favicon.ico

public内の各ファイルについてシンボリックリンクを貼っています。
他にもpublic内にfontsなど別のアセットがある場合はその分のシンボリックリンクも貼ります。

これでURLにアクセスしてみて、表示されていたら成功です。

.shopを使う場合、SSL設定ができないのでhttps://をつけると上手くいきません。

最後に

ここまで書きましたがやっぱりherokuの方が楽な気がし
XREAでLaravelプロジェクトを無料公開する手順を最初から最後まで書いている記事が見つからなかったため、いろいろ調べた勢いで書いてみました。
ところどころ知識が甘く曖昧な表現などがあると思いますが、「とりあえずなんでもいいからLaravelで作ったものを公開してみたい!」という方のお役に立てれば幸いです。

参考記事

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