20191112のlaravelに関する記事は9件です。

laravelのbladeをVue.jsで書き換える【Form編】

はじめに

プログラミング学習をはじめ、10カ月です。
現在はLravelとVue.jsを学習しています。

この組み合わせだと世間的には、SPAと決まっているのですか?

Vueも少しずつわかってきたので、laravelのbladeをVueに書き替えて、MPAを作り始めました。

基本のCRUD処理をと思いましたが、さっそくつまずきました。

register画面を作ろう

laravelやVue.jsの基本は理解している前提で進めていきます。

ポイントを押さえながら解説します

まずは基本となるregister.phpです。

register.php
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <meta name="csrf-token" content="{{ csrf_token() }}"> //①
  //中略(cssなど)
    <title>title</title>
</head>
<body>
    <div id="app">
        <registerform-component 
            :old="{{ json_encode(Session::getOldInput()) }}" //②
            :errors= "{{ $errors }}"> //③
        </registerform-component>
    </div>
    <script src="{{ mix('js/app.js') }}"></script>
</body>

htmlではポイントは3つ
①laravelではcsrf対策を取らないといけません。
headにトークンを仕込んでおきます

②フォームで重要なのは入力ミスがあった場合、元のフォームにリダイレクトされます。入力した内容は残って欲しいですよね。
laravelのbladeでは、oldヘルパーが使えたのですが、Vueでは使えませんでした。
ここはあとで解説します。

③入力ミスがあった場合、何が間違っているのか、バリデーションによるエラーメッセージが表示されます。
laravelでは$errorsでどこからでも拾ってこれるそうですね。

\$errors変数はwebミドルウェアグループに所属する、Illuminate\View\Middleware\ShareErrorsFromSessionミドルウェアによりビューに結合されます。このミドルウェアが適用される場合は、いつでもビューの中で\$errors変数が使えます。$errors変数はいつでも定義済みであると想定でき、安心して使えます。
https://readouble.com/laravel/5.5/ja/validation.html

しかし、これもVueの中では素直に使えませんでした。

Componentを作ろう

今回はRegisterForm.Vueを作成しました。
予め使えるようコンポーネントの登録はしておいてください。

RegisterForm.Vue
<template>
    <form action="/register" method="POST"> //④

        <input type="hidden" name="_token" :value="csrf"> //⑤

        <div class="form-group">
            <label>名前</label>
            <strong class="error" v-for="value in error.name">{{ value }}</strong> //
            <input class="form-control" name="name" type="text" v-model="name"> //⑦
        </div>
        <div class="form-group">
            <label>Email</label>
            <strong class="error" v-for="value in error.email">{{ value }}</strong>
            <input class="form-control" name="email" type="text" v-model="email">
        </div>
        <div class="form-group">
            <label>パスワード</label>
            <strong class="error" v-for="value in error.password">{{ value }}</strong>
            <input class="form-control" name="password" type="password"  v-model="password" autocomplete="off">
        </div>
        <div class="form-group">
            <label>再入力</label>
            <input class="form-control" name="password_confirmation" type="password" v-model="password_confirmation" autocomplete="off">
        </div>

        <input type="submit"  class="button" value="登録"> //⑧
    </form>
</template>

<script> //⑨
    export default {
        props:[
            'old',
            'errors'
        ],
        data:function(){
            return{
                csrf: document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
                name: this.old.name,
                email:this.old.email,
                password:'',
                password_confirmation:'',
                error:{
                    name:this.errors.name,
                    email:this.errors.email,
                    password:this.errors.password,
                }
            }
        },
    }

コンポーネントはこんな感じにしました。

④register登録は/registerにPOSTします。

⑤laravelではPOSTデータにcsrfトークンを仕込まないと、弾かれてしまいます。
scriptタグで取得したトークンをもたせます。
csrf: document.querySelector('meta[name="csrf-token"]').getAttribute('content')

これは、このコンポーネントが入るregister.phpのheadに記述したものをとってきています。

⑥ここはエラー文ですね。後述します。

⑦はVueの基本の双方向バインディングですね。
Vーmodelでinputが変更されると、dataも変更されます。

⑧でサブミットされますね。

先述の
<registerform-component
:old="{{ json_encode(Session::getOldInput()) }}" //②
:errors= "{{ $errors }}"> //③
</registerform-component>

