【Laravel】configをテストするなどした
背景
/config/hoge.php
の中身を\Config::get('hoge.path.to.value')
って感じに読み取れるアレ/config/
配下のPHPはarrayを返しさえすれば何でも書ける- Facadeとかは動かないけど
- 業務で、configの中にif-then-elseのロジックを書くことになった
- ロジックを書くならテストも書きたくなるのが人情というもの
サンプル
- const.php
<?php $ret = []; if (env('APP_ENV') === 'local') { $ret['some_key'] = 'some value for local'; } elseif (env('APP_ENV') === 'staging') { ... } ... return $ret;
- これだけなら「
.env.local
とか.env.staging
とか作れや」という話で終わり - 今回は「envをいっぱい作りたくない・envにいっぱい書きたくない」という要望につきこうなった
- envの中で環境により変えるのは最低限
APP_ENV
だけで済ます
- envの中で環境により変えるのは最低限
テスト
<?php ... class ConfigConditionalTest extends TestCase { // ---------------------------------------- // fixtures // ---------------------------------------- /** * phpunit.xmlのAPP_ENV設定値を退避するやつ * @var string */ private static $appEnv; /** * phpunit.xmlのAPP_ENV設定値を退避する */ public static function setUpBeforeClass(): void { parent::setUpBeforeClass(); self::$appEnv = env('APP_ENV'); } /** * APP_ENVを復元する */ public function tearDown(): void { $this->setAppEnv(self::$appEnv); } /** * APP_ENVを設定するヘルパ * @var string $appEnv */ private function setAppEnv(string $appEnv): void { putenv("APP_ENV=${appEnv}"); // 環境変数の変更をconfigに反映する $this->refreshApplication(); } // ---------------------------------------- // cases // ---------------------------------------- /** * @test */ public function some_keyはlocal環境にて所定の値である(): void { $this->setAppEnv('local'); $this->assertSame( 'some value for local', \Config::get('const.some_key') ); } }
- 思い出しつつ改変しながら書いた。動作未確認
- 守秘義務とかもあるので
- 雰囲気が大事
- 下記3点がミソ
- これで安心してconfigにロジックを書ける
そもそもアンチパターンなのでは?
某氏による指摘。ありがとうございます:
configであればそもそも環境変数を参照するようにしておいて.envを変えれば済むわけで。
envによる分岐をするならサービスプロバイダの中がいいかとそもそもconfigにsetする必要があるのかな?
特定のconfigの値が決まったときにもう一つのconfigの値が自動的に決まるような制約がある場合であればFactoryで整合性を保証するという手もあるけど。
configにsetし直すというのであれば少々まどろっこしいな
- 今回のconfigを振り返ってみる
<?php $ret = []; if (env('APP_ENV') === 'local') { $ret['some_key'] = 'some value for local'; } elseif (env('APP_ENV') === 'staging') { ... } ... return $ret;
- ちょっとでもデザインパターンをかじった人間ならこれをみて嘔吐反射を催すはず
- プログラマが知るべき97のこと/ポリモーフィズムの利用機会を見逃さない
- configのキーが増えるたびに同じif文を書きますか?
- configにこだわらず、こんな感じのクラスを作るべきなのではないか
<?php abstract class ConstRepository { abstract public function getSomeValue(): string; abstract public function getAnotherValue(): string; ... } class LocalConstRepository extends ConstRepository { public function getSomeValue(): string { return 'some value for local' } public function getAnotherValue(): string { return 'another value for local' } ... } class StagingConstRepository extends ConstRepository { ...
- で、いつもどおりサービスプロバイダの中でbindする
- その際に
APP_ENV
により分岐する- 同じ分岐を何度も書かなくて済む
<?php ... if (env('APP_ENV') === 'local') { app()->bind(ConstRepository::class, LocalConstRepository::class); } elseif (env('APP_ENV') === 'staging') { app()->bind(ConstRepository::class, StagingConstRepository::class); } ...
\Config::get('some_key')
を読んでいたクライアントコードはConstRepository@getSomeValue()
を呼び出すようにすればOK