カテゴリーアーカイブ Laravel

著者:杉浦

ダミー画像表示サイトlorempixelの紹介

lorempixel – placeholder images for every case
 lorempixelはurl指定するとダミーの画像を表示してくれるサイトです。urlの指定の仕方は次の画像の通り。urlを指定すればするほどオンリーワンな画像が表示されます。例はhttpプロトコルですがhttpsでもアクセスできます。

 例えば、https://lorempixel.com/400/200/animals/5/msgならば次の様に、横幅400px、縦幅200px、カテゴリanimalsの5番目の画像の左下にmsgの文字を入れた画像が返ってきます。

 webページを制作した時の画像確認においてカテゴリ分けが特に便利で仮のそれらしい画像をいくつも作る手間が省かれます。
 PHPのダミーデータ作成ライブラリfzaninotto/Faker: Faker is a PHP library that generates fake data for youにはlorempixelが組み込まれています

// Image generation provided by LoremPixel (http://lorempixel.com/)
imageUrl($width = 640, $height = 480) // 'http://lorempixel.com/640/480/'
imageUrl($width, $height, 'cats')     // 'http://lorempixel.com/800/600/cats/'
imageUrl($width, $height, 'cats', true, 'Faker') // 'http://lorempixel.com/800/400/cats/Faker'
imageUrl($width, $height, 'cats', true, 'Faker', true) // 'http://lorempixel.com/grey/800/400/cats/Faker/' Monochrome image

 Fakerが組み込まれているLaravelならばダミーデータ作成機能ファクトリに次の様にランダムURL生成を定義できます。

use Faker\Generator as Faker;

// あらかじめ使用する予定のカテゴリを定義
$category = ['abstract', 'animals', 'business', 'cats', 'city', 'food', 'nightlife', 'fashion', 'people', 'nature', 'sports', 'technics', 'transport'];
/* @var \Illuminate\Database\Eloquent\Factory $factory */
$factory->define(App\Models\Post::class, function (Faker $faker) use ($category) {
    $word = $faker->word; // 画像に記述する文字列をランダム生成
    return [
        'img_url' =>
            $faker->imageUrl(640, 480, $faker->randomElement($category), false) // https://lorempixel.com/640/480/{$categoryのどれか}/
            . $faker->numberBetween(1, 10) . '/' . $word, // 末尾に、1~10のいずれかと書き込む$wordを追加
    ];
});
  • この記事いいね! (0)
著者:杉浦

duskによるテストの開始時、終了時にLaravel内で定義してある関数を用いる

 Laravel内で共通のことですがクラス中で用いる初期化関数__construct()が上手く働かないことがあります。これに陥る原因としてLaravel内でクラスを呼び出した時、まだインスタンス化されていないクラスに関わる関数を呼び出すというものがあります。Laravelの組み込み関数や定義したEloquentによる関数、プロパティは便利ですが__construct()内では使えません。duskも同様です。
 Laravel内で用いられているクラスはこれを解決するために、前処理が終わった後、本処理が始まる前に呼び出される関数をboot,setupなどといった名前で定義しています。__construct()の次にboot()が走り、boot()の次にコントローラなどの本処理が走るといった具合です。duskではsetUp()という名前で定義されています。このsetUp()関数内でテストに使うデータを定義すると幾分かすんなりとテストを記述できます。例えば次です。

    /**
     * @inheritDoc
     */
    protected function setUp()
    {
        parent::setUp();
        // テストに用いる編集対象の管理者を指定、インスタンス化
        $admin_key = static::$admin_key ?? Admins::inRandomOrder()->first()->getKey();
        $this->admin = Admins::findOrFail($admin_key);
    }

 あらかじめ特定の管理者を対象にするならばその管理者を、そうでないならばランダムに選んだ管理者を対象にして様々なテストに用います。
 開始に対応するように終了時にフックされる関数もあります。dusk内ではtearDownとされています。あるテストが終わるたびにtearDown関数が呼ばれているため、ブラウザテストの完全終了を待たずともあるテストケースの失敗とそれを解決するための情報を得る記述をすることが出来ます。tests/DuskTestCase.phpを継承することで各テストケースがブラウザ上で走るため、tests/DuskTestCase内に次の様に記述するのが良いでしょう。DuskTestCaseより深く潜るとvendorの領域に入ってしまいます。

    /**
     * @inheritDoc
     */
    protected function tearDown()
    {
        parent::tearDown();
        if ($this->hasFailed()) {
            dump('failed in ' . static::class . '::' . $this->getName() . '. test case instance is');
            $test_class_data = (new \Illuminate\Support\Collection($this))->filter(function ($value, $key) {
                return !strpos($key, 'PHPUnit');// 2、3000行程になるPHPUnit自体の情報を削除
            });
            dump($test_class_data);
        }
    }
  • この記事いいね! (0)
