20201208のlaravelに関する記事は11件です。

Laravel Duskエラー 「Facebook\WebDriver\Exception\UnknownErrorException: unknown error: net::ERR_CONNECTION_REFUSED」

発生したタイミング

 テストコード作成後、下記コマンド実行時にタイトル記載のエラーが表示

php artisan dusk

原因

 調べるとどうやら.envに記載したAPP_URLパラメータの値が間違っている模様

原因解決

 .envのAPP_URLパラメータの値をブラウザからアクセスするアプリケーションで使用するURLと一致させる。
 現在デプロイをしておらずローカル環境だった為下記のように変更する。

APP_URL=http://localhost:8000/

修正後コマンド実行

これで解決したと思い再度php artisan duskを実行すると......。
同じエラーが。
.......原因はサーバーを立ち上げていなかったから。
下記コマンドでサーバーを立ち上げ。。。。

php artisan serve

新規タブで

php artisan dusk

これで無事テストが実行されました!!!
以上

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

Laravel Duskの導入

導入背景

 現在Laravelで作成したポートフォリオのテストをデフォルトで搭載されているテストディレクトリを使用して実行していたが、利用が簡単なブラウザの自動操作/テストAPIであるDuckの存在を知ったので使用してみた

導入方法

プロジェクトに移動し、パッケージを取得

cd (プロジェクト名)
composer require --dev laravel/dusk
php artisan dusk:install

上記後、プロジェクトのtestディレクトリ内にBrowserディレクトリが作成される

.envの変更

.envファイル内のAPP_URL環境変数を変更する。
値はブラウザからアクセスするアプリケーションで使用するURLと一致させます。
※ローカル環境の場合

//デフォルト)
APP_URL=http://localhost
↓
//変更後)
APP_URL=http://localhost:8000

参考文献

https://readouble.com/laravel/7.x/ja/dusk.html

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

Laravel/Socialiteが何をしているか調べてみた ~①リダイレクト編~

目標

Laravelで「Googleでログイン」を実装する際に導入する"Socialite"がどんなことをやっているのか調査してみた。
①リダイレクト編の目標は、Controllerに記述することになる下記コードが最終的なアウトプットは何か、どのように実装されているのかを明確にする。

以下Controllerに記述することになるコード

SocialOAuthController.php
/**
 * Googleアカウントの認証ページへユーザーをリダイレクト
 * ①リダイレクト編で解説するコード
 */
public function redirectToGoogle()
{
    return Socialite::driver('google')->redirect();
}

/**
 * Googleアカウントからユーザー情報を取得
 * ②コールバック編で解説予定
 */
public function handleGoogleCallback()
{
    $user = Socialite::driver('google')->stateless()->user();
}

先に結論(最終的なアウトプット)

認可URLにリダイレクトするRedirectResponseインスタンスを生成します。
リダイレクト先のURLは下記の通りです。

RequestURL = https://accounts.google.com/o/oauth2/auth?
client_id=$client_id
&redirect_url=$redirect_url
&scope=$scope1+$scope2+$scope3
&response_type=code
&state=$state

<環境>

Laravel Framework 8.17.2
laravel/socialite v5.1.2

<解説>

Socialite

SocialiteのファサードはSocialiteServiceProviderで下記のように定義されています。

SocialiteServiceProvider.php
namespace Laravel\Socialite;

class SocialiteServiceProvider extends ServiceProvider
{
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(Factory::class, function ($app) {
            return new SocialiteManager($app);
        });
    }
}

Socialite::driver('google')

driverメソッドに文字列でドライバを指定しています。
Socialiteクラスの基底クラスであるManagerクラスにdriverメソッドで引数に渡した文字列に該当するドライバが可変関数として呼び出されます。

Manager
namespace Illuminate\Support;

abstract class Manager
{
    /**
    * Get a driver instance.
    *
    * @param  string|null  $driver
    * @return mixed
    *
    * @throws \InvalidArgumentException
    */
    public function driver($driver = null)
    {
        $driver = $driver ?: $this->getDefaultDriver();

        if (is_null($driver)) {
            throw new InvalidArgumentException(sprintf(
                'Unable to resolve NULL driver for [%s].', static::class
            ));
        }

        // ドライバのインスタンスはキャッシュして再利用される。
        if (! isset($this->drivers[$driver])) {
            $this->drivers[$driver] = $this->createDriver($driver);
        }

        return $this->drivers[$driver];
    }

    /**
    * Create a new driver instance.
    *
    * @param  string  $driver
    * @return mixed
    *
    * @throws \InvalidArgumentException
    */
    protected function createDriver($driver)
    {
        if (isset($this->customCreators[$driver])) {
            return $this->callCustomCreator($driver);
        } else {
            $method = 'create'.Str::studly($driver).'Driver';

            if (method_exists($this, $method)) {
                // ここでcreateGoogleDriver()が呼び出される
                return $this->$method();
            }
        }

        throw new InvalidArgumentException("Driver [$driver] not supported.");
    }
}

createGoogleDriver()

createGoogleDriverメソッドではconfigファイル(services.google)に設定した情報を取得し、GoogleProviderクラスのインスタンスを生成しています。

SocialiteManager.php
namespace Laravel\Socialite;

class SocialiteManager extends Manager implements Contracts\Factory
{
    /**
    * Create an instance of the specified driver.
    *
    * @return \Laravel\Socialite\Two\AbstractProvider
    */
    protected function createGoogleDriver()
    {
        $config = $this->config->get('services.google');

        return $this->buildProvider(
        GoogleProvider::class, $config
        );
    }
}

AbstractProviderを継承するGoogleProviderインスタンスを生成するまでがSocialite::driver('google')の動きです。

Socialite::driver('google')->redirect()

redirectメソッドでは生成されたリダイレクトURLにリダイレクトするようにRedirectResponseクラスのインスタンスを生成している。

AbstractProvider.php
namespace Laravel\Socialite\Two;

abstract class AbstractProvider implements ProviderContract
{
    /**
     * Redirect the user of the application to the provider's authentication screen.
     *
     * @return \Illuminate\Http\RedirectResponse
     */
    public function redirect()
    {
        $state = null;

        if ($this->usesState()) {
            $this->request->session()->put('state', $state = $this->getState());
        }

        // ここでRedirectResponseクラスのインスタンスを生成
        return new RedirectResponse($this->getAuthUrl($state));
    }



}

getAuthUrl()

getAuthUrlメソッドはGoogleProviderで定義されている

GoogleProvider.php
namespace Laravel\Socialite\Two;

use Illuminate\Support\Arr;

