背景
/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点がミソ
- putenv関数で環境変数をセットすること
tearDown()
で環境変数を復元すること$this->refreshApplication()
で、環境変数の変更をconfigに反映すること
- これで安心して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