著者:杉浦

dusk中でwebdriverを呼び出し多彩な操作をする

 Laravelのブラウザテストツールduskは以下の様なコードでdriverメソッドからwebdriverを呼び出すことによってブラウザを動かします。このコードはLaravelのデフォルトに近い形でGoogle Chromeを呼び出しています。

    /**
     * Create the RemoteWebDriver instance.
     * @return \Facebook\WebDriver\Remote\RemoteWebDriver
     */
    protected function driver()
    {
        $options = (new ChromeOptions())->addArguments([
            '--disable-gpu',
            '--headless',
        ]);

        return RemoteWebDriver::create(
            'http://localhost:9515',
            DesiredCapabilities::chrome()->setCapability(ChromeOptions::CAPABILITY, $options),
            300 * 1000,
            300 * 1000
        );
    }

 通常duskはLaravel\Dusk\Browserクラスからbrowserを操作します。Browserクラスは堅牢であり、クラス中に生やされたプロパティによるブラウザの状態の把握は便利です。Browserクラスは便利ですが、操作に難があります。本当に手打ちの操作を再現するのみならば問題がない程度には操作メソッドが備わってますが、これは手打ちの再現であり手打ちでは膨大な時間がかかる入力をテストする際に面倒が起きます。typeメソッドによる長文入力はこれに見事に引っかかります。
 BrowserクラスはRemoteWebDriverクラスをラッピングしてブラウザ操作を行っています。Browserクラスの下に入っているRemoteWebDriverクラスを直接操作することでより多彩な操作ができます。呼び出し方はいたって簡単次のコードの通り

/** @var Browser $browser */
$web_driver = $browser->driver;

 この$web_driver変数からRemoteWebDriverクラスのメソッドを呼びだすことによってさまざまなことが出来ます。できることの一覧はFacebook\WebDriver\Remote\RemoteWebDriver | php-webdriver APIやvendor/facebook/webdriver/lib/Remote/RemoteWebDriver.phpの中身にあります
 特に便利なメソッドはexecuteScriptとtakeScreenshotです。executeScriptは次の様にjavascriptを実行できます。長文入力の様な手打ちでやっていられない、手打ちの必要が無い操作はこれで肩代わりするによって実行時間の短縮が図れます。

$browser->driver->executeScript('document.querySelector("[name=\"' . $field . '\"]").value = "' . $value . '";');

 takeScreenshotはその名の通りスクリーンショットを取得するメソッドです。Laravelのヘルパ関数によるpath指定を行うことでLaravelプロジェクト内部に任意のタイミングで取得したスクリーンショットを保存できます。

  • この記事いいね! (0)
著者:杉浦

LaravelのCRUDテストとfactory

 CRUDの内updateとdeleteは既存データを加工します。この機能をテストするならば、必然データを壊します。この壊すデータの範囲に重要なものが紛れている場合、問題が起こります。私は今日テスト環境の画面確認用管理者アカウントを壊しました。
 update,deleteをテストするために重要なデータをテスト対象とした場合、テスト中止ないし再起動とする方法があります。この方法にはテスト対象とした範囲に重要なデータしか存在しない場合テストを行えないという欠点があります。もう一つ新たにデータを作成し、それに対してupdate,deleteをテストする方法があります。この手法はコード量が増えがちです。Laravelはfactoryという仕組みを用いてコード量の節約、責任の分担をしています。