class GoogleProvider extends AbstractProvider implements ProviderInterface
{
     /**
     * {@inheritdoc}
     */
    protected function getAuthUrl($state)
    {
        return $this->buildAuthUrlFromBase('https://accounts.google.com/o/oauth2/auth', $state);
    }
}

AbstractProviderに戻ってURLがどのように生成されるか見ていく。
GoogleのベースURLにクエリパラメータとして認可に必要な各項目をセットしている。

AbstractProvider.php
    /**
     * Build the authentication URL for the provider from the given base URL.
     *
     * @param  string  $url
     * @param  string  $state
     * @return string
     */
    protected function buildAuthUrlFromBase($url, $state)
    {
        // $this->encodingTypeはPHP_QUERY_RFC1738が定義されている
        return $url.'?'.http_build_query($this->getCodeFields($state), '', '&', $this->encodingType);
    }

    /**
     * Get the GET parameters for the code request.
     *
     * @param  string|null  $state
     * @return array
     */
    protected function getCodeFields($state = null)
    {
        $fields = [
            'client_id' => $this->clientId,
            'redirect_uri' => $this->redirectUrl,
            'scope' => $this->formatScopes($this->getScopes(), $this->scopeSeparator),
            'response_type' => 'code',
        ];

        if ($this->usesState()) {
            $fields['state'] = $state;
        }

        return array_merge($fields, $this->parameters);
    }

    /**
     * Format the given scopes.
     *
     * @param  array  $scopes
     * @param  string  $scopeSeparator
     * @return string
     */
    protected function formatScopes(array $scopes, $scopeSeparator)
    {
        return implode($scopeSeparator, $scopes);
    }
}


<おまけ>認可リクエストのカスタマイズ

Laravel公式にもあるようにパラメータや、スコープの追加をすることができる。

パラメータの追加

パラメータを追加するにはwithメソッドを使用します。
$parametersの規定値は空の配列[]です。

AbstractProvider.php
    /**
     * Set the custom parameters of the request.
     *
     * @param  array  $parameters
     * @return $this
     */
    public function with(array $parameters)
    {
        $this->parameters = $parameters;
        return $this;
    }

scopeの追加

scopeに規定値は['openid','profile','email']が定義されている。
追加するには下記のようにscopesメソッドを呼び出すか、setScopeメソッドで上書きするとよい。

SocialOAuthController.php
/**
 * Googleアカウントの認証ページへユーザーをリダイレクト
 */
public function redirectToGoogle()
{
    return Socialite::driver('google')->scopes(['read:user', 'public_repo'])->redirect();
}

メソッドの中身は下記のように配列がマージされる。
array_uniqueメソッドでは、重複した要素が存在する場合、はじめの要素のキーと値が保持されます。
https://www.php.net/manual/ja/function.array-unique.php

AbstractProvider.php
    /**
     * Merge the scopes of the requested access.
     *
     * @param  array|string  $scopes
     * @return $this
     */
    public function scopes($scopes)
    {
        // 
        $this->scopes = array_unique(array_merge($this->scopes, (array) $scopes));
        return $this;
    }

    /**
     * Set the scopes of the requested access.
     *
     * @param  array|string  $scopes
     * @return $this
     */
    public function setScopes($scopes)
    {
        $this->scopes = array_unique((array) $scopes);
        return $this;
    }

リダイレクトURLの変更

リダイレクトURLが複数存在し、configファイルではなく動的に値を変更したい場合はredirectURLメソッドを使用します。

SocialOAuthController.php
/**
 * Googleアカウントの認証ページへユーザーをリダイレクト
 */
public function redirectToGoogle()
{
    return Socialite::driver('google')->redirectURL($redirectUrl)->redirect();
}

AbstractProvider.php
    /**
     * Set the redirect URL.
     *
     * @param  string  $url
     * @return $this
     */
    public function redirectUrl($url)
    {
        $this->redirectUrl = $url;

        return $this;
    }

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

Laravelですでにあるマイグレーションファイルを書き換えてテーブル定義を変更する

「テーブル作成のマイグレーションファイルに外部キー制約書き忘れた!」みたいなときの対処法です。
マイグレーションファイルが増えるのが気にならない場合や、複数人で管理している場合は、新しくマイグレーションを追加してテーブル定義を変更したほうがいいと思います。

1. テーブルを出力する

対象のデータベースのテーブルを全て出力します。

$ mysqldump --no-create-info --ignore-table=database_name.migrations -u user_name -ppassword > ~/dumps/database_name_20210101.dump

ポイント

  • --no-create-infoオプション : マイグレーションでテーブルは作成するので、CREATE TABLE ~を出力しないオプションです。INSERT INTO ~のみが出力されます。
  • --ignore-table=database_name.migrations : マイグレーションデータが保存されているテーブルを出力しません。

2. マイグレートする

ファイルを書き換えたあと、マイグレーションを実行します。

php artisan migrate:fresh

注意
migrate:freshはデータベース内の全てのテーブルをDropするので、データベースをLaravelアプリケーション以外と共有している場合はmigrate:refresh等を検討してください。

3. 挿入する

出力したデータを再度データベースに追加します。変更したテーブル定義が、変更前と互換性がないと、挿入できないので注意してください。

mysql -u user_name -ppassword -D database_name < ~/dumps/database_name_20210101.dump

エラーがでなければ、大丈夫です。

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

php artisan migrate で autoload_real.php のシンタックスエラーが出た話

php artisan migrate でエラー

$ php artisan migrate
PHP Parse error:  syntax error, unexpected '' (T_ENCAPSED_AND_WHITESPACE), expecting ']' in /home/vagrant/***/vendor/composer/autoload_real.php on line 71

Parse error: syntax error, unexpected '' (T_ENCAPSED_AND_WHITESPACE), expecting ']' in /home/vagrant/***/vendor/composer/autoload_real.php on line 71

composer update でもエラー

$ composer update

PHP Fatal error:  Allowed memory size of 1610612736 bytes exhausted (tried to allocate 134217736 bytes) in phar:///usr/local/bin/composer/src/Composer/DependencyResolver/RuleSet.php on line 83

Fatal error: Allowed memory size of 1610612736 bytes exhausted (tried to allocate 134217736 bytes) in phar:///usr/local/bin/composer/src/Composer/DependencyResolver/RuleSet.php on line 83

Check https://getcomposer.org/doc/articles/troubleshooting.md#memory-limit-errors for more info on how to handle out of memory errors.⏎ 

composer i は成功するが、今度は autoload_static.php でエラー

$ composer i
# 成功
$ php artisan migrate
PHP Parse error:  syntax error, unexpected 'C' in /home/vagrant/***/vendor/composer/autoload_static.php on line 8765