の③で
コンポーネントに変数$errorsを渡します。
コンポーネント内ではpropで使用することができます。

同様に②で入力したデータはセッションに保存されています。
これをgetOldInput()で取得できます。
これをjson_encode()でjsonデータに直してコンポーネントに渡しています。

propで受け取ったデータは、thisで使用できます。
dataの初期値にthisで取得したデータを入れることで、入力したデータやエラーメッセージを表示させることができます。

まとめ

こうして、無事にフォームができたのでした。
かなり時間かかりましたが、なんとかなりました。
コンポーネントは複雑化させていきますので、
次はvuexを導入します。

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

【サルが書く】Laravelのpassportでの認証するテーブルをカスタマイズ方法

わきゃ〜〜

LaravelのPassportを使い、OAuth2.0のユーザー認証を行いました。
そこで、標準のemail,passwordのみの認証ではない認証方法を取りたかったので、ちょっと調べてみました。

実行環境
Laravel 5.5
Laravel Passport 4.0
PHP 7.x

Laravel Passportは導入した状態でのお話になります。

認証するカラムを変更したい

といっても、かなり簡単にカスタマイズは簡単です。
認証するテーブルのModelに下記のコードを追加するだけです。

usernameを認証するカラムを変更する場合

Model/User.php
public function findForPassport($username)
{
    // 認証カラム名が name の場合
    return $this->where('name', $username)->first();
}

これは実際に認証を行っている、Laravel Passportの Laravel/Passport/Bridge/UserRepository.php にある getUserEntityByUserCredentials() というメソッドがあります。
そのメソッドにModelがfindForPassportというメソッドを持っていれば、メソッドを上書きするような処理があるためです。

認証する条件を変更する

whereをつなげて認証する条件を追加することもできます。
whereで表現できるものであれば、カスタマイズ可能だと思います。

Model/User.php
public function findForPassport($username)
{
    // nameとrole_idの2つの条件で認証を行う
    return $this->where('name', $username)->where('role_id', $role_id)->first();
}

参考文献

http://hakaikosen.hateblo.jp/entry/2017/11/11/084621

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

Laravel Debugbarで簡易速度計測

Laravelをお使いの皆さんにはおなじみLaravel Debugabrですが簡易的な速度計測ができることをご存知でしょうか。
xhprofやblackfireを使えというマサカリは受け付けません
明らかに処理が遅そうだが確証がないな〜、簡易的でいいから計測したいな〜というときに便利です。

start_measure('このへん重そう');
// ここに怪しい処理がある
stop_measure('このへん重そう');

ヘルパー関数が用意されているのでこんな感じで怪しい処理にstart_measurestop_measureを挟みましょう。
始まりと終わりは同じ文字列を入れましょう。

いつものDebugbarのTimelineで以下のように見ることができます。
2019-11-12 17.24.20 localhost adc9fd177940.png
(View数,Query数はひみつ)
はい、全然違いましたね。

start_measure('このへん重そう');
// ここに怪しい処理がある
stop_measure('このへん重そう');

start_measure('じゃあこっちか?');
// あんまり怪しくないと思ってた処理
stop_measure('じゃあこっちか?');

2019-11-12 17.27.52 localhost 471cc4561bf1.png

無事遅い箇所が特定できました。
他にもログを出す機能も付いてるので活用してみてください。
おしまい

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

Laravel5.5から一気にLaravel6.0へアプデ

メモ

snake_case()
studly_case()
str_singular()
str_random()
str_plural()
array_pluck()

が無くなった。
というか、ヘルパがごっそり変わった。

use Illuminate\Support\Str;
use Illuminate\Support\Arr;

Str::snake()
Str::studly()
Str::singular()
Str::random()
Str::plural()
Arr::pluck()

array_except

で怒られる。

viewのキャッシュクリアでおk

php artisan view:clear

str_slug

で怒られる。

どこかにテストで新規Laravel6プロジェクトを作成して、
config/cache.php
config/session.php
をLaravel6のファイルで上書きする(これらを変更している場合はその変更を反映させる)。

composer create-project "laravel/laravel=^6.0" laravel6test

viewのページング部分がレイアウトあたっていない。

デフォルトで、bootstrap-4.blade.phpというファイルが使用されるようになった。
ので、ファイル名を変えるか、ファイルを指定する。
今後も変更あるかもしれないので、指定したほうが無難。

