カテゴリーアーカイブ Laravel

著者:杉浦

【Laravel】DBに依存させずにEloquentモデルをテストする

 EloquentはLaravelに備わっているORM(Object-relational mapping)です。
Eloquent:利用の開始 5.7 Laravel
 オブジェクト関連マッピングとはオブジェクトとDB(データベース)の関連のマッピングのことであり、その都合上オブジェクトはデータベースと密接な関係にあります。

<?php
class Post extends Model
{
    // これだけでpostsテーブル中の各レコードを呼べる。
}


 この密接な関係のため、モデルをテストする際にDBが付きまといがちです。何を呼び出すにしても、DBの中を見に行きます。しかしモデルのメソッドのテストはDBから切り離されるべきです。これはモデルのテストがそれ以上掘り下げるべき最小単位のユニットテストであり、テスト失敗の原因がテストやソースコードのエラーかDBのエラーか考える手間を省くべきでもあるからです。
 次の様なコードを記述することでDBからEloquentモデルを切り離してテストできます。

<?php

namespace Tests\Unit\App\Models;

use App\Models\User;
use Closure;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Collection;
use Tests\TestCase;

class UserTest extends TestCase
{
    public function testScopeFindOrFailPort()
    {
        self::assertInstanceOf(User::class, UserMock::scopeFindOrFailPort(UserMock::query(), '20100'));
        try {
            UserMock::scopeFindOrFailPort(UserMock::query(), 'hoge');
            self::assertTrue(false, 'not foundでなかった');
        } catch (ModelNotFoundException $e) {
            self::assertTrue(true);
        }
    }
}

/**
 * モデルUserのモック。データベースに関わらず一定のものを返すようにしている。
 */
class UserMock extends User
{
    public $mock_data;
    public $mock_return_data;

    /**
     * {@inheritdoc}
     * UserMock constructor.
     * @param array $attributes
     */
    public function __construct(array $attributes = [])
    {
        parent::__construct($attributes);
// テストデータ定義
        $mocks = [];
        for ($i = 0; $i < 5; ++$i) {
            $mocks[] = (new User())->forceFill([
                'id' => $i,
                'group_key' => $i % 3,
                'hash' => 'hash_'.$i,
                'port' => ['1234', '12345', '20100'][$i % 2],
                'created_at' => now(),
                'updated_at' => now(),
                'deleted_at' => null,
            ]);
        }
        $this->mock_data = new Collection($mocks);
    }

// DBにアクセスするLaravel内部に隠れている部分を引っ張り出して置き換え

    /**
     * {@inheritdoc}
     * @return User|Builder|UserMock
     */
    public static function query()
    {
        return new self();
    }

    /**
     * {@inheritdoc}
     * @param  array                    $columns
     * @return User|Model|object|null
     */
    /** @noinspection PhpHierarchyChecksInspection */
    public function first($columns = [])
    {
        return $this->mock_data->first();
    }

    /**
     * {@inheritdoc}
     * @param  array|Closure|string $column
     * @param  string|null          $operator
     * @param  null                 $value
     * @param  string               $boolean
     * @return User|UserMock
     */
    /** @noinspection PhpHierarchyChecksInspection */
    public function where($column, $operator = null, $value = null, $boolean = 'and')
    {
        if (func_num_args() === 2) {
            $value = $operator;

            $operator = '=';
        }
        $this->mock_data = $this->mock_data->where($column, $operator, $value);

        return $this;
    }
}

 重要なのは、テスト対象のモデルを継承しているがテスト対象のモデル内で定義されたメソッドを変更していないクラス、をテストコードに記述することです。このUserMockクラスは、UserクラスのDBにアクセスしてデータを取ってくる部分を、テストコード中で定義する値を取ってくる様に上書きしたクラスです。テスト対象そのものを置き換えている様にも見えますが、この置き換えている部分は大過ないことが担保されているLaravel内部のテスト済みコードのModelクラスの部分です。

 テスト用クラスUserMockはテスト対象クラスUserで定義されたコードを変更せず、Userの更に奥のModelから継承したコードを変更しています。これによりUserクラスを破壊することなく、DBのアクセスの代わりにローカル変数へのアクセスを行うUserクラスであるUserMockクラスが作れます。
 このようにするとデータベースに依存することなくEloquentのテストが出来ます。こういったコードはローカルのPHPのみで完結しており、実行速度が速い、他のデータリソースを汚染しない、と便利です。ここまで小さいテストならばモデル全体を通しても10秒とかからずにテストが終わります。おかげでGitにコミットする度にローカルで走らせても苦になりません。