データベースのテスト 5.6 Laravel#ファクトリの記述
 ファクトリはダミーデータを作成する仕組みでデータベースにテスト用初期データを入れる、データベースのテストに用いるといった使い方が主ですが、更新、削除のテストでもよく働いてくれます。factoryは次の様な記述によって与えるデータを定義します。

/** /database/factories/UserFactory.php */
use Faker\Generator as Faker;

$factory->define(App\User::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret
        'remember_token' => str_random(10),
    ];
});

 これを定義した場合、次の様にダミーデータをワンライナーで呼び出せます。createはデータベースへの書き込みを実行してインスタンスのCollectionを返し、makeはインスタンスのCollecitonを返します。

// factory(Model::class, 個数)
$user = factory(User::class, 1)->create()->first();
$new_user_data = factory(User::class, 1)->make()->first();

 createは削除と更新用の壊していいデータ、makeは更新用の入力値として便利に使えます。

  • この記事いいね! (0)
著者:杉浦

dusk中でブラウザに依らないassertを実行

 ブラウザテスト(Laravel Dusk) 5.6 Laravel#使用可能なアサート
 Laravelの自動ブラウザテスト機能duskでは多くのassertが使用可能です。多くのassertが使用可能ですがいずれもブラウザ上で何かを確認するためのassertです。duskは単一責任の原則を守りきれいに作られています。時にはブラウザ上の操作の結果データベース上の値がどうなったかを確認するケースの様にブラウザを介さないassertを実行したい場合があります。このためにduskで使用するコード中にduskを飛び越してduskの使っているPHPUnitにアクセスするコードを埋め込みます。
 duskの使用可能なアサートはだいたい次の様なコードで作られています。

use PHPUnit\Framework\Assert as PHPUnit;
/* 省略 */
    /**
     * Assert that the given text appears within the given selector.
     *
     * @param  string  $selector
     * @param  string  $text
     * @return $this
     */
    public function assertSeeIn($selector, $text)
    {
        $fullSelector = $this->resolver->format($selector);

        $element = $this->resolver->findOrFail($selector);

        PHPUnit::assertTrue(
            Str::contains($element->getText(), $text),
            "Did not see expected text [{$text}] within element [{$fullSelector}]."
        );

        return $this;
    }

 与えられたセレクタに従った要素中に与えられたtextが存在しないならばエラーというassertTrueを呼び出しています。このコード同様にPHPUnit\Framework\Assertからより素朴なassert関数を呼び出すことによってより自由度の高いテストをすることが出来ます。もちろんエラーが起きた際、ログやコンソールにエラーが起きたコード中の場所とエラーメッセージが表示されます。

    /**
     * 契約取消しテスト
     * @param Browser $browser
     * @throws \Facebook\WebDriver\Exception\TimeOutException
     */
    private function cancelContractTest(Browser $browser)
    {
        $browser->//省略
        $member = Members::find($this->member_key);
        PHPUnit::assertTrue(is_null($member->contract), "exist expected null contract.");
        $this->assertSeeMemberType($browser, $member);
    }

 最近書いたコードではこのようなものを書きました。これは、契約取消し操作をしたならばmemberはcontractを持っていないはず、というテストでした。
 duskは見事にSeleniumとPHPUnitを繋げて分かりやすく覆った便利なテスト機能です。duskはブラウザ操作とテストケースの抽象化のために幾重ものコードになっていますが、個々のソースはわかりやすく結局はPHPUnitでSeleniumの操作に対してassertするという言葉でまとめられる機能です。以前紹介した個々のテストケース実行コマンドもduskの説明書にないがPHPUnitの説明書にはある機能です。ヘルプには大して記述はありませんがPHPUnitの動作のかなりの部分が簡単にduskで使えます。

追記:
 テストケースを記述するクラスが継承するべきTestCaseクラス自身がPHPUnit\Framework\Assertを継承していました。useせずとも