{{ $documents->appends(request()->input())->links('vendor/pagination/default') }}

laravelcollective/htmlを使用している場合、
GETで送信した際にフォームに初期値がセットされない。

ビューに下記を追加

Form::considerRequest(true);

Laravelのログを見ると、

laravel.EMERGENCY: Unable to create configured logger. Using emergency logger.

で怒られている。

どこかにテストで新規Laravel6プロジェクトを作成して、
config/logging.php
を持ってきて置く。


Access level to App\Http\Requests\CustomRequest::validationData() must be public (as in class Illuminate\Foundation\Http\FormRequest)

で怒られる。

該当のfunctionをpublicにする


なんかエラー画面が出ない

Call to undefined method Whoops\Run::appendHandler()

でいっぱい怒られてる。

そもそもエラー画面が新しくなっている。
composer.jsonのrequire-devを新しくする。

    "require-dev": {
        "facade/ignition": "^1.4",
        "fzaninotto/faker": "^1.4",
        "mockery/mockery": "^1.0",
        "nunomaduro/collision": "^3.0",
        "phpunit/phpunit": "^8.0"
    },

さらに、composer updateの際に

Class 'Symfony\Thanks\GitHubClient' not found

で怒られる。

composer.jsonのscriptsを新しくする。

    "scripts": {
        "post-autoload-dump": [
            "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
            "@php artisan package:discover --ansi"
        ],
        "post-root-package-install": [
            "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
        ],
        "post-create-project-cmd": [
            "@php artisan key:generate --ansi"
        ]
    },

artisan optimizeで失敗する。

The command "php artisan optimize" failed.


optimizeは削除する。


DBに接続できない

.envのパスワードに#やら入ってませんか?
そんなときは、クオーテーションでエスケープしましょう。

DB_PASSWORD="1234#56789"

やっといたほうが良さそうな変更

5.7から以下の変更がされています。

新しくdataディレクトリがstorage/framework/cacheへ追加されました。このディレクトリをアプリケーションに作成してください。

mkdir -p storage/framework/cache/data

次に、新しく作成したdataディレクトリへ、.gitignoreファイルを追加してください。

cp storage/framework/cache/.gitignore storage/framework/cache/data/.gitignore

最後にstorage/framework/cache/.gitignoreファイルを以下のように確実に更新してください。

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

【Laravel】自作ミドルウェア内で取得するルートパラメータはID?それともモデル?

自作ミドルウェアの中で、ルートパラメータを取得してEloquentでfindして・・・、みたいな事をしていた時に気づいた事を書いておきます。

環境

  • php7.3
  • laravel5.8

補足

  • ミドルウェアはKernel.php$routeMiddlewareに追加する
  • 結果は経験則なので理由を調べたわけではない(すみません)

ルーティング

ルーティングでミドルウェアを適用します。

web.php
Route::get('/users/{user}', 'UserController@detail')->middleware('user.open');

ミドルウェアの登録

カーネルで自作ミドルウェアを登録します。

/project/app/Http/Kernel.php
    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    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,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
        'user.open' => \App\Http\Middleware\UserOpenMiddleware::class, //これを追加
    ];

ミドルウェアを作成

UserOpenMiddleware.php
class UserOpenMiddleware
{
    public function handle($request, Closure $next)
    {
        $param = $request->route()->parameter('user');

        /**
         * $paramの値はどっち??
         */
        $userModel = User::findOrFail($param); //10?
        $userModel = $param; //Userモデルのオブジェクト?

        //取得したモデルを使って色々ロジックを書く・・・・
        if($userModel->open){

        }

        return $next($request);
    }
}

さて、本題です。
ここでhttp://ドメイン/users/10にアクセスした時、ミドルウェアの最初の行で取得した$param変数は10なのか、それともUserモデルのオブジェクトなのか・・・?

実は、これだけじゃ判断できないんです。

ここからは経験則なので、ちゃんと調べたわけじゃないんですが、、、

コントローラーがどうなっているかによって$paramの値は変わります。

コントローラ

(暗黙の)モデル結合ルートを使う場合