DBへのアクセスメソッドは大体\Illuminate\Database\Eloquent\Builder以下にそろっています。

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

【Laravel】artisan view:cacheでBladeの中身を読めるようにする

 LarvelはBladeなるテンプレート言語を備えています。
Bladeテンプレート 5.7 Laravel
 便利は便利なのですが独自な上、説明もざっくばらんなので詳細に中身を把握するのは難しいです。
 artisan view:cacheはresources以下のBladeファイルのキャッシュを生成するコマンドです。このコマンドで生成されるPHPファイルはBladeをPHP言語にコンパイルした結果です。コンパイル後のファイルを読むことでBladeが何をやっているのか少しわかりやすくなります。
 例えば次のコードをコンパイルすると

@section('header')
@endsection
@yield('header')

@section('footer')
@show

@section('body')
@overwrite
@yield('body')

次の様になります。

<?php $__env->startSection('header'); ?>// @section
<?php $__env->stopSection(); ?>// @endsection
<?php echo $__env->yieldContent('header'); ?>// @yield

<?php $__env->startSection('footer'); ?>// @section
<?php echo $__env->yieldSection(); ?>// @show

<?php $__env->startSection('body'); ?>// @section
<?php $__env->stopSection(true); ?>// @overwrite
<?php echo $__env->yieldContent('body'); ?>// @yield

 こうやってPHPコードに直すとやっていることを追いかけられるようになり、中身を知ることが出来ます。
 @endsection, @yield, @show, @overwriteはそれぞれ@sectionと密に関わるディレクティブです。
Bladeテンプレート 5.7 Laravel#レイアウト定義
 @endsectionはsectionで定義を始めたコードの終点を示し、@yieldはsectionの出力を示し、@showはsectionで定義を始めたコードの終点であり即時展開を示し、overwriteは同名sectionの上書きを行うsection定義の終点を示します。これだけだとどういうこととなりますがコードを読むと少しわかります。@sectionの中身であるstartSectionの内容が次です。

    /**
     * Start injecting content into a section.
     *
     * @param  string  $section
     * @param  string|null  $content
     * @return void
     */
    public function startSection($section, $content = null)
    {
        if ($content === null) {
            if (ob_start()) {// ここが大事
                $this->sectionStack[] = $section;
            }
        } else {
            $this->extendSection($section, $content instanceof View ? $content : e($content));
        }
    }

 obstart()でこれ以降の出力(ブラウザに映る予定のHTMLコードなど)をバッファにため込みます。これは改めて命令されるまで出力されません。ついで@endsection,@overwriteの中身であるstopSection()が次です。

    /**
     * Stop injecting content into a section.
     *
     * @param  bool  $overwrite
     * @return string
     *
     * @throws \InvalidArgumentException
     */
    public function stopSection($overwrite = false)
    {
        if (empty($this->sectionStack)) {
            throw new InvalidArgumentException('Cannot end a section without first starting one.');
        }

        $last = array_pop($this->sectionStack);

        if ($overwrite) {
            $this->sections[$last] = ob_get_clean();
        } else {
            $this->extendSection($last, ob_get_clean());
        }

        return $last;
    }

 ob_get_clean()でバッファの内容を取得し、出力バッファを削除します。@overwriteはこの$overwrite=trueでstopSectionを呼び出しています。これにより問答無用でプロパティsectionsの末尾にバッファを書き込んでいます。@overwriteに引数がないのは必ず末尾を参照していたせいです。
 $overwrite=falseの時はextendSection()へ回って、もし以前に定義していればそちらを呼び出すようにしています。
 @showの中身であるyieldSection()、@yieldの中身であるyieldContent()が次です。

    /**
     * Stop injecting content into a section and return its contents.
     *
     * @return string
     */
    public function yieldSection()
    {
        if (empty($this->sectionStack)) {
            return '';
        }

        return $this->yieldContent($this->stopSection());
    }
    
    /**
     * Get the string contents of a section.
     *
     * @param  string  $section
     * @param  string  $default
     * @return string
     */
    public function yieldContent($section, $default = '')
    {
        $sectionContent = $default instanceof View ? $default : e($default);

        if (isset($this->sections[$section])) {
            $sectionContent = $this->sections[$section];
        }

        $sectionContent = str_replace('@@parent', '--parent--holder--', $sectionContent);

        return str_replace(
            '--parent--holder--', '@parent', str_replace(static::parentPlaceholder($section), '', $sectionContent)
        );
    }

 yieldSectionは短縮形の様なものです。stopSection、yieldContentの順に呼び出し、最後に宣言したsectionを閉じ、出力しています。
 yieldContentはプロパティ$this->sectionsの中から目当てのsectionを選び、整形して返しているのみです。
 @endsection, @yield, @show, @overwriteはBlade内部では糖衣構文として役割に応じた大きく違う名前を与えられていましたが、実際にはちょっとした違う引数で呼び出される同じ関数らであることがわかります。

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