Parse error: syntax error, unexpected 'C' in /home/vagrant/***/vendor/composer/autoload_static.php on line 8765

/vendor/composer/autoload_static.php を削除

$ rm /vendor/composer/autoload_static.php
$ php artisan migrate
PHP Warning:  require_once(/home/vagrant/***/vendor/composer/autoload_static.php): failed to open stream: No such file or directory in /home/vagrant/***/vendor/composer/autoload_real.php on line 28

Warning: require_once(/home/vagrant/***/vendor/composer/autoload_static.php): failed to open stream: No such file or directory in /home/vagrant/***/vendor/composer/autoload_real.php on line 28
PHP Fatal error:  require_once(): Failed opening required '/home/vagrant/***/vendor/composer/autoload_static.php' (include_path='.:/usr/share/php') in /home/vagrant/***/vendor/composer/autoload_real.php on line 28

Fatal error: require_once(): Failed opening required '/home/vagrant/***/vendor/composer/autoload_static.php' (include_path='.:/usr/share/php') in /home/vagrant/***/vendor/composer/autoload_real.php on line 28

/vendor/composer/autoload_real.php を削除

$ rm /vendor/composer/autoload_real.php
$ php artisan migrate
PHP Warning:  require_once(/home/vagrant/***/vendor/composer/autoload_real.php): failed to open stream: No such file or directory in /home/vagrant/***/vendor/autoload.php on line 5

Warning: require_once(/home/vagrant/***/vendor/composer/autoload_real.php): failed to open stream: No such file or directory in /home/vagrant/***/vendor/autoload.php on line 5
PHP Fatal error:  require_once(): Failed opening required '/home/vagrant/***/vendor/composer/autoload_real.php' (include_path='.:/usr/share/php') in /home/vagrant/***/vendor/autoload.php on line 5

Fatal error: require_once(): Failed opening required '/home/vagrant/***/vendor/composer/autoload_real.php' (include_path='.:/usr/share/php') in /home/vagrant/***/vendor/autoload.php on line 5

composer i 再度実行で成功

$ composer i
$ php artisan migrate
# 成功

結論

vender/composer/ 配下は composer によって自動生成されるので、その配下のファイルでエラーが出たら怖がらずに削除して、再度 composer i すればよし。

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

Laravelのフォームリクエストで他のパラメータを参照する方法

やりたいこと

いろいろあって、バリデーション時に他の入力値を使ってバリデーションしたい。
(今回は、ユーザー情報を暗号化していたため、他の入力値を元にユーザーを探して、一致するかどうかを確認したかった)

方法

$this->request->get()を使用する。

laravel/app/Http/Requests/HogeRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use App\Rules\BirthdayMatched;

class HogeRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        // getメソッドで入力値を取得する。
        $email = $this->request->get('email');

        return [
            'email' => ['required', 'string', 'email'],
            'birthday' => ['required', 'string', new BirthdayMatched($email)],
        ];
    }

あとはカスタムバリデーションルールのプロパティに代入して使用する。

laravel/app/Rules/BirthdayMatched.php
<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;
use App\User;

class BirthdayMatched implements Rule
{
    private $email;

    /**
     * Create a new rule instance.
     *
     * @return void
     */
    public function __construct(string $email)
    {
        $this->email = $email;
    }

    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        //ここで$this->emailを使ったバリデーション処理をする。
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return '誕生日が一致しません';
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel  ユーザー登録と認証機能

Laravelの認証機能について学習している中で気になったことがあったのでまとめ

今までlaravelの学習をしていてユーザーの認証などはlaravel uiに頼りっきりであまり理解していなかった。
今回、自分で認証機能を設定して作成する場合にどういった手順で進めていけばよいかを記載していく。

〇ユーザー登録機能を自作する場合
web.phpを確認し、元々設定されているユーザー登録の廃止

キャプチャ.PNG

上記のコマンドを実行する。

そうすると、web.php内に、

キャプチャ2.PNG

このような、1文が追加される。この追加された文の引数を指定することにより、現在のユーザー登録機能のON/OFFが可能になる。

引数を指定してユーザー登録機能をOFFにする
キャプチャ3.PNG

あとは、RegisterController.phpを編集することで任意の認証機能の実装が可能になる。

Controller内の設定などについてはまた勉強していく上で気が付いたことがあれば記述していく。

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

Laravel8で完成されたModelFactoryの使い方

Laravel Advent Calendar 2020 - Qiita の 8日目 の記事です。
昨日は @okdyy75 さんのLaravelを触って1年経ったのでTIPSの記事でした!
明日は @kaino5454 さんのポケモンAPIを叩いて使用率ランキングを取得してみたの記事です!

概要

Laravel8からModelFactoryの機能が刷新されました。
新しい書き方についてまとめたいと思います。

こちらの記事をリスペクトしてタイトル名に使わせていただきました???

予備知識

Laravelのデータベース関連の用語として、Migration、Seeder、ModelFactory、Fakerの4つあります。

Migration(マイグレーション)

https://readouble.com/laravel/8.x/ja/migrations.html

マイグレーションはデータベースのテーブルの定義を書きます。
英語で「移動・移行」という意味になります。

テーブルの変更のソースコードで管理することができます。
そのため本番環境へリリースしたり、新しいメンバーが参画した際にも簡単に同じテーブルの状態を再現できます。

artisan コマンド使用例

$ php artisan make:migration create_posts_table # マイグレーションのテンプレート生成
$ php artisan migrate # マイグレーション実行
$ php artisan migrate:fresh # 全テーブル削除、全マイグレーション再実行
$ php artisan migrate:fresh --seed # シーディングも行う

同じ名前のマイグレーションファイルはエラーになるので、注意して命名していく必要があります。
1ファイルで複数テーブルのマイグレーション定義は書かないほうが良いです。

Seeder(シーダー)

https://readouble.com/laravel/8.x/ja/seeding.html

シーダーはデータベースにダミーデータを一斉に挿入できる機能です。
英語で「種をまく人」という意味になります。(Seeding/シーディングとも表記されます)

artisan コマンド使用例

$ php artisan make:seeder PostsSeeder # シーダーのテンプレート生成
$ php artisan db:seed # DatabaseSeeder を実行
$ php artisan db:seed --class=PostsSeeder # クラス指定して実行

テーブル単位やデータのまとまり毎に書くと良いでしょう。

ModelFactory(モデルファクトリ)

https://readouble.com/laravel/8.x/ja/database-testing.html#creating-factories

モデルファクトリはEloquentモデルの各フィールドに入る値を定義します。
モデルの工場といったところです。

主にシーダーやテストコードからモデルファクトリーは呼び出されます。

artisan コマンド使用例

$ php artisan make:factory PostFactory # モデルファクトリのテンプレート生成

Facker(フェイカー)

フェイカーはダミーデータ生成用のライブラリです。
主にモデルファクトリを定義する際に使用します。

名前や住所、メールアドレスといったよく使われるテストデータをランダムに生成してくれる便利な機能です。

補足: fzaninotto/Fakerのアーカイブ

最近の話(2020年10月21日)ですが、 fzaninotto/Faker の本家のリポジトリがアーカイブされています。
詳細については、下記のリンクをご参照ください。

今後はフォーク先のリポジトリでメンテナンスが続けられるそうです。
また、こちらのプルリクエスト で既にライブラリの変更が行われてます。