$this->assertTrue(is_null($member->contract), "exist expected null contract.");

で十分でした。

  • この記事いいね! (0)
著者:杉浦

Laravelの自動ブラウザテスト機能duskで任意のテストケースのみを実行する

 ブラウザテスト(Laravel Dusk) 5.6 Laravel
 Laravelには自動でブラウザテストを行ってくれるduskという機能があります。それなりの手間がかかり、何度も行わなければならないブラウザ上における手作業を代行してくれる機能です。ブラウザテストなしに複雑な機能を変更の都度テストするのはとてもとても辛いです。
 そのように便利なブラウザテスト機能を行うduskですが、様々な機能を実装するにつれテストが肥大化し総実行時間も長くなります。私的な環境ですが軽い検索機能付きページのテストは1ページ2分ほどで終わります。これに機能の大小、ページ数が加わり総実行時間は10分、20分となります。とてもやっていられないため結果が欲しい一部のテストのみを実行する必要に駆られます。
 duskはPHPUnitの機能の大部分を引き継ぎブラウザテスト用にラッピングされたテストツールです。そしてPHPUnit同様のコマンドラインオプションが備わっており次の様なフィルターパターンによって、任意のクラス、任意のメソッドのテストを実行できます
 

例 3.2: フィルターパターンの例

  • --filter 'TestNamespace\\TestCaseClass::testMethod'

  • --filter 'TestNamespace\\TestCaseClass'

  • --filter TestNamespace

  • --filter TestCaseClass

  • --filter testMethod

  • --filter '/::testMethod .*"my named data"/'

  • --filter '/::testMethod .*#5$/'

  • --filter '/::testMethod .*#(5|6|7)$/'

 PHPUnit マニュアル – 第3章 コマンドラインのテストランナーから引用
 これにより何分も待たずに変更の度にテストをかけること、あるテストが通る様なコードを書くことが楽になります。

  • この記事いいね! (0)
著者:杉浦

Laravelでクエリのロギングを簡単な記述で行う

 ORM(データベースの中身をオブジェクトにマッピングする仕組み)は便利であり、Laravelに入っているEloquent ORMはとてもとても便利で開発を簡単にしてくれます。しかしながらORMを介して記述をしていると実際に発行されているSQL命令がコードから読み取りにくいものになります。グローバルスコープにより暗黙的に条件を追加する、User::create($request->all())だけでINSERTを済ませる、といったことをするならばなおさらです。デバッグのためには実際に発行されているクエリをロギングしたいものです。
 LaravelのDB接続クラスには次のlistenメソッドが備わっており、これを用いることでクエリの発行の度にlistenイベントを発生させます。
 データベース:利用開始 5.6 Laravel#クエリイベントのリッスン

    /**
     * Register a database query listener with the connection.
     * データベースクエリリスナを接続に登録する。
     *
     * @param  \Closure  $callback
     * @return void
     */
    public function listen(Closure $callback)
    {
        if (isset($this->events)) {
            $this->events->listen(Events\QueryExecuted::class, $callback);
        }
    }

 このlistenメソッドに渡す関数にクエリをロギングする関数にし、その記述を上記リンクにある様にアプリケーションサービスの初期処理で呼び出されるapp\Providers\AppServiceProviderのbootメソッドに行います。

class AppServiceProvider extends ServiceProvider
{
    /**
     * アプリケーションサービスの初期処理
     * @return void
     */
    public function boot()
    {
        DB::listen(function (QueryExecuted $query) {
            Log::debug(str_replace_array('?', $query->bindings, $query->sql));// クエリロギングコード
        });
    }
}

 ここからは、コードの細部の説明です。ロギングを行う関数部はLog::debug(ログに残すメッセージ)です。debugレベルのログを/config/logging.phpの設定に従って出力します。デフォルトでは/storage/logs/laravel.logファイルに追記されます。
 ログに残すメッセージはstr_replace_array(‘?’, $query->bindings, $query->sql)です。$query->bindings、$query->sql、str_replace_array(‘?’, $query->bindings, $query->sql)の中身はそれぞれ次の画像の通りです。

 それぞれバインドした値、値を入れる前のクエリ、バインドした値を当てはめたクエリです。str_replace_arrayはLaravelのヘルパ関数の一つです。
 ヘルパ 5.6 Laravel#str_replace_array()
 str_replace_array関数は配列を使い、文字列を指定値へ順番に置き換えます。