UserController.php
class UserController extends Controller
{
    public function detail(Request $request, User $user){
        //ごにょごにょ・・・

        return view('pages.user.detail');
    }

コントローラーの引数にUser型をタイプヒントした$user変数がいた場合は、Larvelが自動的にid=10のUserモデルを注入してくれます。

この場合、ミドルウェアの$paramUserモデルのオブジェクトになります。
ポイントはルートパラメータ名{user}と変数名$userを合わせる事。

モデル結合ルートを使わない場合

UserController.php
class UserController extends Controller
{
    public function detail(Request $request){
        //ごにょごにょ・・・

        return view('pages.user.detail');
    }

この場合だと、ミドルウェアの$param10になります。

結果

UserOpenMiddleware.php
class UserOpenMiddleware
{
    public function handle($request, Closure $next)
    {
        $param = $request->route()->parameter('user');

        /**
         * $paramの値はどっち??
         */

        //コントローラーがモデル結合じゃない場合
        //$param = 10
        $userModel = User::findOrFail($param);

        //コントローラーがモデル結合の場合
        //Userモデルのオブジェクト
        $userModel = $param;

        //取得したモデルを使って色々ロジックを書く・・・・
        if($userModel->open){

        }

        return $next($request);
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelのファイルパーミッションの問題

問題発生!!

会社の仕事でアプリケーションの速度を改善するためにDBからマスターテーブルのModelを取得してCacheに格納する方法を利用しました。ウェブの速度はかなりよくなりCacheを使ってよかったと思いましたが、問題が発生!!OTZ

ユーザーの構成

  • バッチ実行ユーザー:ec2-user
  • ウェブ(Apache)ユーザー:apache
  • ec2-userのサブグループにapacheを入れている

作ったCacheを同じくバッチで使用するとパーミッションエラーが出ますね。読み込みは問題ないですが(?)、作成されるディレクトリがウェブで作成したものだと「755」で、バッチ実行ユーザーで新しくファイルを作れないエラーが発生しました。

解決

Laravel は Flysystem という PHPパッケージを使用しているらしいので、見てみましょう!
https://flysystem.thephpleague.com/docs/adapter/local/

$adapter = new Local(
    __DIR__.'/path/to/too',
    LOCK_EX,
    Local::DISALLOW_LINKS,
    [
        'file' => [
            'public' => 0744,
            'private' => 0700,
        ],
        'dir' => [
            'public' => 0755,
            'private' => 0700,
        ]
    ]
);

こうらしいです。
vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemManager.phpのcreateLocalDriverメソッドにpermissionsが突っ込まれるようなので「config/filesystems.php」を修正しました。

        'local' => [
            'driver' => 'local',
            'root' => storage_path('app'),
            'permissions' => [
                'file' => [
                    'public' => 0764,
                    'private' => 0700,
                ],
                'dir' => [
                    'public' => 0775,
                    'private' => 0700,
                ]
            ]
        ],

この内容はCacheの問題を解決する方法で書きましたが、Laravelがログとアップロードファイルは反映しますが、Cacheでできなかったです。残念!!!!!!
:relaxed::relaxed::relaxed::relaxed:

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

【jsなし】Laravel でチェックボックス未選択時にデフォルト値(false)を使う

checkboxは選択外した時にリクエストには含まれません。
調べたところhiddenにしjsで選択、非選択時に書き換えてそれを使うのを見ましたが、
Laravelならこれで出来そうっていうのがあったので、やってみます。

ミス、別の手法等がありましたら、ご指摘いただけますと幸いです。

view

まずはview側
普通に何個かチェックボックスがあるのみ

blade
<form action="{{route('test')}}" method="post">
    @csrf
    <input type="checkbox" value=1 name="c1">
    <input type="checkbox" value=1 name="c2">
    <input type="checkbox" value=1 name="c3">
</form>

Request

FormRequestを作成します。

php artisan make:request CheckRequest

作成したら、getValidatorInstanceをオーバーライドします。
送信されたものの値を優先するので、デフォルト側に上書きするようにmerge

CheckRequest.php
    protected function getValidatorInstance()
    {
        // デフォルト
        $default = [
            'c1' => 0,
            'c2' => 0,
            'c3' => 0,
        ];
        $data = array_merge($default,$this->all());

        $this->getInputSource()->replace($data);

        return parent::getValidatorInstance();
    }

dd($this->all(),$default,$data);
送信されたリクエスト、デフォルト、マージ後を出してみると下記のようになる。
c3は未選択なので送信されず、デフォルトのが使われています。
SC000010.JPG

あとは、バリデーションして登録するだけです。

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

git cloneした時に発生したエラー

はじめに

【個人メモ】

自宅で作成・学習したアプリ環境を、会社でもデモ実施環境として使う為に、git cloneした時のお話。

難しいことはせず、ローカルサーバーで動かしたいだけだったのに、 php artisan serve をして立ち上がらなかった。

解決方法

composer install をする。

原因・理由

php artisan serve をした際に読み込む、autoload.php がなかったから。
その保存場所は、vendorフォルダ直下。
そのフォルダは、.gitignore でgit管理外にしていたから、存在しなかったという理由。

では、どうしてgit管理外にしていたか。

vendorフォルダとは何か?

composer による依存パッケージが入っている所。

どうして管理外にしていたか?

compose.jsoncomposer.lock があれば落とす事が可能な為。

ちなみに、composer updatecomposer install の違いは下記の通り(で認識中)。

composer install

composer.lock に書かれているライブラリをインストールする。

composer update

composer.json をもとに最新版にアップデートする。

composer.lock は存在していたので、 composer install を実行。

おわりに

エンジニアになって1年、これがある程度さらっと頭に入るようになってきた。
それでもまだまだ知らない事が多い。
いまだに composer あたりは頭の中でふわっとしている。

関連機会が生じた時に、深掘りしていければな〜と思う。

参照記事

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

ゼロから始める PHP + Laravel テスト覚書

Step 1 - 出力

まずはここから。

item.php
<?php

function getId() {
    return '4395bc36da6dca494ed7';
}

getId() は、Qiita 当記事の ID を返す関数です。

この関数が想定通りに動くかどうか、

echo getId();

のようなコードを書いて確認していませんか?

$ php item.php
4395bc36da6dca494ed7

この出力に期待していた値は "4395bc36da6dca494ed7" です。見比べて確認することはできますが、なぜ目視で「間違い探し」を始めてしまうのか。ここは、サイゼリヤではないのです。

Step 2 - 例外

目視ではなくコードで比較して、例外を投げてみます。

if (getId() !== '4395bc36da6dca494ed7') {
    throw new Exception();
}

を実行すると、

$ php item.php

のように何も起こらず、正常な動作が確認できます。

一方、getId() の実装を誤り、

- return '4395bc36da6dca494ed7';
+ return '43956c36da6dca494ed7';

の状態で実行すると、

$ php item.php

Fatal error: Uncaught Exception in item.php:8

のように異常な動作が確認できます。ただし、「例外」と「誤り」は異なる概念です。

Step 3 - 表明

PHP には assert() が用意されているため、比較と例外のコードを、

assert(getId() === '4395bc36da6dca494ed7');

に置き換えて実行します。

$ php item.php

のように何も起こらず、正常な動作が確認できます。

getId() の実装を誤った場合は、

$ php item.php

Warning: assert(): assert(getId() === '4395bc36da6dca494ed7') failed in item.php on line 7

のように警告が確認できます。

Step 4 - 分割

ここまでのコードを「プロダクト」と「テスト」に分割して、下記の構成にします。

.
├── src
│   └── Item.php
└── tests
    └── ItemTest.php
src/Item.php
<?php

class Item
{
    public function getId() {
        return '4395bc36da6dca494ed7';
    }
}
tests/ItemTest.php
<?php

require_once __DIR__ . '/../src/Item.php';

$item = new Item();
assert($item->getId() === '4395bc36da6dca494ed7');

テストコードを実行すると、

$ php tests/ItemTest.php

これまでと同じ動作が確認できます。分割したことで、コードの責務が明確になりました。

Step 5 - 仕様

getId() の仕様を考えて、

  • 文字列を返す
  • 文字列は 16 進数表記
  • 文字列の長さは 20 文字

を想定したテストを書いてみます。

tests/ItemTest.php
<?php

require_once __DIR__ . '/../src/Item.php';

$item = new Item();
assert(is_string($item->getId()));
assert(ctype_xdigit($item->getId()));
assert(mb_strlen($item->getId()) == 20);

getId() は "4395bc36da6dca494ed7" を返すので、

$ php tests/ItemTest.php

問題なくテストは通ります。テストコードはバグを見つけるだけでなく、仕様を示す役割も担います。

Step 6 - PHPUnit

sebastianbergmann/phpunit

PHPUnit は、テスト用のフレームワークです。

$ composer require --dev phpunit/phpunit

でインストールします。

テストコードを PHPUnit を使って書き直すと、下記のようになります。

tests/ItemTest.php
<?php

require_once __DIR__ . '/../src/Item.php';

use PHPUnit\Framework\TestCase;

class ItemTest extends TestCase
{
    public function setUp(): void {
        $this->item = new Item();
    }

    public function testGetId_string() {
        $this->assertIsString($this->item->getId());
    }

    public function testGetId_hexadecimal() {
        $this->assertStringMatchesFormat('%x', $this->item->getId());
    }

    public function testGetId_length20() {
        $this->assertEquals(20, mb_strlen($this->item->getId()));
    }
}

PHPUnit を実行すると、

$ ./vendor/bin/phpunit tests
PHPUnit 8.4.2 by Sebastian Bergmann and contributors.

...                                                                 3 / 3 (100%)

Time: 149 ms, Memory: 4.00 MB

OK (3 tests, 3 assertions)

のようにテスト結果が確認できます。

test* の関数がテスト項目で、setUp() は各テストの前に実行されます。比較には PHPUnit のアサーションを使用します。

1. Assertions — PHPUnit latest Manual

Step 7 - Mockery

mockery/mockery

Mockery は、モック用のフレームワークです。

$ composer require --dev mockery/mockery

でインストールします。

ここでは Item クラスに、

public $user = null;

public function __get($name) {
    return $name === 'author' ? $this->user->getName() : null;
}

というコードを追加して、ユーザオブジェクトとの連携を考えてみます。

「author プロパティは "Name" を返す」というテストコードは、

public function testGetAuthor() {
    $this->assertEquals('Name', $this->item->author);
}

のようになりますが、$user が null のためテストできません。

このテストコードに Mockery を使うと、

public function testGetAuthor() {
    $mock = Mockery::mock('User');
    $mock->shouldReceive('getName')->andReturn('Name')->once();
    $this->item->user = $mock;

    $this->assertEquals('Name', $this->item->author);
}

public function tearDown(): void {
    Mockery::close();
}

のように書いてテストができます。

Step 8 - Faker

fzaninotto/Faker

Faker は、ダミーデータを生成するライブラリです。

$ composer require --dev fzaninotto/faker

でインストールします。

Faker でユーザ名を置き換えると、

public function testGetAuthor() {
    $faker = Faker\Factory::create();
    $name = $faker->name;

    $mock = Mockery::mock('User');
    $mock->shouldReceive('getName')->andReturn($name)->once();
    $this->item->user = $mock;

    $this->assertEquals($name, $this->item->author);
}

のように書いて、ランダムなユーザ名でテストができます。

日本の個人情報を生成したければ、

$faker = Faker\Factory::create('ja_JP');
$faker->name;           // 名前
$faker->address;        // 住所
$faker->phoneNumber;    // 電話番号
$faker->email;          // メールアドレス

のように書けます。

Step 9 - phpunit-watcher

spatie/phpunit-watcher

phpunit-watcher は、コード変更時に PHPUnit を実行するツールです。

$ composer require --dev spatie/phpunit-watcher

でインストールします。

$ ./vendor/bin/phpunit-watcher watch tests

で監視を始めると、コードを変更するたびに PHPUnit の実行結果を確認できます。

Step 10 - php-code-coverage

sebastianbergmann/php-code-coverage

php-code-coverage は、カバレッジを計測するライブラリです。

phpdbg を使うと、

$ phpdbg -qrr ./vendor/bin/phpunit tests --whitelist src --coverage-text

Code Coverage Report:
  2019-11-10 23:59:59

 Summary:
  Classes: 100.00% (1/1)
  Methods: 100.00% (2/2)
  Lines:   100.00% (2/2)

Item
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  2/  2)

のようにカバレッジを確認できます。

HTML 形式で確認したければ、

$ phpdbg -qrr ./vendor/bin/phpunit tests --whitelist src --coverage-html coverage

で coverage ディレクトリに出力できます。

Step 11 - php-webdriver

facebook/php-webdriver

php-webdriver は、Selenium を操作するライブラリです。

$ composer require --dev facebook/webdriver

でインストールします。

Selenium は、selenium/standalone-chrome の Docker イメージを使えば、

$ docker run -p 4444:4444 selenium/standalone-chrome

で準備ができます。

ここでは Qiita のタイトルを確認するテストを書いてみます。

tests/BrowserTest.php
<?php

use PHPUnit\Framework\TestCase;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;

class BrowserTest extends TestCase
{
    public function testQiita() {
        $driver = RemoteWebDriver::create('http://localhost:4444/wd/hub', DesiredCapabilities::chrome());

        $driver->get('https://qiita.com/');
        $this->assertEquals('Qiita', $driver->getTitle());

        $driver->quit();
    }
}
$ ./vendor/bin/phpunit tests/BrowserTest.php

でブラウザ経由のテストができます。

Step 12 - Laravel

laravel/laravel

ここからは、Laravel アプリケーションのテストを取り扱います。

$ composer create-project --prefer-dist laravel/laravel example

で作成したプロジェクトには、tests ディレクトリが含まれており、