  • v8.12.0 (2020-10-29)
  • v7.29.0 (2020-10-29)
  • v6.20.0 (2020-10-28)

上記のバージョン以降のLaravelをご利用であればフォーク先のリポジトリが使用されます。

tinkerでお試し実行方法

ModelFactory, Fakerはtinker上で簡単にお試しできます。
どんなテストデータが生成されるのか気になる場合は、tinkerで試してみましょう!

ModelFactoryのお試し実行方法

$ php artisan tinker

# make() だとインスタンスのみ返す
>>> App\Models\User::factory()->make()
=> App\Models\User {#3385
     name: "Miss Dolores Mueller III",
     email: "marcus19@example.org",
     email_verified_at: "2020-12-06 14:58:34",
   }

# create() だとデータベースに挿入してインスタンスを返す
>>> App\Models\User::factory()->create()
=> App\Models\User {#3387
     name: "Annabel Upton III",
     email: "kenton.klocko@example.org",
     email_verified_at: "2020-12-06 14:58:39",
     updated_at: "2020-12-06 14:58:39",
     created_at: "2020-12-06 14:58:39",
     id: 1,
   }

Fakerのお試し実行方法

詳しい使い方はこちら https://fakerphp.github.io

$ php artisan tinker
>>> $faker = app()->make(Faker\Generator::class)

>>> $faker->name()
=> "中津川 和也"

# () なしでメソッドではなくプロパティへのアクセスでも実行可能
>>> $faker->name
=> "佐々木 学"

>>> $faker->safeEmail()
=> "kondo.yui@example.com"

>>> $faker->emoji()
=> "?"

>>> $faker->imageUrl()
=> "https://via.placeholder.com/640x480.png/0066ee?text=aut"

>>> $faker->dateTimeBetween('-2weeks', '9 days')->format('Y-m-d H:i:s')
=> "2020-11-28 05:24:51"

設定

Faker 日本語化

config/app.phpfaker_locale の値を変更してください。

config/app.php
    /*
    |--------------------------------------------------------------------------
    | Faker Locale
    |--------------------------------------------------------------------------
    |
    | This locale will be used by the Faker PHP library when generating fake
    | data for your database seeds. For example, this will be used to get
    | localized telephone numbers, street address information and more.
    |
    */

//    'faker_locale' => 'en_US',
    'faker_locale' => 'ja_JP',

実行すると日本語名のデータが入るようになります。

>>> App\Models\User::factory()->make()->name
=> "田中 桃子"

本題

横道に逸れてしまいましたが、ここからが本題のModelFactoryです。

シーダーからテストデータを挿入する

シーダーからテストデータをデータベースに挿入したい場合、こんな感じで生成することができます。

database/seeders/DatabaseSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        DB::table('users')->insert([
            'name' => Str::random(10),
            'email' => Str::random(10).'@gmail.com',
            'password' => Hash::make('password'),
        ]);
    }
}

数件程度であれば良いですが、開発が進むにつれて複雑なテストデータを作ろうとしていくとメンテナンスがとても大変になってしまいます...。

デフォルトで用意されているモデル

app/Models/User.php
<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
}

Illuminate\Database\Eloquent\Factories\HasFactory trait を use する必要があります。

デフォルトで用意されているモデルファクトリ

database/factories/UserFactory.php
<?php

namespace Database\Factories;

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

class UserFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = User::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
            'name' => $this->faker->name,
            'email' => $this->faker->unique()->safeEmail,
            'email_verified_at' => now(),
            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
            'remember_token' => Str::random(10),
        ];
    }
}

tinker からモデルファクトリーを呼び出して使う

$ php artisan tinker

App\Models\User::factory()->create()
=> App\Models\User {#3380
     name: "近藤 結衣",
     email: "uno.naoki@example.net",
     email_verified_at: "2020-12-06 15:43:28",
     updated_at: "2020-12-06 15:43:28",
     created_at: "2020-12-06 15:43:28",
     id: 3,
   }

// name 属性を上書きする
App\Models\User::factory()->create(['name' => 'ucan'])
=> App\Models\User {#4264
     name: "ucan",
     email: "sato.yuki@example.com",
     email_verified_at: "2020-12-06 15:43:46",
     updated_at: "2020-12-06 15:43:46",
     created_at: "2020-12-06 15:43:46",
     id: 4,
   }

Laravel7との違い

database/factories/UserFactory.php
<?php

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

use App\User;
use Faker\Generator as Faker;
use Illuminate\Support\Str;

/*
|--------------------------------------------------------------------------
| Model Factories
|--------------------------------------------------------------------------
|
| This directory should contain each of the model factory definitions for
| your application. Factories provide a convenient way to generate new
| model instances for testing / seeding your application's database.
|
*/

$factory->define(User::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'email_verified_at' => now(),
        'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
        'remember_token' => Str::random(10),
    ];
});

呼び出し方

factory(App\User::class)->create();

以前は $factory->define() 関数を使って登録、 factory() 関数を使って呼び出ししていく形でしたが、Laravel 8からクラスベースとなりました。

基本の流れ

posts テーブル

テーブルがないとテストデータを入れれないので、下記のようにマイグレーションファイルを作っておきます。

$ php artisan make:migration create_posts_table

database/migrations/YYYY_MM_DD_XXXXXX_create_posts_table.php といったマイグレーションファイルが生成されるので、下記のように追記しておきます。

database/migrations/2020_12_08_000000_create_posts_table.php
<?php

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

class CreatePostsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id');
            $table->string('title');
            $table->text('content');
            $table->string('published_at')->nullable();
            $table->timestamps();
        });
    }

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

モデル、モデルファクトリークラスの生成

新しく自分でファイルを作成しても良いですが、テンプレートを生成する便利コマンドがあるので使いましょう。

# モデルとファクトリー個別に生成する
$ php artisan make:model Post
$ php artisan make:factory PostFactory --model=Post