LaravelをNginxで動作させる際の設定

LaravelではどのURLを指定されても、(自分の担当するパス配下へのリクエストであれば)Laravel自身を起動させるために必ずpublic/index.phpを経由させる必要があります。

この設定はPHPだけでは限界があるため、Webサーバー側の設定で対応する必要があります。

Laravelのパッケージにはこの”index.phpを必ず経由させる”という設定が書かれた設定ファイルがデフォルトで含まれています。
具体的には

public/.htaccess

上記のファイルがこの設定ファイルにあたります。
ところが、この.htaccessファイルは基本的にApacheなどのごく一部のWebサーバーでしか認識しないので、たとえは先日の記事のようにnginxにPHPをインストールした環境で動作させようとしても、そのままではうまく動いてくれません。

この場合、.htaccessに書かれている設定と同様の働きになる設定をWebサーバーの設定ファイルに指定する必要が出てきます。
ただし、その記述の仕方もApacheとは異なってきますので、各Webサーバー用の書き方に書き換えないといけません。

nginxの場合、.htaccessの中身をnginx用の設定に書き換えてくれるWebサービスがあり、まず最初にこのサービスを使って変換した設定ファイルを使って設定を行ってみました。

元の.htaccess

<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews -Indexes
    </IfModule>

    RewriteEngine On

    # Handle Authorization Header
    RewriteCond %{HTTP:Authorization} .
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

    # Redirect Trailing Slashes If Not A Folder...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_URI} (.+)/$
    RewriteRule ^ %1 [L,R=301]

    # Handle Front Controller...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

上記サイトで変換後のnginxコンフィグ

# nginx configuration

location / {
  if (!-e $request_filename){
    rewrite ^(.*)$ /%1 redirect;
  }
  if (!-e $request_filename){
    rewrite ^(.*)$ /index.php break;
  }
}

実際に入れ込むと以下のようになります。

server {

  listen        80;
  listen        443 ssl;

  server_name  example.com;

  ssl_certificate       /path/to/ssl/fullchain.pem;
  ssl_certificate_key   /path/to/ssl/privkey.pem;

  root /path/to/docroot;
  client_max_body_size 1g;
  location / {
        if (!-e $request_filename){
                rewrite ^(.*)$ /%1 redirect;
        }
        if (!-e $request_filename){
                rewrite ^(.*)$ /index.php break;
        }      
        index index.php index.htm index.html;
  }
  location ~ \.php$ {
    if (!-e $request_filename){
        rewrite ^(.*)$ /%1 redirect;
    }
    if (!-e $request_filename){
        rewrite ^(.*)$ /index.php break;
    }
    fastcgi_pass   unix:/var/run/php-fpm.sock;
    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    include        fastcgi_params;
    allow all;
  }
}

ところが、上記のような設定を行ったところ、リダイレクトループに陥ってしまいます。

いろいろ調べたところ、Nginx向けのLaravel用の設定が公開されていました。

nginxをLaravel5.4用に設定する – Qiita

上記の記事を基に今回の場合の設定ファイルを書き換えてみます。

server{
   listen        80;
   listen        443 ssl;

   server_name  example.com;

   ssl_certificate       /path/to/ssl/fullchain.pem;
   ssl_certificate_key   /path/to/ssl/privkey.pem;

   root /path/to/docroot;

   location / {     
        index  index.php index.html index.htm;
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass   unix:/var/run/php-fpm.sock;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }
}

全然違うじゃん…

やはり、ただ設定を変換しただけではうまく行かないみたいですね。

参考元の記事ではLaravel5.4となっていましたが、5.6の環境でも同様の設定で問題なく動作しました。