str_replace_array('?', $query->bindings, $query->sql)

 このコードは値を入れる前のクエリの中の’?’をバインドした値に順番に置き換えるものということです。
 このコードの注意点としてログをDBに吐き出す設定にしておくと無限ループが起きることです。ログをDBに保存すると、そのDBへの保存をログする動作が走り、またログのログを……と続きます。

  • この記事いいね! (0)
著者:杉浦

Laravelのルーティングを司るRouteServiceProvider

 Laravelのチュートリアルにおいてルーティングは/route/web.phpに記述する様に指示されます。実際このweb.phpとRouteファサード、ミドルウェアを用いるだけで大抵のやりたいルーティングは実現できます。しかしながらあくまで大抵であり、より深いことを行おうとした場合、もっと元の部分からルーティングを扱う必要があります。またルーティングパターンがあまりに多い時も元をさわってルーティングを記述するファイルを増やすことが可読性に貢献します。
 /app/Providers/RouteServiceProviderは/route/web.phpを呼び出し、ルーティングのマップを定義するコードです。ソースコードには次の様にあります。

    /**
     * Define the routes for the application.
     * @return void
     */
    public function map()
    {
        $this->mapApiRoutes();

        $this->mapWebRoutes();
    }

    /**
     * Define the "web" routes for the application.
     * These routes all receive session state, CSRF protection, etc.
     * @return void
     */
    protected function mapWebRoutes()
    {
        Route::middleware('web')
             ->namespace($this->namespace)
             ->group(base_path('routes/web.php'));
    }

 コメントとコードの通り各ルーティングのマップ呼び出し、webのルーティングを定義、というメソッドらです。例えばルーティングファイルを増やしたいならば次の様に行えます。

    /**
     * Define the routes for the application.
     * @return void
     */
    public function map()
    {
        $this->mapApiRoutes();

        $this->mapWebRoutes();

        $this->mapAppRoutes();
    }

    /**
     * Define the "web" routes for the application.
     * These routes all receive session state, CSRF protection, etc.
     * @return void
     */
    protected function mapWebRoutes()
    {
        Route::middleware('web')
             ->namespace($this->namespace)
             ->group(base_path('routes/web.php'));
    }
    /**
     * アプリ用ルーティングの定義
     * @return void
     */
    protected function mapAppRoutes()
    {
        Route::prefix('app')
             ->middleware('web')
             ->namespace($this->namespace)
             ->group(base_path('routes/app.php'));
    }

 もうほとんどコピペです。他にもモデルを定義する事によりコントローラ上の引数で楽をできます。

    // RouteServiceProvider内部
    /**
     * Define your route model bindings, pattern filters, etc.
     * @return void
     */
    public function boot()
    {
        parent::boot();
        Route::model('member_rank', MemberRankMaster::class);// 追記部
    }
          // route/web.php内部
          Route::resource('member_rank', 'Admin\MemberRankController');

 上のコードの様にモデルクラスとルーティング名の対応の定義とそのルーティング名によるコントローラの呼び出しを記述することによって

    /**
     * member_rank/{id}/editで呼び出されるメソッド
     * Show the form for editing the specified resource.
     * @param  MemberRankMaster          $rank
     * @return \Illuminate\Http\Response
     */
    public function edit(MemberRankMaster $rank)
    {
        return view('admin.pages.member_rank.edit')
            ->with(compact('rank'));
    }

 このように引数でいきなりMemberRankMaster::findOrFail($id)で得られる様なEloquentオブジェクトが使えます。わかりやすい上にシンプルな記述で済みますね。

  • この記事いいね! (0)