or

# モデルとファクトリーを生成する
$ php artisan make:model Post --factory
$ php artisan make:model Post -f # 省略オプション

or

# モデルとファクトリー、マイグレーション、シーダー、コントローラを生成する
$ php artisan make:model Post --all
$ php artisan make:model Post -a # 省略オプション

生成されるファイル

  • app/Models/Post.php
  • database/factories/PostFactory.php
app/Models/Post.php
<?php

namespace App\Models;

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

class Post extends Model
{
    use HasFactory;
}
database/factories/PostFactory.php
<?php

namespace Database\Factories;

use App\Models\Post;
use Illuminate\Database\Eloquent\Factories\Factory;

class PostFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = Post::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
            //
        ];
    }
}

database/factories/PostFactory.phpdefinition() を書き換えてみます。

database/factories/PostFactory.php
    public function definition()
    {
        return [
            'user_id' => \App\Models\User::factory(),
            'title' => $this->faker->title,
            'content' => $this->faker->paragraph,
        ];
    }

シーダーへ追記します。

database/seeders/DatabaseSeeder.php
    public function run(): void
    {
        \App\Models\Post::factory()->count(3)->create();
    }
$ php artisan migrate:fresh --seed
$ php artisan tinker

>>> App\Models\User::count();
=> 3

>>> App\Models\Post::count();
=> 3

>>> App\Models\User::first();
=> App\Models\User {#4044
     id: 1,
     name: "斉藤 里佳",
     email: "yamamoto.asuka@example.org",
     email_verified_at: "2020-12-06 16:43:25",
     created_at: "2020-12-06 16:43:25",
     updated_at: "2020-12-06 16:43:25",
   }

>>> App\Models\Post::first();
=> App\Models\Post {#3639
     id: 1,
     user_id: 1,
     title: "Dr.",
     content: "Aspernatur maiores qui neque iure cupiditate. Facilis voluptas consequatur mollitia placeat ea. Aperiam neque qui quae placeat debitis nobis minima.",
     published_at: null,
     created_at: "2020-12-06 16:43:25",
     updated_at: "2020-12-06 16:43:25",
   }

Laravelではこんな流れでテストデータを作成していきます。

詳細な使い方

https://readouble.com/laravel/8.x/ja/database-testing.html

公式ドキュメントに詳しい使い方が記載されているので詳細はこちらを参考すると良いです。

補足

公式ドキュメントに書かれてないけど、知ってると便利そうなことをいくつかご紹介します。

Fakerに独自のメソッドを追加する

FakerServiceProvider を作成します。

$ php artisan make:provider FakerServiceProvider

FakerServiceProvider を登録します。

config/app.php
    'providers' => [
        App\Providers\FakerServiceProvider::class,
    ],

FakerServiceProvider を登録します。

app/Providers/FakerServiceProvider.php
<?php declare(strict_types=1);

namespace App\Providers;

use App\Faker\Planet;
use Faker\Generator;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Support\ServiceProvider;

class FakerServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap services.
     *
     * @return void
     * @throws BindingResolutionException
     */
    public function boot(): void
    {
        $faker = $this->app->make(Generator::class);
        $faker->addProvider(new Planet($faker));
    }
}
app/Faker/Planet.php
<?php declare(strict_types=1);

namespace App\Faker;

use Faker\Provider\Base;

/**
 * 惑星をランダムに返す
 */
class Planet extends Base
{
    protected array $planet = [
        '水星',
        '金星',
        '地球',
        '火星',
        '木星',
        '土星',
        '天王星',
        '海王星',
    ];

    /**
     * @return string
     */
    public function planet(): string
    {
        return $this->generator->randomElement($this->planet);
    }
}
$ php artisan tinker
>>> $faker = app()->make(Faker\Generator::class)

>>> $faker->planet()
=> "海王星"

モデルファクトリーのリレーション定義

ファクトリークラスにリレーションの定義も行えます。
ただ私個人としては、ファクトリークラスはなるべくシンプルに保ち、シーダークラスに生成ルールを書いた方が読みやすいかなと思ってます。

連番を生成する

連番のテストデータを作りたい時は、staticなプロパティを作ると良いです。

class UserFactory extends Factory
{
    private static $sequence = 1;

    public function definition()
    {
        return [
            'name' => sprintf('No. %d %s', self::$sequence++, $this->faker->name),
        ];
    }

実行するとこんな感じです。

>>> App\Models\User::factory()->make()->name
=> "No. 1 青山 明美"

>>> App\Models\User::factory()->make()->name
=> "No. 2 工藤 七夏"

>>> App\Models\User::factory()->make()->name
=> "No. 3 渡辺 稔"

$faker->email は使うな! $faker->safeEmail を使え!

$ php artisan tinker
>>> $faker = app()->make(Faker\Generator::class)

>>> $faker->email
=> "kyosuke.sasada@gmail.com"

>>> $faker->safeEmail
=> "kondo.yui@example.com"

実行結果を見るとわかりますが、 @gmail.com など実在するドメインのテストデータが生成されます。
開発環境でメールの誤送信を防止するため $faker->safeEmail を使うようにしましょう。

Faker nullデータをランダムで入れたい

->optional() を使うとランダムでnull値が入ります!

    public function definition()
    {
        return [
            // ランダムでnullを入れたい時
            'completed_at' => $this->faker->boolean ? $this->faker->dateTime : null,
            // Fakerのoptional()を使うとランダムでnullを入れてくれます(オススメ)
            'completed_at' => $this->faker->optional()->dateTime,
            // 80%の確率で日付データが入り、20%の確率でnullが入ります
            'completed_at' => $this->faker->optional(0.8)->dateTime,
        ];
    }

状態に応じたテストデータを作成する

公式サイトに詳しく記述されているので、ここで書く必要ないですがとても便利な機能なのでご紹介します。

※お試しする場合はマイグレーションファイルに下記を追記して、php artisan migrate:fresh してください。

database/migrations/2014_10_12_000000_create_users_table.php
            $table->string('account_status');
database/factories/UserFactory.php
<?php

namespace Database\Factories;

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

class UserFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = User::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
            'name' => $this->faker->name,
            'account_status' => 'init',
            'email' => $this->faker->unique()->safeEmail,
            'email_verified_at' => now(),
            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
            'remember_token' => Str::random(10),
        ];
    }

    /**
     * そのユーザーが有効化(activated)されていることを表す
     *
     * @return Factory
     */
    public function activated(): Factory
    {
        return $this->state(function () {
            return [
                'account_status' => 'activated',
            ];
        });
    }

    /**
     * そのユーザーが資格保留(suspended)されていることを表す
     *
     * @return Factory
     */
    public function suspended(): Factory
    {
        return $this->state(function () {
            return [
                'account_status' => 'suspended',
            ];
        });
    }
}
$ php artisan tinker