Laravel + Nginxの構築でお困りの方の参考になれば幸いです。

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

【Laravel】Collection中のitemが未定義でもnullでもないか検査したい時はisset()でなくget()!==nullを使うべき

 CollectionはLaravelの持つ配列をラッピングしたクラスです。Collectionを用いることによってPHPの素の配列操作よりも多彩な配列操作を一貫性を保ったまま容易に実現できます。

Illuminate\Support\Collectionクラスは配列データを操作するための、書きやすく使いやすいラッパーです。以下の例をご覧ください。配列から新しいコレクションインスタンスを作成するためにcollectヘルパを使用し、各要素に対しstrtoupperを実行し、それから空の要素を削除しています。
コレクション 5.7 Laravel

 Collectionは素晴らしいですが、題名にあるisset()の関連部分では特殊な挙動を見せます。

$arr = ['hoge' => null];
dump(isset($arr['hoge']));// false
dump(isset($arr['fuga']));// false

$col = new \Illuminate\Support\Collection(['hoge' => null]);
dump(isset($col['hoge']));// true
dump(isset($col['fuga']));// false

 Collectionを介したせいで結果が素の配列と異なってしまいました。これはCollectionのArrayAccessインタフェースの実装の仕方に原因があります。
 ArrayAccessインタフェースはオブジェクトを配列として呼び出すためのインタフェースです。これを実装すると次の画像の様にオブジェクトを配列として呼び出せます。
 
PHP: ArrayAccess – Manual
 ArrayAccessの中でissetと直接的に絡んでいるメソッドがoffsetExistsです。

このメソッドが実行されるのは、ArrayAccess を実装したオブジェクト上で isset() あるいは empty() を使用した場合です。
PHP: ArrayAccess::offsetExists – Manual

 \Illuminate\Support\CollectionのArrayAccess::offsetExistsは次の様に実装されています。

    /**
     * Determine if an item exists at an offset.
     *
     * @param  mixed  $key
     * @return bool
     */
    public function offsetExists($key)
    {
        return array_key_exists($key, $this->items);
    }

 $this->itemsがCollection内で扱われている配列です。これはkeyが存在すれば値がnullであってもissetでtrueを返すということです。このため

$col = new \Illuminate\Support\Collection(['hoge' => null]);
dump(isset($col['hoge']));// true

となります。
 一見バグの様に見えますが、Determine if an item exists at an offset.とコメントされている上、実装内容を

    public function offsetExists($key)
    {
        return isset($this->items[$key]);
    }

と変えるとこの変更が原因でLaravel自体のテストで失敗が起きます。つまり仕様です。

 このアンチパターンと化したisset()の代わりになる判定方法が次のコードです。

$col = new \Illuminate\Support\Collection(['hoge' => null]);
dump($col->get('hoge') !== null);// false
dump($col->get('fuga') !== null);// false

https://implode.io/5yDeL4
 Collection中のgetメソッドを用います。getメソッドはキーの値を返すメソッドです。次の引用の様にキーが存在しない場合、例外でなくnullを返します。

getメソッドは指定されたキーのアイテムを返します。キーが存在していない場合はnullを返します。

 これによりgetメソッドの返り値はキーの値がnullであっても、キーが存在しなくともnullになります。これでissetと同じく、変数がセットされていること、そしてNULLでないことを検査することができます。

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

【Laravel】Laravel付きのPHPオンラインテストページImplode.io

Implode.io
 Implode.ioは簡易なPHPをテストする環境を持ったwebサービスです。Implode.ioにはコードを簡単に書き、共有できるような環境が整っています。Implode.ioの特長はフレームワークLaravelを使えることです。図の様にフレームワークを指定することで組み込まれているクラス、関数などが使えます。

 図のNewからセットを選ぶとモデルやテンプレートを試すこともできます。

 下図中の赤丸部分の画面上部にある検索ボックスから今まで大勢に作られてきたコードを探せます。

 これでLaravelのみに関わるテストでも、開発中のコードを触ったり余計な計算資源を食ったりせずに済みます。

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

【Laravel】Bladeの制御構文の探し方

 LaravelのBladeドキュメントは丁寧でわかりやすいですが、わかりやすさのために切り捨てられている部分もあります。例えばBladeの制御構文がいくつか抜け落ちています。この記事では探し方を紹介します。探し方が分かればバージョンが変わっても平気です。
