- 投稿日: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 でいい感じに書く」を参考にしてください。