# 仮登録(init)状態のユーザーを作成
>>> App\Models\User::factory()->create()
=> App\Models\User {#3346
     name: "中津川 あすか",
     account_status: "init",
     email: "tanaka.momoko@example.org",
     email_verified_at: "2020-12-06 18:30:21",
     updated_at: "2020-12-06 18:30:21",
     created_at: "2020-12-06 18:30:21",
     id: 7,
   }

# 有効化(activated)状態のユーザーを作成
>>> App\Models\User::factory()->activated()->create()
=> App\Models\User {#3343
     name: "石田 京助",
     account_status: "activated",
     email: "harada.yuta@example.com",
     email_verified_at: "2020-12-06 18:30:23",
     updated_at: "2020-12-06 18:30:23",
     created_at: "2020-12-06 18:30:23",
     id: 8,
   }

# 資格保留(activated)状態のユーザーを作成
>>> App\Models\User::factory()->suspended()->create()
=> App\Models\User {#4090
     name: "伊藤 稔",
     account_status: "suspended",
     email: "taichi.sugiyama@example.com",
     email_verified_at: "2020-12-06 18:30:24",
     updated_at: "2020-12-06 18:30:24",
     created_at: "2020-12-06 18:30:24",
     id: 9,
   }

モデルの状態によって必要な埋めたいデータは異なるので、状態に応じてモデルファクトリを定義できるのはとても便利です!

db:seed でテストデータをクリアする

先週書いた記事ですが、こちらもやっておくと便利です。

Laravel 8 のリリース記事

Laravel 8の新しい機能を知りたい方はこちらの記事をご覧ください。

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

Composerパッケージを作ろう その② - Laravelパッケージ編

はじめに

今回はLaravelのComposr自作パッケージの公開手順を書いていきます。
基本的な手順は前回の記事と同様なので、まだご覧になっていない人はこちらの記事から先に読むことをお勧めします。

Composerパッケージを作ろう その①

下準備

ディレクトリやファイルの構成や準備などは前回の記事を参考にしてください。

Laravelでパッケージを自作する際はLaravelというFWのコアとなるサービスプロバイダをパッケージ側にも実装します。

Laravel5.5以降からはPackage Auto Discoveryという仕組みが導入されており、作成するパッケージのcomposer.jsonに以下の様に追記しておくことでサービスプロバイダを自動的に読み込んでくれます。

"extra": {
        "laravel": {
            "providers": [
                "Vendor\\PackageName\\PackageServiceProvider"
            ]
        }
    }

サービスプロバイダ内でルートを生成したり、作成したルートにミドルウェアを挟んだりすれば、デバッグツールの様なものを作成することも可能です。

ちなみに自分は以下の様なパッケージを公開しています。
temori/artisan-browser

ブラウザ上でターミナル風の画面を表示してルートの確認やartisanコマンドを実行するための開発ツールになります。

57972369-476f1280-79d4-11e9-9918-dfddb07a2d80.gif

このパッケージでもサービスプロバイダでルートを生やして、ミドルウェア内でhtmlの要素やjs,cssを表示しているviewに追加する形でツールの画面を表示させています。

基本的な構成は前回の記事と同様ですが、Laravel用にディレクトリを若干追加しています。

以下が作成したパッケージのディレクトリになります。

├── artisan-browser
│   ├── LICENSE
│   ├── README.md
│   ├── composer.json
│   ├── config
│   │   └── artisanbrowser.php
│   └── src
│       ├── ArtisanBrowser.php  #このパッケージの基幹クラス
│       ├── ArtisanBrowserServiceProvider.php  #サービスプロバイダー
│       ├── Controllers  #ルートを生成したので、そこで呼び出すコントローラー
│       │   ├── AjaxController.php
│       │   └── AssetController.php
│       ├── Facades
│       │   └── ArtisanBrowser.php
│       ├── Logs
│       │   └── HistoryLogger.php
│       ├── Middleware  #パッケージのhtml要素を全ページにレンダリングするためのミドルウェア
│       │   └── Inject.php
│       ├── Renderer
│       │   ├── AssetRenderer.php
│       │   └── HtmlRenderer.php
│       ├── Resources
│       │   ├── artisanBrowser.css
│       │   ├── artisanBrowser.js
│       │   ├── image
│       │   │   └── logo.png
│       │   ├── vendor
│       │   │   ├── draggabilly.pkgd.min.js
│       │   │   ├── jquery-1.12.4.min.js
│       │   │   └── suggest.js
│       │   └── views
│       │       └── index.blade.php
│       └── helpers.php

作成するパッケージによってディレクトリを適宜作成してください。

パッケージの処理を実装する

ここも前回の記事と一緒ですが、もし設定ファイル等で挙動を制御する様な場合、
パッケージ使用者に設定ファイルを編集してもらいたい場面が出てくるかと思います。

その様な場合はLaravelにはアプリケーション側にassetを配置する仕組みが存在しているので、それを活用しましょう。

サービスプロバイダのbootメソッド内に

$this->publishes([
            __DIR__ . '/../config/artisanbrowser.php' => config_path('artisanbrowser.php', 'config'),
        ]);

の様な記述をしておくと

# forceオプションはすでにファイルが存在していても上書きでファイルを配置する
php artisan vendor:publish --tag=config [--force]

で設定ファイルがアプリケーション側へ配置されます。

もしくはアプリケーション側とvendor下のパッケージの設定ファイルをマージすることもできます。

registerメソッド内に以下の様に定義すればパッケージ側の設定をデフォルトとして使用することができ、ユーザーは変更したい値のみオーバーライドすればパッケージ側とアプリケーション側の設定値をマージしてくれます。

$this->mergeConfigFrom(
        __DIR__ . '/../config/artisanbrowser.php', 'artisanbrowser'
    );

詳細は公式のパッケージ開発に詳しく書かれています。

パッケージの実装が完了したらその後の手順は前回の記事と同様です。

最後に

2回に分けてComposer自作パッケージの流れを書いていきましたがいかがでしたでしょうか?
ざっくりとしか流れしか書いていませんが、そこまでハードルの高いものではないと少しでも感じてもらえたら嬉しいです:grinning:

手始めに自分が使う用にパッケージを作ってみるのもありかも知れません。

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

AWS EC2上にDockerコンテナ(Laravel, MySQL, nginx)を起動しアクセスする