  • tests/Unit/ExampleTest.php
  • tests/Feature/ExampleTest.php

がサンプルのテストコードです。

PHPUnit を使っているため、

$ ./vendor/bin/phpunit

でテストができます。設定は phpunit.xml を参照してください。

Laravel x PHPUnit

サンプルのテストコードは、tests/TestCase.php を継承しています。

tests/TestCase.php
<?php

namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication;
}

use CreatesApplicationtests/CreatesApplication.php を利用して、テスト対象の Laravel アプリケーションを立ち上げます。

Illuminate\Foundation\Testing\TestCase を覗くと、PHPUnit\Framework\TestCase のサブクラスであることが確認できます。

Step 13 - Laravel (HTTP)

Laravel は、HTTP テストをサポートします。

tests/Feature/ExampleTest.php では、

public function testBasicTest()
{
    $response = $this->get('/');

    $response->assertStatus(200);
}

と書かれており、/ のレスポンスを確認しています。

HTTP リクエスト

$response = $this->get('/');

HTTP リクエストは、Illuminate\Foundation\Testing\Concerns\MakesHttpRequests で実装されており、基本的な HTTP メソッドが揃っています。

また、Illuminate\Foundation\Testing\Concerns\InteractsWithSession でサポートされているため、

$response = $this->withSession(['key' => 'value'])->get('/');

のようにセッションを設定することもできます。

HTTP レスポンス アサーション

$response->assertStatus(200);

Response Assertions

HTTP リクエストは、Illuminate\Foundation\Testing\TestResponse を返します。このオブジェクトには HTTP レスポンス用のアサーションが実装されているため、$response から各アサーションを利用します。

assertStatus() の実装は、

public function assertStatus($status)
{
    $actual = $this->getStatusCode();

    PHPUnit::assertTrue(
        $actual === $status,
        "Expected status code {$status} but received {$actual}."
    );

    return $this;
}

となっており、各アサーションは PHPUnit のアサーションのラッパーです。

Step 14 - Laravel (Database)

Laravel は、データベーステストをサポートします。

phpunit.xml に、

<server name="APP_ENV" value="testing"/>

が設定されているため、テスト時のデータベースは .env.testing で指定します。

ここでは、

$ cp .env .env.testing

.env.testing を用意して、DB_CONNECTION=sqlite に書き換えて、

$ touch database/database.sqlite
$ php artisan migrate --env=testing

でテスト用のデータベースを準備します。

準備が整えば、

public function testBasicTest()
{
    $user = factory(\App\User::class)->create();

    $this->assertDatabaseHas('users', ['email' => $user['email']]);
}

のように書いて、データベースへの保存をテストできます。

Eloquent ファクトリ

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

Laravel には、テストデータを生成する factory() が用意されており、ここでは database/factories/UserFactory.php を基にしています。ファイルを覗くと、Faker を使っていることが確認できます。

ファクトリの雛型は、

$ php artisan make:factory ExampleFactory

で作成できます。

データベース アサーション

$this->assertDatabaseHas('users', ['email' => $user['email']]);

Database Assertions

Tests\TestCase には、Illuminate\Foundation\Testing\Concerns\InteractsWithDatabase でデータベース用のアサーションが追加されているため、$this から各アサーションを利用します。

テスティング トレイト

テストを繰り返すとデータが蓄積してしまいますが、サンプルのテストコードで定義されている、

use Illuminate\Foundation\Testing\RefreshDatabase;

class ExampleTest extends TestCase
{
    use RefreshDatabase;

のように適用すれば、PHPUnit の setUp() 時にデータベースを初期化できます。

Illuminate\Foundation\Testing\RefreshDatabase を覗くと、トレイトで実装されていることが確認できます。その他、

のトレイトが Tests\TestCase に適用できます。

Step 15 - Laravel (Authentication)

Laravel は、認証テストをサポートします。

Laravel 6 から artisan make:auth が無くなったため、

$ composer require --dev laravel/ui
$ php artisan ui vue --auth

で認証機能を準備します。

準備が整うと /home に認証が求められるため、

public function testBasicTest()
{
    $response = $this->get('/home');

    $response->assertStatus(200);
}

のようなテストは失敗します。

認証済みの状態でテストしたければ、

public function testBasicTest()
{
    $user = factory(\App\User::class)->make();
    $response = $this->actingAs($user)->get('/home');

    $response->assertStatus(200);
}

のように書きます。

認証 アサーション

$this->assertAuthenticated();

Authentication Assertions

Tests\TestCase には、Illuminate\Foundation\Testing\Concerns\InteractsWithAuthentication で認証用のアサーションが追加されているため、$this から各アサーションを利用します。

Step 16 - Laravel (Mock)

Laravel は、モックをサポートします。

サービス

app/Services/UserServices.php
<?php

namespace App\Services;

use App\User;

class UserService
{
    public function getUsers() {
        return User::all();
    }
}

というサービスの getUsers() は、

public function testBasicTest()
{
    $this->mock(\App\Services\UserService::class, function($mock) {
        $user = factory(\App\User::class)->make();
        $mock->shouldReceive('getUsers')->andReturn([$user])->once();
    });

    $userService = app(\App\Services\UserService::class);

    $this->assertCount(1, $userService->getUsers());
}

のように、mock() で置き換えられます。

Illuminate\Foundation\Testing\Concerns\InteractsWithContainer を覗くと、Mockery を使っていることが確認できます。

ファサード

ファサードのモック機能を使うと、

public function testBasicTest()
{
    Cache::shouldReceive('get')->with('key')->andReturn('value');

    $this->assertEquals('value', Cache::get('key'));
}

のように書けます。

Illuminate\Support\Facades\Facade は、::shouldReceive() で Mockery オブジェクトを返します。

フェイク

特定のファサードには fake() が用意されているため、

public function testBasicTest()
{
    Storage::fake('s3');

    $file = UploadedFile::fake()->image('image.png');
    $file->storeAs('images', $file->name, 's3');

    Storage::disk('s3')->assertExists('images/image.png');
}

のように書けます。

Step 17 - Laravel Dusk

laravel/dusk

Laravel Dusk は、E2E テスト用のフレームワークです。

$ composer require --dev laravel/dusk
$ php artisan dusk:install

でインストールします。