Bladeテンプレート 5.7 Laravel
 探すことをプログラムに任せる直接的な解決策があります。そういうプラグインのあるエディタを使用してそのプラグインを入れましょう。PhpStormならばBlade Supportがあり次の画像の様に、色付けと予測を行ってくれます。
 Blade Support – Plugins | JetBrains

 Bladeの元を探そうとしてもクラスやメソッドの呼び出しを辿って見つけることは難しいです。というのもLaravelはそのソースコード内で特定の名前のクラス、メソッドをまとめて呼び出したり、その場でPHPの構文を作ったりすることで根本のコードを小さく保っているためです。例えば次の様なコードが現れます。

    /**
     * Register the engine resolver instance.
     *
     * @return void
     */
    public function registerEngineResolver()
    {
        $this->app->singleton('view.engine.resolver', function () {
            $resolver = new EngineResolver;

            // Next, we will register the various view engines with the resolver so that the
            // environment will resolve the engines needed for various views based on the
            // extension of view file. We call a method for each of the view's engines.
            foreach (['file', 'php', 'blade'] as $engine) {
                $this->{'register'.ucfirst($engine).'Engine'}($resolver);
            }

            return $resolver;
        });
    }

 このためLaravelの中を探す時はそれっぽいクラス名、メソッド名を用いた正規表現によって隠された呼び出し元を探す方法が有効です。肝心のBlade構文を字句解析(大雑把いえば文字列を意味のある字句に区切る処理)している部分は次です。この部分はViewのProvider周りから呼び出されています。

// vendor/laravel/framework/src/Illuminate/View/Compilers/BladeCompiler.phpの内部
    /**
     * All of the available compiler functions.
     *
     * @var array
     */
    protected $compilers = [
        'Comments',
        'Extensions',
        'Statements',
        'Echos',
    ];
    
    
    /** 省略 **/
    
    /**
     * Parse the tokens from the template.
     *
     * @param  array  $token
     * @return string
     */
    protected function parseToken($token)
    {
        [$id, $content] = $token;

        if ($id == T_INLINE_HTML) {
/** ここから */
            foreach ($this->compilers as $type) {
                $content = $this->{"compile{$type}"}($content);
            }
/** ここまでが重要 */
        }

        return $content;
    }

 $this->{“compile{$type}”}($content);を満たすメソッドで字句解析をしています。これを満たすメソッドで@以下を解析しているのが次のcompileStatementsです。

    /**
     * Compile Blade statements that start with "@".
     *
     * @param  string  $value
     * @return string
     */
    protected function compileStatements($value)
    {
        return preg_replace_callback(
            '/\B@(@?\w+(?:::\w+)?)([ \t]*)(\( ( (?>[^()]+) | (?3) )* \))?/x', function ($match) {
                return $this->compileStatement($match);
            }, $value
        );
    }

    /**
     * Compile a single Blade @ statement.
     *
     * @param  array  $match
     * @return string
     */
    protected function compileStatement($match)
    {
        if (Str::contains($match[1], '@')) {
            $match[0] = isset($match[3]) ? $match[1].$match[3] : $match[1];
        } elseif (isset($this->customDirectives[$match[1]])) {
            $match[0] = $this->callCustomDirective($match[1], Arr::get($match, 3));
/** ここから*/
        } elseif (method_exists($this, $method = 'compile'.ucfirst($match[1]))) {
            $match[0] = $this->$method(Arr::get($match, 3));
        }
/** ここまで重要 ucfirstは最初の一文字が大文字なら小文字にする関数*/
        return isset($match[3]) ? $match[0] : $match[0].$match[2];
    }

 到達しました。BladeCompilerのインスタンスに引っ付いているcompileHogeなメソッドがあれば@hogeが動作するということです。後はBladeCompilerに生えているメソッド一覧を見て終わりです。


 多いです。使いこなせている人はいるのでしょうか。

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

【Laravel】Laravel5.6以降のヘルパ関数abortは結構自由

 LaravelにはHTTP例外を投げるヘルパ関数abortが用意されています。

abort関数は、例外ハンドラによりレンダーされるであろう、HTTP例外を投げます。

abort(403);

例外のレスポンステキストと、カスタムヘッダを指定することもできます。

abort(403, 'Unauthorized.', $headers);