環境

  • Mac
  • Docker
  • EC2 (Amazon Linux2, t2.micro)
  • nginx
  • Laravel
  • MySQL
  • Github

ゴール

EC2上でLaravelのページを表示する
スクリーンショット 2020-09-12 14.48.10.png

前提

  • 今回はお試しなのでEC2は無料利用枠を使えるAmazon Linux2, t2.micro選定
  • EC2のインスタンスならびにSSHの接続で使用するキーペアは作成済みとする。

EC2にログインする

xxx.pemにEC2を作成する際に作成したキーペアのファイルパス、(接続先にユーザー名)@(EC2のパブリックDNS)を指定する。
ユーザー名はデフォルトで「ec2user」、EC2のパブリックDNSはEC2の詳細画面で確認する。

mac
$ sudo ssh -i xxx.pem ec2user@ec2-xx-xx-xx-xx.ap-northeast-1.compute.amazonaws.com

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/

dockerをインストールする

ec2
$ sudo yum install docker
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
依存性の解決をしています
--> トランザクションの確認を実行しています。
---> パッケージ docker.x86_64 0:19.03.6ce-4.amzn2  インストール
--> 依存性の処理をしています: runc >= 1.0.0 のパッケージ: docker-19.03.6ce-4.amzn2.x86_64
--> 依存性の処理をしています: containerd >= 1.3.2 のパッケージ: docker-19.03.6ce-4.amzn2.x86_64
--> 依存性の処理をしています: pigz のパッケージ: docker-19.03.6ce-4.amzn2.x86_64
--> 依存性の処理をしています: libcgroup のパッケージ: docker-19.03.6ce-4.amzn2.x86_64
--> トランザクションの確認を実行しています。
---> パッケージ containerd.x86_64 0:1.3.2-1.amzn2  インストール
:

$ docker --version
Docker version 19.03.12, build 48a66213fe    <- インストールされたことを確認

docker-composeをインストールする

最新バージョンを公式サイトで確認しインストールする

ec2
$ sudo curl -L https://github.com/docker/compose/releases/download/1.27.0/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   651  100   651    0     0   2141      0 --:--:-- --:--:-- --:--:--  2134
100 11.6M  100 11.6M    0     0  3734k      0  0:00:03  0:00:03 --:--:-- 5900k

$ sudo chmod +x /usr/local/bin/docker-compose    <- 実行権限を与える
$ docker-compose --version
docker-compose version 1.27.0, build 980ec85b    <- インストールされたことを確認

gitインストール

ec2
$ sudo yum install git-all
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
amzn2-core                                                                                                                                                                           | 3.7 kB  00:00:00     
依存性の解決をしています
--> トランザクションの確認を実行しています

ローカル(Mac)で作成した環境を取り込む

今回はgithub上にあるリポジトリからクローンする

ec2
$ git clone https://github.com/xxx.git
Cloning into 'typing-app'...
Username for 'https://github.com': xxx    <- githubのユーザー名を入力
Password for 'https://narita-akk@github.com':     <- githubのパスワードを入力
remote: Enumerating objects: 133, done.
remote: Counting objects: 100% (133/133), done.

[ec2-user@ip-172-31-47-56 ~]$ ls    <- クローンされたことを確認
test-project

Dockerのデーモンを起動する

ec2
$ systemctl status docker.service    <- デーモンの状態を確認
 docker.service - Docker Application Container Engine
   Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
   Active: inactive (dead)
     Docs: https://docs.docker.com

$ sudo systemctl enable docker.service    <- デーモンを有効化する
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.

$ systemctl status docker.service    <- デーモンが有効化されたことを確認
 docker.service - Docker Application Container Engine
   Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled)
   Active: inactive (dead)
     Docs: https://docs.docker.com

$ sudo systemctl start docker    <- デーモンを起動する

$ systemctl status docker    <- デーモンが起動されたことを確認
 docker.service - Docker Application Container Engine
   Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled)
   Active: active (running) since  2020-09-11 23:03:25 UTC; 5s ago
     Docs: https://docs.docker.com
  Process: 3651 ExecStartPre=/usr/libexec/docker/docker-setup-runtimes.sh (code=exited, status=0/SUCCESS)
  Process: 3638 ExecStartPre=/bin/mkdir -p /run/docker (code=exited, status=0/SUCCESS)
 Main PID: 3659 (dockerd)
    Tasks: 8
   Memory: 118.4M
   CGroup: /system.slice/docker.service
           └─3659 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --default-ulimit nofile=1024:4096
:

Dockerコンテナを構築し起動する

ec2
$ docker-compose up -d --build
Creating network "typing-app_default" with the default driver
Creating volume "typing-app_db-store" with default driver
Building app
Step 1/7 : FROM php:7.4-fpm-buster
7.4-fpm-buster: Pulling from library/php
d121f8d1c412: Pull complete
:

$ docker ps    <- 起動されているコンテナを確認
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                   NAMES
734c90dda24a        nginx:1.19.2-alpine   "/docker-entrypoint.…"   2 minutes ago       Up 2 minutes        0.0.0.0:10080->80/tcp   typing-app_web_1
0e545f085ec4        typing-app_app        "docker-php-entrypoi…"   2 minutes ago       Up 2 minutes        9000/tcp                typing-app_app_1
f860eb86350e        mysql:8.0             "docker-entrypoint.s…"   2 minutes ago       Up 2 minutes        3306/tcp, 33060/tcp     typing-app_db_1

Laravelの初期設定をする

ec2
$ docker-compose exec app bash    <- Laravelがインストールされているコンテナに入る
ec2(app)
$ composer install    <- 必要なパッケージをインストール
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Package operations: 106 installs, 0 updates, 0 removals
  - Installing doctrine/inflector (2.0.3): Downloading (100%)         
  - Installing doctrine/lexer (1.2.1): Downloading (100%)         
  - Installing dragonmantank/cron-expression (3.0.1): Downloading (100%)         
  - Installing voku/portable-ascii (1.5.3): Downloading (100%)         
:

$ ls -al    <- .env.exampleがあることを確認
total 304
drwxrwxr-x 12 1000 1000   4096 Sep 12 05:33 .
drwxr-xr-x  1 root root     33 Sep 12 05:33 ..
-rw-rw-r--  1 1000 1000    220 Sep 12 05:26 .editorconfig
-rw-rw-r--  1 1000 1000    784 Sep 12 05:26 .env.example
-rw-rw-r--  1 1000 1000    111 Sep 12 05:26 .gitattributes
:

$ cp .env.example .env    <- .env.example.envにコピー

$ php artisan key:generate    <- アプリケーションキーを作成
Application key set successfully.

