- 投稿日:2019-11-24T21:43:11+09:00
【テストおじさんへの道】テストをやると幸せになれる
what this is?
これは僕がテストおじさんになるまでの軌跡を書いた記事です。
この記事を社内で共有してテストの文化を浸透させるために書いています。
執筆当時はまだ「テスト赤ちゃん」です。優しく指摘してください。テストをなぜ行う?
答えは簡単です。
「人間は愚かで、プログラムを操ることは難しいから」
です。最近では「誰でもプログラマーになれる!」なんて、さもプログラマーの敷居が低いかのような言葉がインターネッツで飛び交っていますが
甘いです。
プログラムは簡単に壊れます。そして人に牙をむきます。
そんな事が当たり前の職業でどうやってプログラムを正常にコントロールするのか…
それが「テスト」です。テストを書かないとどうなるのか
いくつかあります。
・品質が保証されない
・デバッグのコストが莫大になる
・改修のコストとリスクが莫大になる
・引き継ぎがのリスクが高くなる
などなど…僕が一番推したいのは
「不安で寝れない」
ということです。
いつ、どこで、どんなときにバグが起こるか、書いた本人ですらわからないのです。
そんな不安をテストは解消してくれます。テストを書くとどうなるのか
もうメリットがたくさんです。
・品質が保証されているので、安心して運用出来る。
・デバッグのコストが一気に下がる。手動でのフォームテストなどをしなくてもよくなる。
・改修が簡単になる。どこに副作用があるのか簡単に把握出来る。
・引き継ぎが楽になる。とりあえずテストを読めば使用がふんわりわかる。様々な恩恵を受けることができますが、一つだけデメリットがあります。
それは
「開発コストが上がる」
ということです。
当然今までの開発にプラスしてテストも書くわけですから、仕方ありません。
ですがメリットを考えるとプラマイゼロ、むしろメリットのほうが大きいと思います。テストをするにはどうする?
僕は主にphpを使うので、テストではphpunitを利用します。
他の言語ではjsunitやrubyunitなど、○○unitでググればなんか出ると思います。そんなphpunitですが、みんな使っているLaravelに基本搭載されています。
vendor\phpunit\phpunit\phpunit
にバイナリデータがあります。コレが正体です。
ですが、結構奥のディレクトリに眠っているので
普段はvendor\phpunit\bin\phpunit
というシンボリックリンクがあるので、それを叩きます。コマンドで
sudo php ./vendor/bin/phpunit
と打つとPHPUnit 8.4.2 by Sebastian Bergmann and contributors.と表示されてテストが走ります。
テストを書くにはどうする?
テストはLaravelの
tests\Feature
の中に置きます。
デフォルトで入っているExampleTest.php
には以下のように書かれています。<?php namespace Tests\Feature; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; class ExampleTest extends TestCase { /** * A basic test example. * * @return void */ public function testBasicTest() { $response = $this->get('/'); $response->assertStatus(200); } }基本的に書き方としては
・test○○○というpublic functionを書く
・$this->get('○○○')でレスポンスを得る
・aeert○○○()でテストする
です。test○○○というpublic functionを書く
基本的にクラスの上から下へ実行されます。
関数名はtestというプレフィックスを付けなければ自動で実行されません。$this->get('○○○')でレスポンスを得る
getの他にもpostやputなどがあります。
aeert○○○()でテストする
assert○○○()をアサーションといいます。
assertStatus()やassertSee()などたくさんあります。
このアサーションを組み合わせてプログラムをテストします。アサーションには、phpunitが用意しているものと
https://phpunit.readthedocs.io/ja/latest/assertions.htmlLaravelのヘルパとして用意されているものがあります。
https://readouble.com/laravel/5.7/ja/http-tests.htmlまずは書いてみよう
簡単なものを書いてみて動きを自分で確認するのがテストへの第一歩だと思います。
とりあえず触ってみましょう、テストの素晴らしさがわかると思います。
- 投稿日:2019-11-24T20:29:50+09:00
Laravelの処理の流れコードリーディング(投げたエラーはどこで処理されている???)
概要
Laravelではthrowしたエラーを明示的にcatchしなくてもよしなにしてくれます。
エラーをカスタムしたいなと思ったときにどこでエラーをキャッチしているのか知りたかったのでLaravelの流れを読むことにしました。
start
public/index.php$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); $response = $kernel->handle( $request = Illuminate\Http\Request::capture() );
Kernel::handle()
のtry句の中身を見ていきます。vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php$request->enableHttpMethodParameterOverride(); $response = $this->sendRequestThroughRouter($request);
enableHttpMethodParameterOverride()
はプロパティをtrueにセットしているだけなのでスルーします。
sendRequestThroughRouter()
は同じクラスに実装されています。vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.phpprotected function sendRequestThroughRouter($request) { $this->app->instance('request', $request); Facade::clearResolvedInstance('request'); $this->bootstrap(); return (new Pipeline($this->app)) ->send($request) ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) ->then($this->dispatchToRouter()); }
$this->app->instance('request', $request)
では$request
をサービスコンテナに登録しています。
Facade::clearResolvedInstance('request')
はファサードにキャッシュされているrequestをクリアしています。(キャッシュされるタイミングはあるのだろうか?)
$this->bootstrap()
でいろいろ準備をしてるみたいです。vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.phpprotected $bootstrappers = [ \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class, \Illuminate\Foundation\Bootstrap\LoadConfiguration::class, \Illuminate\Foundation\Bootstrap\HandleExceptions::class, \Illuminate\Foundation\Bootstrap\RegisterFacades::class, \Illuminate\Foundation\Bootstrap\RegisterProviders::class, \Illuminate\Foundation\Bootstrap\BootProviders::class, ]; public function bootstrap() { if (! $this->app->hasBeenBootstrapped()) { $this->app->bootstrapWith($this->bootstrappers()); } }
$bootstrappers
プロパティの配列をbootstrapWith()
の引数にぶちこんでいきます。vendor/laravel/framework/src/Illuminate/Foundation/Application.phppublic function bootstrapWith(array $bootstrappers) { foreach ($bootstrappers as $bootstrapper) { $this->make($bootstrapper)->bootstrap($this); } }サービスコンテナで順番にインスタンス化していって、その中の
bootstrap()
を呼んでいます。
bootstrap()
が終わったら、sendRequestThroughRouter()
に戻ります。vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.phpprotected function sendRequestThroughRouter($request) { $this->app->instance('request', $request); Facade::clearResolvedInstance('request'); $this->bootstrap(); return (new Pipeline($this->app)) ->send($request) ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) ->then($this->dispatchToRouter()); }ここでreturnしているので、
Pipeline
クラスがresponseを生成しているみたいですね。vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.phppublic function send($passable) { $this->passable = $passable; return $this; } public function through($pipes) { $this->pipes = is_array($pipes) ? $pipes : func_get_args(); return $this; }
send()
はrequestをプロパティにセットして、through()
はmiddlewareをプロパティにセットしています。
Pipeline
のthen()
はmiddlewareを適用してrouterにdispatchを行います。(then()
については複雑なのでこちらの記事を読んでください。LaravelのPipeline::thenでmiddlewareを処理している流れをまとめてみた)
return $pipeline($this->passable)
は最終的にprepareDestination()
を呼んで、引数のClosureを呼び出しています。vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.phpprotected function prepareDestination(Closure $destination) { return function ($passable) use ($destination) { try { // ここが呼び出される return $destination($passable); } catch (Exception $e) { return $this->handleException($passable, $e); } catch (Throwable $e) { return $this->handleException($passable, new FatalThrowableError($e)); } }; }
$destination
はthen()
の引数に渡されたものなので、$this->dispatchToRouter()
で生成したClosureが呼び出されます。\Illuminate\Foundation\Http\Kernel.phpprotected function dispatchToRouter() { return function ($request) { $this->app->instance('request', $request); return $this->router->dispatch($request); }; }routerの
dispatch()
を呼び出します。vendor/laravel/framework/src/Illuminate/Routing/Router.phppublic function dispatch(Request $request) { $this->currentRequest = $request; return $this->dispatchToRoute($request); } public function dispatchToRoute(Request $request) { return $this->runRoute($request, $this->findRoute($request)); } protected function runRoute(Request $request, Route $route) { //... return $this->prepareResponse($request, $this->runRouteWithinStack($route, $request) ); }
$this->findRoute()
でリクエストされたルートやメソッド(get, postなど)で対象のルーティングを見つけてきます。
runRoute()
でレスポンスを返却しています。vendor/laravel/framework/src/Illuminate/Routing/Router.phpprotected function runRouteWithinStack(Route $route, Request $request) { $shouldSkipMiddleware = $this->container->bound('middleware.disable') && $this->container->make('middleware.disable') === true; $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route); return (new Pipeline($this->container)) ->send($request) ->through($middleware) ->then(function ($request) use ($route) { return $this->prepareResponse( $request, $route->run() ); }); }Pipelineでmiddlewareを適用しつつ、
$route->run()
でいよいよControllerに処理を委譲していきます。vendor/laravel/framework/src/Illuminate/Routing/Route.phppublic function run() { $this->container = $this->container ?: new Container; try { if ($this->isControllerAction()) { return $this->runController(); } return $this->runCallable(); } catch (HttpResponseException $e) { return $e->getResponse(); } } protected function runController() { return $this->controllerDispatcher()->dispatch( $this, $this->getController(), $this->getControllerMethod() ); }vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.phppublic function dispatch(Route $route, $controller, $method) { $parameters = $this->resolveClassMethodDependencies( $route->parametersWithoutNulls(), $controller, $method ); if (method_exists($controller, 'callAction')) { return $controller->callAction($method, $parameters); } return $controller->{$method}(...array_values($parameters)); }ここまできてやっと
$controller->{$method}
とコントローラーのメソッドが呼ばれました!コントローラーで投げられた例外は、
HttpResponseException
クラスなら、Route::run()
ないでcatchされレスポンスに変換されています。vendor/laravel/framework/src/Illuminate/Routing/Route.phppublic function run() { try { if ($this->isControllerAction()) { return $this->runController(); } } catch (HttpResponseException $e) { return $e->getResponse(); } }それ以外の例外は、
Pipeline::prepareDestination()
でキャッチされ、handleException()
が呼ばれます。vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.phpprotected function prepareDestination(Closure $destination) { return function ($passable) use ($destination) { try { return $destination($passable); } catch (Exception $e) { return $this->handleException($passable, $e); } catch (Throwable $e) { return $this->handleException($passable, new FatalThrowableError($e)); } }; }vendor/laravel/framework/src/Illuminate/Routing/Pipeline.phpprotected function handleException($passable, Exception $e) { if (! $this->container->bound(ExceptionHandler::class) || ! $passable instanceof Request) { throw $e; } $handler = $this->container->make(ExceptionHandler::class); $handler->report($e); $response = $handler->render($passable, $e); if (method_exists($response, 'withException')) { $response->withException($e); } return $response; }
- 投稿日:2019-11-24T19:57:05+09:00
LaravelでSNSの雛形デザイン作成してみた(user_tableまで)
今回SNSの雛形デザインを作成してみましたので、オープンソースとして共有いたします。
目的
・汎用的データ作成
|- デザイン
|- ソース実装した機能
|- user側のテーブルの編集機能
作成物
環境構築
Mac High sierra
Laravel 5.8
VirtialBox+Vagrant+Homestead環境※こちらはチートシートにまとめてありますので
このURLを参照してください。
|- Laravel環境構築 チートシートチートシートの「vagrant ssh」まで完了すれば大丈夫です。
1.データクローン
まずどこでもいいので下記コマンドをターミナルに入力してデータをクローンしてください
ターミナル$ git clone https://dai570415@bitbucket.org/dai570415/laravel-sns-master.git
2.クローンデータを解凍して環境に入れる
先ほど環境構築した際に「code」フォルダを作成しているかと思いますので、その中にクローンしてきたファイル(中身のみ)をごそっと入れてください。
下記アドレスにアクセス
192.168.10.10Topページが表示されるはずです。
3.DB関係を設定する
マイグレート
ターミナル$ php artisan migrate * こちらでDB周りが構築できました最後に
汎用性を重視した雛形作成を目的にしているので
投稿側はあえて実装していません。以下URLから実装してください。
- 投稿日:2019-11-24T19:47:14+09:00
Laravelでクロージャを使った自作バリデーションの作成
Laravelのバリデーションでチェックボックスの特定の組み合わせを不可にするために、
自作バリデーションを作成してみました。
自作バリデーションはRulesを作る方法がありますが、
調べた所、バリデーションでもクロージャが使えるようでした。簡単なサンプルを作ってみました。
まずフォームを作ります。<h1>果物を選んで下さい。</h1> @if ($errors->any()) <ul> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> @endif <form action="{{ route('check') }}" method="POST"> @csrf <input type="checkbox" name="fruits[]" value="apple">りんご <input type="checkbox" name="fruits[]" value="orange">みかん <input type="checkbox" name="fruits[]" value="banana">バナナ <input type="submit"> </form>りんごとバナナに同時にチェックがある時にエラーにするために、
コントローラーに以下のバリデーションを追加します。
fruitsのバリデーションの1つとしてクロージャを組み込んでます。
$attributeにフォーム名、$valueにフォームの値が入り、
$failにエラーメッセージ入れて返すという形になります。public function check(Request $request) { $request->validate([ 'fruits' => [ 'required', function ($attribute, $value, $fail) { if (in_array('apple', $value) && in_array('banana', $value)) { return $fail('りんごとバナナは同時に選べません。'); } } ], ]); return view('input'); }少し可読性は悪いですが、別クラスを作らなくてすむのは便利です。
- 投稿日:2019-11-24T19:40:49+09:00
Twigで拡張子が2つある理由
Twigのファイル名は、「index.html.twig」と「html」+「twig」と拡張子が2つついています。
これは「twig」というのが、Twigのテンプレートファイルということを示し、「html」は、twigファイルをレンダリングして生成されるものが、「HTML」ファイルであることを示している。
これは、Template Namingに記載されていました。
- 投稿日:2019-11-24T16:51:40+09:00
Laravelで日付をYYYY年MM月DD日 (曜日) hh: mmと表示させる
日付と時間だけだと簡単ですが、間に曜日を挟んだ場合の情報が少なくて面倒だったので、メモ
LaravelにCarbonのv2以降をインストールすれば以下のように1行でかけます。
今回は参考にdirectiveを使ってますが、viewHelperとかの方がいいかも。AppServiceProvider.php<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function boot() { \Blade::directive('datetime_and_week', function ($expression) { return "<?php echo Carbon\Carbon::parse($expression)->isoFormat('YYYY年MM月DD日 (ddd) LT '); ?>"; }); } }directiveなのでView clearコマンドを実行後に
viewなどで下記のように呼び出せばOKSample.blade.php<?php @datetime_and_week('2019-11-24 00:00:00')下記のように表示されます!
2019年11月24日 (日) 00:00他に良い方法あれば教えてください。
- 投稿日:2019-11-24T16:41:30+09:00
Laravelのファサードについて簡単にまとめる
概要
Laravelの代表的な機能としてファサードがある。これを使うと、staticなメソッドを呼ぶのと同じ要領で、どこからでも簡単にフレームワークの機能を呼び出すことができる。
use Illuminate\Support\Facades\Cache; Route::get('/cache', function () { return Cache::get('key'); });下の例のように、useで名前空間を指定せずとも、グローバル名前空間でアクセスするように書くことも可能。(ちなみにこれが可能なのは
config/app.php
のaliases
に記述されている対応関係をclass_alias
関数によって結びつけているから。要はエイリアス(別名)をつけて省略的に書けるようにしていると思っておけば良いと思う。たぶん。)Route::get('/config', function () { return \Config::get('key'); });これを一見すると、
Illuminate\Support\Facades\Config
クラスに実装されたget
メソッドを呼んでいるように見えるが、実はget
メソッドはIlluminate\Support\Facades\Config
クラスにも、継承元のIlluminate\Support\Facades\Facade
クラスにも存在していない。実際には、getFacadeAccessor()
が実装されているのみである。Illuminate\Support\Facades\Config.php<?php namespace Illuminate\Support\Facades; class Config extends Facade { /** * Get the registered name of the component. * * @return string */ protected static function getFacadeAccessor() { return 'config'; } }この時何が起こっているか、簡単に辿ってみる。
マジックメソッドと__callStatic()
Illuminate\Support\Facades\Config
クラスはget()
を持っていなかった。
実装されていないstaticなメソッドを呼ぼうとするとどうなるかというと、PHPでは__callStatic()
というマジックメソッドが呼ばれることになる。public static __callStatic ( string $name , array $arguments ) : mixed__callStatic() は、 アクセス不能メソッドを静的コンテキストで実行したときに起動します。
引数$name
は、 コールしようとしたメソッドの名前です。 引数$arguments
は配列で、メソッド $name に渡そうとしたパラメータが格納されます。マジックメソッドはPHPにおける特殊な関数であり、開発者が直接指定して呼び出すことは基本的になく、特定のタイミングで勝手に呼び出される。代表的なものが馴染み深い
__construct()
であり、新たにインスタンスが生成されるタイミングで勝手に呼び出されるマジックメソッドといえる。中身
継承元の
Illuminate\Support\Facades\Facade
クラスには、__callStatic()
が実装されている。(コメントなどは省略)Illuminate\Support\Facades\Facade.php<?php namespace Illuminate\Support\Facades; ... abstract class Facade { ... public static function getFacadeRoot() { return static::resolveFacadeInstance(static::getFacadeAccessor()); } protected static function resolveFacadeInstance($name) { if (is_object($name)) { return $name; } if (isset(static::$resolvedInstance[$name])) { return static::$resolvedInstance[$name]; } if (static::$app) { return static::$resolvedInstance[$name] = static::$app[$name]; } } public static function __callStatic($method, $args) { $instance = static::getFacadeRoot(); if (! $instance) { throw new RuntimeException('A facade root has not been set.'); } return $instance->$method(...$args); } }上記のPHPマニュアルからの引用の通り、
__callStatic()
の引数$method
には呼び出そうとしたメソッドの名前、$args
にはその時に使用した引数が配列の形式で入る。
getFacadeRoot()
でサービスコンテナ($app
)から対象となるインスタンスを取得し、そのインスタンスのメソッドを引数の$method
によって実行する。つまり、staticなメソッドを使用しているように見えて、実際には実体となるインスタンスがサービスコンテナから取り出され、指定したメソッドが実行される、という流れになっていることがわかる。
- 投稿日:2019-11-24T16:41:30+09:00
ファサードの内部における動作を簡単にまとめる【Laravel】
概要
Laravelの代表的な機能としてファサードがある。これを使うと、staticなメソッドを呼ぶのと同じ要領で、どこからでも簡単にフレームワークの機能を呼び出すことができる。
use Illuminate\Support\Facades\Cache; Route::get('/cache', function () { return Cache::get('key'); });下の例のように、useで名前空間を指定せずとも、グローバル名前空間でアクセスするように書くことも可能。(ちなみにこれが可能なのは
config/app.php
のaliases
に記述されている対応関係をclass_alias
関数によって結びつけているから。要はエイリアス(別名)をつけて省略的に書けるようにしていると思っておけば良いと思う。たぶん。)Route::get('/config', function () { return \Config::get('key'); });これを一見すると、
Illuminate\Support\Facades\Config
クラスに実装されたget
メソッドを呼んでいるように見えるが、実はget
メソッドはIlluminate\Support\Facades\Config
クラスにも、継承元のIlluminate\Support\Facades\Facade
クラスにも存在していない。実際には、getFacadeAccessor()
が実装されているのみである。Config.php<?php namespace Illuminate\Support\Facades; class Config extends Facade { /** * Get the registered name of the component. * * @return string */ protected static function getFacadeAccessor() { return 'config'; } }この時何が起こっているか、簡単に辿ってみる。
マジックメソッドと__callStatic()
Illuminate\Support\Facades\Config
クラスはget()
を持っていなかった。
実装されていないstaticなメソッドを呼ぼうとするとどうなるかというと、PHPでは__callStatic()
というマジックメソッドが呼ばれることになる。public static __callStatic ( string $name , array $arguments ) : mixed__callStatic() は、 アクセス不能メソッドを静的コンテキストで実行したときに起動します。
引数$name
は、 コールしようとしたメソッドの名前です。 引数$arguments
は配列で、メソッド $name に渡そうとしたパラメータが格納されます。マジックメソッドはPHPにおける特殊な関数であり、開発者が直接指定して呼び出すことは基本的になく、特定のタイミングで勝手に呼び出される。代表的なものが馴染み深い
__construct()
であり、新たにインスタンスが生成されるタイミングで勝手に呼び出されるマジックメソッドといえる。中身
継承元の
Illuminate\Support\Facades\Facade
クラスには、__callStatic()
が実装されている。(コメントなどは省略)Facade.php<?php namespace Illuminate\Support\Facades; ... abstract class Facade { ... public static function getFacadeRoot() { return static::resolveFacadeInstance(static::getFacadeAccessor()); } protected static function resolveFacadeInstance($name) { if (is_object($name)) { return $name; } if (isset(static::$resolvedInstance[$name])) { return static::$resolvedInstance[$name]; } if (static::$app) { return static::$resolvedInstance[$name] = static::$app[$name]; } } public static function __callStatic($method, $args) { $instance = static::getFacadeRoot(); if (! $instance) { throw new RuntimeException('A facade root has not been set.'); } return $instance->$method(...$args); } }上記のPHPマニュアルからの引用の通り、
__callStatic()
の引数$method
には呼び出そうとしたメソッドの名前、$args
にはその時に使用した引数が配列の形式で入る。
getFacadeRoot()
でサービスコンテナ($app
)から対象となるインスタンスを取得し、そのインスタンスのメソッドを引数の$method
によって実行する。つまり、staticなメソッドを使用しているように見えて、実際には実体となるインスタンスがサービスコンテナから取り出され、指定したメソッドが実行される、という流れになっていることがわかる。
- 投稿日:2019-11-24T15:26:09+09:00
PHP7.4のpreloadいれたらLaravelは早くなるのだろうかと思って検証した
皆さんこんにちは
PHP7.4でpreloadが来るっていうので、ワクテカしているわけですが、実際どれくらい早くなるのかしらって思いまして、ちょうど色々検証していたので、Laravelでも軽くやっちゃおうぜって思ったので、やってみます。
あと4日ですね!!
三行で
- preloadが来るよ
- preloadで読み込み済みの状態を作れるよ
- Laravelに導入すると、結構早くなるよ
Preload
PHP7.4でopcacheにpreloadという機能が入ります。これの概要は
- opcacheの設定にpreloadを実行するヘルパーファイルを指定できる
- webサーバを起動するとopcacheがヘルパーファイルを実行する
- ヘルパーファイル内でキャッシュ化したものは、通常のアクセスにおいて、すでに読み込まれたものとしてみなされる
そう、読み込み済みですよ!
簡単に読み込み済みの効果を見るために、以下のような例を考えてみましょう。preload利用の例
まずは、こんなDockerfileで、動かせるコンテナを作ってみます。
FROM php:7.4.0RC4-cli RUN apt update && apt install -y vim sudo && docker-php-ext-install opcache &&\ echo 'opcache.preload=/var/www/preload.php' >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini &&\ useradd nginx WORKDIR /var/wwwベースコンテナはちょいと古いのですが、まあ、検証当時のものなんで、許してくださいな。
そして、以下のようなディレクトリ構成のファイル群を用意します。classes - A.php - B.php index.php preload.php内容としては
A.php<?php class A { public function name(): string { return return static::class; } }B.php<?php class B extends A {}index.php<?php $b = new B; echo $b->name() . '<BR>';さて、サーバのエンドポイントとしてはindex.phpを使うわけですが、この中ではrequireを一回も使っていないし、当然autoloadの設定もしていないので、
sudo -u nginx php -S 0.0.0.0:8080としてサーバを起動しても、いざアクセスすると「Bなんてクラス知らんのだけど」っていうエラーが出るだけです。
ここで、preloadを使ってみましょう。
先程のDockerfileには予めopcache.preloadの設定が挿入されるようになっています。実際に書かれている設定はこんな感じになっています。/usr/local/etc/php/conf.d/docker-php-ext-opcache.inizend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20190902/opcache.so opcache.preload=/var/www/preload.phpこの
opcache.preload
がヘルパーファイルの場所になっているので、そこに以下のようなヘルパーをおいておきましょう。preload.php<?php $files = glob(__DIR__ . '/classes/**.php'); foreach ($files as $file) { opcache_compile_file($file); }この状態でサーバを起動します。
sudo -u nginx php -S 0.0.0.0:8080 [Sun Nov 24 04:54:36 2019] PHP 7.4.0RC4 Development Server (http://0.0.0.0:8080) startedちゃんと起動したようです。
それではアクセスしてみましょう。$ curl http://localhost:8080 B<BR>普通に動きますね。
preloadは性能に絡めるのか
多くのWebフレームワークは大量のクラスが定義されていますが、PHPでそれを読むにはいちいちファイルを見に行かなければなりません。しかも、PHPでは、基本的に各アクセスごとにプロセスが独立しているため、ファイルの読み込みもアクセスごとに発生します。
opcacheでこの動きを緩和することができますが、その動きは割と複雑です。
- クラスが呼ばれる (静的・インスタンス生成など)
- クラスの定義がない場合、autoloadで設定したルールに従い、ファイルをrequireする
- requireしようとしたファイルのキャッシュがあった場合、ファイルのタイムスタンプよりも新しければ、そのままキャッシュを使う
- キャッシュが古ければ、ファイルを読みに行く(設定によってスキップ可能)
- ファイルを読んだ場合はキャッシュを更新する
preloadしてあれば、1ですむので、それなりに早くなるんじゃないかって思います。
Laravelに入れてみる
たくさんのファイルを読み込むといえばLaravelですね!
そこで、適当にLaravelを入れて、検証してみましょう。includeしているファイルを見てみる
とりあえず、どのくらいのファイルを読み込んでいるのか見てみます。
public/index.php$kernel->terminate($request, $response); $includes = get_included_files(); file_put_contents('/var/www/includes.txt', implode("\n", $includes));こんな感じのを仕込むと、ファイルのリストが出てきます。350ファイル以上ありますね。
preloadに入れてみる
適当にpreloadに入れるわけですが、viewのキャッシュファイルとかは読んでもしょうがないかなとか思いながら、以下のようなloaderを作ります。
preload.php<?php $includes = file_get_contents('/var/www/includes.txt'); $includes = explode("\n", $includes); foreach ($includes as $include) { if (strpos($include, '/storage/framework') !== false) { echo $include, " - storage \n"; continue; } if (strpos($include, 'index.php') !== false) { echo $include, " - index \n"; continue; } if (strpos($include, 'src/config') !== false) { echo $include, " - config\n"; continue; } opcache_compile_file($include); }こいつをpreloadした状態でサーバを起動します。
index.phpにerror_log(count($includes));
みたいなコードを突っ込んでやると、読み込みファイル数が363 -> 46 になりました。せっかくなら1とかになってくれるとありがたかったんですが、そううまくは行かないようですね。検証する
例によってab使って一分間のアクセスレートを見てみましょう。
abはubuntu 18.04 (wsl2のやつ) を使ってます。preloadなし
$ ab -t 60 -c 1 http://localhost:8080/ ... Concurrency Level: 1 Time taken for tests: 60.004 seconds Complete requests: 23 Failed requests: 0 Total transferred: 77747 bytes HTML transferred: 55798 bytes Requests per second: 0.38 [#/sec] (mean) Time per request: 2608.884 [ms] (mean) Time per request: 2608.884 [ms] (mean, across all concurrent requests) Transfer rate: 1.27 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.1 0 0 Processing: 177 2609 1619.2 2894 7586 Waiting: 176 2608 1619.1 2893 7585 Total: 177 2609 1619.2 2894 7586 Percentage of the requests served within a certain time (ms) 50% 2844 66% 3064 75% 3230 80% 3256 90% 3323 95% 3658 98% 7586 99% 7586 100% 7586 (longest request)2.5秒超えるのか。。。
一応、opecache使ってファイルはキャッシュしているはずなんですが。preloadあり
$ ab -t 60 -c 1 http://localhost:8080/ ... Concurrency Level: 1 Time taken for tests: 60.160 seconds Complete requests: 301 Failed requests: 0 Total transferred: 1017565 bytes HTML transferred: 730226 bytes Requests per second: 5.00 [#/sec] (mean) Time per request: 199.867 [ms] (mean) Time per request: 199.867 [ms] (mean, across all concurrent requests) Transfer rate: 16.52 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.0 0 0 Processing: 148 200 140.9 179 1575 Waiting: 148 199 140.9 179 1574 Total: 148 200 140.9 179 1575 Percentage of the requests served within a certain time (ms) 50% 179 66% 187 75% 193 80% 199 90% 209 95% 224 98% 260 99% 852 100% 1575 (longest request)ほんまか?
10倍強の速度になっているんだが。まとめ
速度強化が劇的すぎて、流石にコンテナ特有のボリュームアクセス遅い問題に関係しちゃっているのかもしれないなぁとか思いながらも、ファイルアクセスが減って早くなっているのは事実だなぁって思いました。
将来的にはフレームワークごとにpreloadのヘルパーを用意してくれると、こっちとしては脳死で実装できて楽だなぁって他力本願に考えちゃったりしています。今回はこんなところです。
- 投稿日:2019-11-24T14:20:24+09:00
LaravelでControllerの名前を変更する方法
概要
php artisan make:controllerで作成したControllerを後に改名したくなった場合、知らずにやると
file does not exit とかで困ったので記録。手順
1.Controllerのファイル名を変更する。
2.Controllerファイルを開いて、Class名を変更する。
3.web.phpを開いて、第二引数の@の前の部分を手順2のClass名に差し替える。
4.以下をターミナルで実行する。composer dump-autoload参考にしたページ
Laravel 5.7でControllerのファイル名を少し変更(大文字)しただけなのに、Controllerファイルが見つからないエラーが出るようになった
- 投稿日:2019-11-24T13:05:27+09:00
herokuデプロイで10分であなたのアプリを世界に公開する手順【Laravel5.8】
今回はherokuを使った、Laravelプロジェクトを世界に手っ取り早く公開する手順をご紹介します。
実は今回僕も
初の公開作業をしながら同時にこのqiitaを書いている
ので、これが公開されているということは動作は保証されます笑herokuとはPaaS(Platform as a Service)と呼ばれるサービスで、アプリケーションを実行するためのプラットフォームです。
もう少しわかりやすく説明すると、サーバやOS、データベースなどの「プラットフォーム」と呼ばれる部分を、インターネット越しに使えるようにしてくれるサービスの一つです。
レンタルサーバーと似ていると思うかもしれませんが、レンタルサーバーとPaaSは「環境を貸してくれる」という意味では一緒なので、ほぼ同じと考えても問題はありません
herokuとは?初心者でも5分で分かる基本や特徴をまるっと紹介
大まかな流れか以下の通りです。
herokuに登録
↓
heroku CLIダウンロード
↓
herokuでアプリ作成
↓
PostgreSQLへの接続設定
↓
herokuでDBマイグレーション
↓
heroku openで世界に公開!それではいきましょう!
環境
MacOS Mojave 10.14.5(windowsのみなさんごめんなさい?♂️)
Laravel5.8.x
MAMP5.3で作成したLaravelプロジェクトherokuに登録
こちらより各自登録しておいてください。
heroku CLI
まずはherokuコマンドを入れます。
$ brew tap heroku/brew && brew install heroku念の為バージョン確認します。
$ heroku -v heroku/7.35.0 darwin-x64 node-v12.13.0okですね。
ターミナルからherokuログイン
cdコマンドでlaravelプロジェクトに移動してください。
$ cd /Applications/MAMP/htdocs/kanbankanbanは僕のプロジェクト名で、MAMPなのでhtdocs以下になっています
さあheroku CLIでログインします。
$ heroku login以下のようなメッセージが出たら
q以外を押す
heroku: Press any key to open up the browser to login or q to exit:すると?
こんなブラウザが出てくるので、
Log in
押します。Logging in... done Logged in as xxx@xxx.xxxログインできた!
HerokuのHTTPサーバー用コンフィグファイル作成
$ echo "web: vendor/bin/heroku-php-apache2 public" > /Applications/MAMP/htdocs/kanban/Procfileここは正直自分も行う理由があやふやです(^◇^;)
ProcfileはHerokuアプリの起動時に実行するプロセスを定義するためのファイルみたいです。herokuでアプリを作成する
$ heroku create前者がアプリのURL、後者がgitのプッシュ先になります。
これから使う大切なものなので、控えておきます。ここからgitでherokuのリモートリポジトリにプッシュしていきます。
$ git initこれでリポジトリを作成します。
そして実はLaravelプロジェクト直下に.git
という隠しディレクトリができています。
これ超重要です。$ git add .git add .コマンドはリポジトリにコミットする前に行うステージングを全ファイル分やってくれます。
$ git commit -m "initial commit"ステージングしたファイルをコミットしています。
そしたら、ここで一回
.git/config
を開きます。そして、先ほどターミナルに出たgitのリポジトリアドレスも確認してください。
[remote "heroku"] url = https://git.heroku.com/lit-plateau-44850.gitこれを追記しますが、
ここのアドレスは人によって違ってきます。
これがないと
リモートリポジトリがどれかわからないので、fatal errorが出る
んですよね(再送2回)では、
$ git push heroku masterherokuのmasterリポジトリにプッシュせしめるで候。
remote: Verifying deploy... done. To https://git.heroku.com/lit-plateau-44850.git * [new branch] master -> masterこのようなメッセージが出たらherokuデプロイできています。
ここからは細々した設定をしていきます。
Herokuの環境変数
$ heroku plugins:install heroku-config $ heroku config:set DB_DATABASE=laravel $ heroku config:pushDB名Laravelは、僕はDBローカルのMySQLでDB名がLaravelだったので、herokuでも合わせました。
Herokuにpostgresqlを作成、接続
herokuが無料で提供しているのはPostgreSQLということなので、herokuでHeroku Postgresを作成し、接続します。
$ heroku addons:create heroku-postgresql:hobby-dev $ php -r 'preg_match("/^postgres:\/\/(.+?):(.+?)@(.+?):(.+?)\/(.*?)$/", `heroku config:get DATABASE_URL`, $matches); `heroku config:set DB_CONNECTION=pgsql DB_HOST=$matches[3] DB_PORT=$matches[4] DB_DATABASE=$matches[5] DB_USERNAME=$matches[1] DB_PASSWORD=$matches[2]`;'herokuでマイグレーション
それでは、herokuでマイグレーションを実行します。
$ heroku run "php artisan migrate"その後、
Migration table created successfully.と出たら、終了です。
先ほどターミナルに出てきたアプリのURL覚えてますか?
あれが公開URLなので、緊張の入力です...
公開できた?
DBも機能してる。
ちなみにtechpitで作ったtrello風タスク管理アプリになります(https化し忘れた...)
http://lit-plateau-44850.herokuapp.com/
画像投稿機能とかtwitter Authとか入れたかったなぁ...