- 投稿日:2019-11-12T23:57:56+09:00
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を導入します。
- 投稿日:2019-11-12T22:58:57+09:00
【サルが書く】Laravelのpassportでの認証するテーブルをカスタマイズ方法
わきゃ〜〜
LaravelのPassportを使い、OAuth2.0のユーザー認証を行いました。
そこで、標準のemail,passwordのみの認証ではない認証方法を取りたかったので、ちょっと調べてみました。実行環境
Laravel 5.5
Laravel Passport 4.0
PHP 7.xLaravel Passportは導入した状態でのお話になります。
認証するカラムを変更したい
といっても、かなり簡単にカスタマイズは簡単です。
認証するテーブルのModelに下記のコードを追加するだけです。usernameを認証するカラムを変更する場合
Model/User.phppublic function findForPassport($username) { // 認証カラム名が name の場合 return $this->where('name', $username)->first(); }これは実際に認証を行っている、Laravel Passportの
Laravel/Passport/Bridge/UserRepository.php
にあるgetUserEntityByUserCredentials()
というメソッドがあります。
そのメソッドにModelがfindForPassportというメソッドを持っていれば、メソッドを上書きするような処理があるためです。認証する条件を変更する
whereをつなげて認証する条件を追加することもできます。
whereで表現できるものであれば、カスタマイズ可能だと思います。Model/User.phppublic function findForPassport($username) { // nameとrole_idの2つの条件で認証を行う return $this->where('name', $username)->where('role_id', $role_id)->first(); }参考文献
- 投稿日:2019-11-12T17:35:20+09:00
Laravel Debugbarで簡易速度計測
Laravelをお使いの皆さんにはおなじみLaravel Debugabrですが簡易的な速度計測ができることをご存知でしょうか。
xhprofやblackfireを使えというマサカリは受け付けません
明らかに処理が遅そうだが確証がないな〜、簡易的でいいから計測したいな〜というときに便利です。start_measure('このへん重そう'); // ここに怪しい処理がある stop_measure('このへん重そう');ヘルパー関数が用意されているのでこんな感じで怪しい処理に
start_measure
とstop_measure
を挟みましょう。
始まりと終わりは同じ文字列を入れましょう。いつものDebugbarのTimelineで以下のように見ることができます。
(View数,Query数はひみつ)
はい、全然違いましたね。start_measure('このへん重そう'); // ここに怪しい処理がある stop_measure('このへん重そう'); start_measure('じゃあこっちか?'); // あんまり怪しくないと思ってた処理 stop_measure('じゃあこっちか?');無事遅い箇所が特定できました。
他にもログを出す機能も付いてるので活用してみてください。
おしまい
- 投稿日:2019-11-12T17:27:23+09:00
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のキャッシュクリアでおkphp 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
- 投稿日:2019-11-12T16:25:47+09:00
【Laravel】自作ミドルウェア内で取得するルートパラメータはID?それともモデル?
自作ミドルウェアの中で、ルートパラメータを取得してEloquentで
find
して・・・、みたいな事をしていた時に気づいた事を書いておきます。環境
- php7.3
- laravel5.8
補足
- ミドルウェアは
Kernel.php
の$routeMiddleware
に追加する- 結果は経験則なので理由を調べたわけではない(すみません)
ルーティング
ルーティングでミドルウェアを適用します。
web.phpRoute::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.phpclass 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.phpclass UserController extends Controller { public function detail(Request $request, User $user){ //ごにょごにょ・・・ return view('pages.user.detail'); }コントローラーの引数に
User
型をタイプヒントした$user
変数がいた場合は、Larvelが自動的にid=10のUser
モデルを注入してくれます。この場合、ミドルウェアの
$param
もUser
モデルのオブジェクトになります。
ポイントはルートパラメータ名{user}
と変数名$user
を合わせる事。モデル結合ルートを使わない場合
UserController.phpclass UserController extends Controller { public function detail(Request $request){ //ごにょごにょ・・・ return view('pages.user.detail'); }この場合だと、ミドルウェアの
$param
は10
になります。結果
UserOpenMiddleware.phpclass 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); } }
- 投稿日:2019-11-12T16:18:44+09:00
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でできなかったです。残念!!!!!!
- 投稿日:2019-11-12T12:31:40+09:00
【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
をオーバーライドします。
送信されたものの値を優先するので、デフォルト側に上書きするようにmergeCheckRequest.phpprotected 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
は未選択なので送信されず、デフォルトのが使われています。
あとは、バリデーションして登録するだけです。
- 投稿日:2019-11-12T07:14:56+09:00
git cloneした時に発生したエラー
はじめに
【個人メモ】
自宅で作成・学習したアプリ環境を、会社でもデモ実施環境として使う為に、git cloneした時のお話。
難しいことはせず、ローカルサーバーで動かしたいだけだったのに、
php artisan serve
をして立ち上がらなかった。解決方法
composer install
をする。原因・理由
php artisan serve
をした際に読み込む、autoload.php
がなかったから。
その保存場所は、vendorフォルダ直下。
そのフォルダは、.gitignore
でgit管理外にしていたから、存在しなかったという理由。では、どうしてgit管理外にしていたか。
vendorフォルダとは何か?
composer による依存パッケージが入っている所。
どうして管理外にしていたか?
compose.json
やcomposer.lock
があれば落とす事が可能な為。ちなみに、
composer update
とcomposer install
の違いは下記の通り(で認識中)。composer install
composer.lock
に書かれているライブラリをインストールする。composer update
composer.json
をもとに最新版にアップデートする。
composer.lock
は存在していたので、composer install
を実行。おわりに
エンジニアになって1年、これがある程度さらっと頭に入るようになってきた。
それでもまだまだ知らない事が多い。
いまだに composer あたりは頭の中でふわっとしている。関連機会が生じた時に、深掘りしていければな〜と思う。
参照記事
- 投稿日:2019-11-12T01:18:56+09:00
ゼロから始める 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.phpsrc/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
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 は、モック用のフレームワークです。
$ 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
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
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
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 アプリケーションのテストを取り扱います。
$ 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 CreatesApplication
はtests/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);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']]);
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 を覗くと、トレイトで実装されていることが確認できます。その他、
- Illuminate\Foundation\Testing\DatabaseMigrations
- Illuminate\Foundation\Testing\DatabaseTransactions
- Illuminate\Foundation\Testing\WithoutMiddleware
- Illuminate\Foundation\Testing\WithoutEvents
- Illuminate\Foundation\Testing\WithFaker
のトレイトが
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();
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 は、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'); }); }
Laravel\Dusk\Browser
に Laravel\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 は、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 でいい感じに書く」を参考にしてください。