takahashi 著者:takahashi

Laravel環境をインポートしたときに”artisan package:discover handling the post-autoload-dump event returned with error code 1″エラーが出た時に確認したいこと

いろいろあって、開発中のLaravel環境をgit経由で再インポートしたのですが、再度npm installしてからcomposer installしたところ、こんなエラーが発生しました。

Script @php artisan package:discover handling the post-autoload-dump event returned with error code 1

エラーで調べるとこんな記事が。

[Laravel] プロダクション環境にはLaravel Duskをインストールしない – 端くれプログラマの備忘録

Laravel Duskはブラウザでの自動テストAPIを提供するプラグインで、製品版(production)モードではセキュリティ上の理由で無効化しなければいけないため、productionモードで有効化されているとエラーになります。

記事の通りにcomposer.jsonを確認すると

...
"require": {
        "php": "^7.1.3",
        "ext-json": "*",
        "fideloper/proxy": "^4.0",
        "intervention/image": "^2.4",
        "lampager/lampager-laravel": "^0.3.0",
        "laravel/framework": "5.6.*",
        "laravel/tinker": "^1.0",
        "league/flysystem-aws-s3-v3": "^1.0",
        "maatwebsite/excel": "^3.1",
        "simplesoftwareio/simple-qrcode": "^2.0"
    },
    "require-dev": {
        "beyondcode/laravel-dump-server": "^1.0",
        "filp/whoops": "^2.0",
        "friendsofphp/php-cs-fixer": "^2.13",
        "fzaninotto/faker": "^1.4",
        "laravel/dusk": "^4.0",
        "mockery/mockery": "^1.0",
        "nunomaduro/collision": "^2.0",
        "phpunit/phpunit": "^7.0",
        "squizlabs/php_codesniffer": "^3.3"
    },
...

あれ、含まれてない(汗

ちゃんとrequire-devにしか”laravel/dusk”の項目は入っていません。
もしやと思ってcomposer.lockの方を確認すると…

...
"name": "laravel/dusk",
            "version": "v4.0.2",
            "source": {
                "type": "git",
                "url": "https://github.com/laravel/dusk.git",
                "reference": "9810f8609c8b53d9a3bac7d38c56530e0d77a6bb"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/laravel/dusk/zipball/9810f8609c8b53d9a3bac7d38c56530e0d77a6bb",
                "reference": "9810f8609c8b53d9a3bac7d38c56530e0d77a6bb",
                "shasum": ""
            },
...

含まれていました。
composer installすると
composer.lock
の記述が優先されます。

今回の原因は、composer.lockにその記述が入っていることが原因か…
と思いましたが、ここでふと気づきます。

(まてよ、そもそも開発を始めるときにdevelopモードにしていたはずじゃなかったっけ…)

そもそも今回のエラーはproductionモードにしていなければ出ないはずのエラーなのですが、それにもかかわらず出てしまっているので、よく考えたらおかしな状況です。
そこで調べたところ、.envファイルが入っていないことに気づきました。

前に使っていたLaravelのプロジェクトから.envファイルを書き戻してみると、

composer install
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Nothing to install or update
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover
Discovered Package: beyondcode/laravel-dump-server
Discovered Package: fideloper/proxy
Discovered Package: intervention/image
Discovered Package: lampager/lampager-laravel
Discovered Package: laravel/dusk
Discovered Package: laravel/tinker
Discovered Package: maatwebsite/excel
Discovered Package: nunomaduro/collision
Discovered Package: simplesoftwareio/simple-qrcode
Package manifest generated successfully.

となり、先ほどのDuskのエラーは出なくなりました。

.envファイル内の
APP_ENV
の設定が
APP_ENV=local
または
APP_ENV=development
になっていないと、自動でproductionモードとして動作してしまい、Duskの部分でエラーになるようです。

つまり開発の際は.envファイルに
APP_ENV=local
または
APP_ENV=development
を記述するのが必須になってきますので、最初にLaravel環境を展開したときは要注意です。

  • この記事いいね! (0)