  • tests/DuskTestCase.php
  • tests/Browser

が追加され、tests/Browser/ExampleTest.php がサンプルのテストコードです。

public function testBasicExample()
{
    $this->browse(function (Browser $browser) {
        $browser->visit('/')
                ->assertSee('Laravel');
    });
}

Browser Assertions

Laravel\Dusk\BrowserLaravel\Dusk\Concerns\MakesAssertions でブラウザ用のアサーションが追加されているため、$browser から各アサーションを利用します。

$ php artisan dusk

ChromeDriver 経由のテストができます。

Selenium

tests/DuskTestCase.php は、php-webdriver を使っているため、

public static function prepare()
{
    //static::startChromeDriver();
}

protected function driver()
{
    return RemoteWebDriver::create('http://localhost:4444/wd/hub', DesiredCapabilities::chrome());
}

と書けば、Selenium に置き換えることができます。

Step Ex - Kahlan

kahlan/kahlan

Kahlan は、BDD テスト用のフレームワークです。

$ composer require --dev kahlan/kahlan

でインストールします。

Kahlan を使うと、テストを describe-it 構文で書けます。

describe('Example', function() {
    it('passes if true === true', function() {
        expect(true)->toBe(true);
    });
});

Laravel への導入方法は、「Laravel のテストを describe-it でいい感じに書く」を参考にしてください。

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