ヘルパ 5.7 Laravel#method-abort

 abort(403);と一言記述するのみで権限エラーである403のエラーページにクライアントを飛ばせます。この使い方が最も主で伝統的な使い方です。しかしもう少し複雑なHTTPレスポンスを返したい場合もあります。
 使い方が書かれている公式の翻訳ドキュメントにおいて、abort関数はステータスコードとメッセージとヘッダを指定するだけの簡易な関数に見えますが、実際はもっと自由です。ヘルパ関数abortは/vendor/laravel/framework/src/Illuminate/Foundation/helpers.phpにあるabort関数を呼び出しています。その呼び出される部分が次です。

// Laravel5.7版のコード
if (! function_exists('abort')) {
    /**
     * Throw an HttpException with the given data.
     *
     * @param  \Symfony\Component\HttpFoundation\Response|\Illuminate\Contracts\Support\Responsable|int     $code
     * @param  string  $message
     * @param  array   $headers
     * @return void
     *
     * @throws \Symfony\Component\HttpKernel\Exception\HttpException
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
     */
    function abort($code, $message = '', array $headers = [])
    {
        if ($code instanceof Response) {
            throw new HttpResponseException($code);
        } elseif ($code instanceof Responsable) {
            throw new HttpResponseException($code->toResponse(request()));
        }

        app()->abort($code, $message, $headers);
    }
}

 注目する部分はPHPDocの$codeで指定されている型と関数が始まった直後のif文部分です。これは$codeにはステータスコードになるintのみでなく\Symfony\Component\HttpFoundation\Responseを継承するか\Illuminate\Contracts\Support\Responsableを実装したクラスのインスタンスを引数にすれば、HTTPレスポンスとして適した形で例外の結果をクライアントに返します。
 PHPのinstanceofは完全にイコールのインスタンスのみでなく継承元、実装元の場合でも真になります。
PHP: 型演算子 – Manual

 このinstanceofの性質からabort関数は例えばこのように使えます。

abort(resnponse()->json("I'm a teapot", 418));

 ExceptionクラスやHandlerクラスを気にすることなく例外の結果をJSON型のHTTPレスポンスとしてクライアントに投げられます。
 LaravelにはインタフェースIlluminate\Contracts\Support\Jsonableがあり、これを実装したクラスはJSON化が担保され、至る所でJSONとして扱えます。JsonableはModelやCollectionを始めとして多くのもので実装されています。このため次の様なこともできます。

// User.php
class User extends Model implements Responsable
{
    public function isOne()
    {
        return $this->getKey() === 1;
    }
    public function toResponse($request)
    {
        return response()->json($this);
    }
}


// 適当な別ファイル
$a = User::inRandomOrder();
$a->isOne() ?: abort($a);

 Modelの場合、プロパティがJSON化されます。abortに渡されたUserインスタンスのtoResponseメソッドが動作し、response()->json($this)によりJSON化されたUserインスタンスがHTTPレスポンスとして返されます。
 この変更はLaravel5.6時に追加されました。
allow giving a response to abort · laravel/framework@4e29889

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

PHPStormでartisanコマンドの予測入力をする

 PHPフレームワークLaravelにはartisanコマンドがあります。artisanはアプリケーション開発全体で役に立つ、数多くのコマンドを提供しています。例えば必須ないしあった方がよいコードを持ったモデル、テスト、ミドルウェアの作成、データベースのまとまった操作、Laravelが今持つことになるルーティングリストの表示です。もちろん他にも様々なものがあります。
 artisanの一覧を見ることはphp artisan listからできますが、いちいち確認するのは面倒です。PHPStormのコマンドラインツールにはSymfonyのコマンドライン用クラス\Symfony\Component\Console\Command\Commandを元に作成されたコマンドのコマンドライン入力とサポートするプラグインCommand Line Tool Supportがあります。LaravelのartisanコマンドはSymfonyのCommandクラスから継承されて作られています。次の図はCommandクラスの継承先のリストの一部です。各aritsanコマンドの名が冠されたクラスが並んでいます。
 
 プラグインは設定->プラグインのマーケットプレイスから検索できます。インストールできたなら設定->ツール->コマンド・ライン・ツールのサポートを選び、追加からTool Based on Symfony Consoleを選択、php実行ファイルとartisanファイルのパスを設定して完成です。


 これでartisanコマンドの予測ができるようになりました。artisanを介することでnamespaceや必須メソッド、継承元などをいちいち記述せずとも各基本クラスのひな型を作ることができ、データベースの操作もまとめてでき、開発がはかどります。

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

ダミー画像表示サイト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)