root@3e98c590943b:/work# ls -al    <- storageの権限を確認
:
total 304
drwxrwxr-x  5 1000 1000     46 Sep 12 05:26 storage
:

$ cd bootstrap/
$ ls
app.php  cache
$ ls -al    <- bootstrap/cacheの権限を確認
total 8
drwxrwxr-x  2 1000 1000   64 Sep 12 05:34 cache
:

$ cd ..
$ chmod -R 777 storage    <- storageフォルダに書き込み権限を付与
$ chmod -R 777 bootstrap/cache/    <-bootstrap/cache/フォルダに書き込み権限を付与
:

ec2の80番ポートを開けてhttpアクセスを許可する

ec2のセキュリティグループを編集する

スクリーンショット 2020-09-12 15.08.51.png

ec2にブラウザからアクセス

スクリーンショット 2020-09-12 14.48.10.png

Laravelが表示できた!

参考サイト

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

Github Actionsを使って自動テスト(PHPunit)をパスしないとマージできないようにする

自分でもCI/CD環境を構築したい!と思いつつ、CircleCIはチョット敷居が高い・・・。
と思ったら、GithubでもGithub Actionsを使って自動テストができるらしいことを知ったので設定してみました!

Githubアクションとは何なのか?

設定ファイル(yml)を元にイベント駆動で処理を実行してくれるCI/CDサービス。
Githubで用意した仮装サーバー(ランナーと呼ぶ)使ってジョブを実行する
個人利用の範囲なら無料で利用できる。

手順

1.アカウント連携
2.ワークフローファイル(./github/workflows/test.yml)を作成(編集)
3.プルリクを立ててActions実行の確認
4.ブランチ保護設定でワークフローを指定
5.テストに失敗するプルリクを立ててブランチが保護されているのを確認

アカウント連携をする

Github Actionsはリポジトリ管理の拡張機能なので、普通にgithubを使っている方でも利用のための設定をする必要があります。

といってもそれほど難しいことはなく、githubにログインした状態でリポジトリを閲覧できるページとかで表示されるGithub Actionsボタンを押すだけです。

*ここに画像挿入

これでアカウントとしてGithub Actionsを使えるようになりました。

ワークフローファイルの作成

アカウント連携をした後でリポジトリページを開くと、タブメニューにActionsが追加されているので、これをクリックします。
すると、↓のようにリポジトリの内容からワークフローの内容をサジェストしてくれているはずです!
image.png

Set up this workflowボタンを押すと、ymlのテンプレートが表示されます。

ymlファイルの項目解説

name: Laravel

on:
  # masterブランチへのpush時
  push:
    branches: [ master ]
  # masterブランチへのプルリク作成時
  pull_request:
    branches: [ master ]

#
jobs:
  laravel-tests:

    runs-on: ubuntu-latest

    steps:
    # 自動テスト用.envのを作成(.env.exampleをコピー)し、ライブラリインストール
    - uses: actions/checkout@v2
    - name: Copy .env
      run: php -r "file_exists('.env') || copy('.env.example', '.env');"
    - name: Install Dependencies
      run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
    # laravelの初期設定コマンド
    - name: Generate key
      run: php artisan key:generate
    - name: Directory Permissions
      run: chmod -R 777 storage bootstrap/cache
    # テスト用DB作成
    - name: Create Database
      run: |
        mkdir -p database
        touch database/database.sqlite
    # .envにテスト用環境変数を書き込みphpunit実行
    - name: Execute tests (Unit and Feature tests) via PHPUnit
      env:
        DB_CONNECTION: sqlite
        DB_DATABASE: database/database.sqlite
      run: vendor/bin/phpunit

ファイル内にコメントを書いてますが、
各項目の役割はこんな感じです↓

項目 役割
name ワークフローの名前
on どんな操作があった時にジョブを起動するか
jobs ワークフローで行われるジョブのグループ
runs-on ジョブを実行するランナー(CI/CD処理を行うサーバー)の設定
steps ジョブで段階的に実行される処理
uses コマンドを再利用可能な形に定義したもの。ワークフロー、パブリックリポジトリ、公開Dockerコンテナイメージで定義されているアクションを指定・使用できる
name 各stepsの名前
runs stepsで実行するコマンド
env ワークフローを実行する時の環境変数

参考:公式:ワークフローファイルを理解する

CI/CDを自分の手で設定したので備忘録として書いておくと、
ワークフロー・ジョブ・ステップの違いは
・ジョブはファイルによって実行されるCI/CD処理(自動テスト)内容のこと
・ステップはジョブを実行するための段階的手順のこと
・起動イベントを含めた処理全体をワークフロー
と捉えておけばいいのかな、と思いました。

ワークフロー実行の確認

ymlファイルを作るページからそのままプルリクエストを作りましょう。
image.png
※ テンプレートをそのまま使った場合、マージ先がmasterに向いてないとワークフローが実行されないので注意

また、Actionsタブからもワークフローの経過・結果を確認することができます。

ブランチ保護設定

最後に、ブランチ保護設定(ステータス)でワークフローと結びつける設定を行います。

Settings画面の左メニューBranchesを選択後、
Branch protection rules>Add Rulesをクリック。

image.png

Branch name patternに保護したいブランチの名前
Require status checks to pass before mergingにチェックを入れ、先ほど追加したワークフローを選択
・管理者にも保護ルールを適用させたい場合はInclude administratorsにチェック
をして、画面下部のCreateボタンで設定を確定します。

テスト失敗時にマージできないうことの確認

設定は全て完了したので、最後にテストコードが失敗した時にマージできなくなっていることを確認します。

class ExampleTest extends TestCase
{
    /**
     * A basic test example.
     *
     * @return void
     */
    public function testBasicTest()
    {
        $response = $this->get('/');
        // 200でパスするケースを失敗するよう編集
        $response->assertStatus(300);
    }
}

↑をpushした後にプルリクのマージボタンを確認すると、マージできなくなっているのが分かります。
image.png

また、自分の場合はInclude administratorsにチェックを入れてなかったため、Use your administrator...にチェックを入れて制約を無視したマージができるようになっています。

補足

・設定に使うymlファイルの名前はなんでもOK。Githubは./github/workflowsにあるymlファイルを読み込んでGithub Actionsに使用する
・パブリックリポジトリである必要がある
・sqlite以外のDBを使用する場合はymlファイルの編集が必要

参考資料

Github Actions公式リファレンス
Github Actions 8ヶ月使ってみてわかったことまとめ
【Github Actions】プッシュ時にgithubに自動でテストを実行してもらおう!
GitHub Actionsを使ってLaravelアプリケーションをCI/CDする

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