CodeIgniter4 の 環境/環境変数/設定 (Config) の使い方

CodeIgniter4 メモ。
[CodeIgniter4 User Guide](https://bcit-ci.github.io/CodeIgniter4/index.html) を参考にしながら、必要に応じてソースを読み解き、挙動を追って理解を深めようと思います。

## Attention
2017/09/23 現在 開発版の CodeIgniter4 についての記事です。リリースまでに変更される可能性があることをご承知おきの上、お読み下さい。

使用している commit: [8eba1da1f194e5124d87cc4d3b5c75e68af1ad44](https://github.com/bcit-ci/CodeIgniter4/tree/8eba1da1f194e5124d87cc4d3b5c75e68af1ad44)

## TL;DR

* **`.env` に `CI_ENVIRONMENT` を設定しない。**
* 環境に依存する Config 以下の設定は `.env` に設定する

## 環境 (Environment)

User Guide: [Handling Multiple Environments](https://bcit-ci.github.io/CodeIgniter4/general/environments.html?highlight=environment)

環境を CodeIgniter に伝えるには、次の二つがあります。

1. `.env` ファイルに記述
2. Webサーバーに環境変数を設定
    * Apache であれば `SetEnv CI_ENVIRONMENT development`
    * nginx であれば `fastcgi_param CI_ENVIRONMENT development`

この `CI_ENVIRONMENT` は、下記で 定数 `ENVIRONMENT` に設定されます。

```framework/system/CodeIgniter.php
	protected function detectEnvironment()
	{
		// running under Continuous Integration server?
		if (getenv('CI') !== false)
		{
			define('ENVIRONMENT', 'testing');
		}
		else
		{
			define('ENVIRONMENT', $_SERVER['CI_ENVIRONMENT'] ?? 'production');
		}
	}
```

`CI_ENVIRONMENT` が設定されていなければ、`production` として動作します。
ちなみに、`getenv('CI')` の CI とは、CodeIgniter ではなく、*Continuous Integration* の意味ですね。`framework/.travis.yml` に設定されていました。

### `.env` と Web サーバー、どちらが優先される？

Apache/nginx 等の Web サーバーに設定された環境変数は、PHP では `$_SERVER` に格納され、使用できますが、`.env` にも `CI_ENVIRONMENT` を設定していた場合、どちらが優先されるのでしょう？

実際に環境変数をセットしているのは、下記の箇所です。

```framework/system/Config/Dotenv.php
	protected function setVariable(string $name, string $value = '')
	{
		list($name, $value) = $this->normaliseVariable($name, $value);

		if ( ! getenv($name, true))
			putenv("$name=$value");
		if (empty($_ENV[$name]))
			$_ENV[$name] = $value;
		if (empty($_SERVER[$name]))
			$_SERVER[$name] = $value;
	}
```

`$_ENV`, `$_SERVER` それぞれ、その環境変数名が空だったら、値をセットするようになっています。なので、 Web サーバーの設定値が優先されます。

「`.env` に書いてあるのに、環境が切り替わらない〜」 とハマらないためにも、どこで設定するかのルールを決めた方がよいでしょう。

*追記*

[User Guide](https://bcit-ci.github.io/CodeIgniter4/general/configuration.html#handling-different-environments) に書いてありましたね。

> If the variable exists in the environment already, it will NOT be overwritten.

意訳

> すでに環境変数が設定されていたら、変数は上書きしません。

*追記ここまで*

### どの方法で `CI_ENVIRONMENT` を設定するか？

ここでは、本番・ステージング・ローカル開発の3つを切り替えたいと思います。

今や仮想化、コンテナ、Infrastructure as Code があるため、本番とステージングの構成に差異は無いという前提にします。

#### 本番 (`production`)

`$_SERVER['CI_ENVIRONMENT']` が設定されていなければ、`production` として扱うため、何も設定しません。

本番環境は、「しなくていいことは極力しない」ほうが良いと考えています。

#### ステージング (`staging`)

前述のとおり、本番と差はありません。ですが、後述のローカル開発環境との兼ね合いで、`.env`には書きたくありません。

なので、ここは Web サーバーに任せようと思います。

`.htaccess` に書くのも手ではありますが、nginx になると使えません。なので、もうサーバーの設定ファイルに書くことにします。

```apache:/etc/httpd/conf.d/apache_staging.conf
SetEnv CI_ENVIRONMENT development
```

```nginx:/etc/nginx/conf.d/nginx_staging.conf
fastcgi_param CI_ENVIRONMENT "development";
```

専用のサーバーを設置していると思うので、グローバルなスコープで定義してしまいます。
これらのファイルをサーバー構築時に**設置するだけ**でよいので、最もお手軽かと。

#### ローカル開発 (`development`)

CodeIgniter4 にはローカル開発環境のための `serve` スクリプトが付属しています。

```bash
php serve
```

と実行すると、ビルトインサーバーを使って http://localhost:8080/ で動作します。
この時、ルータースクリプトとして、`rewrite.php` を指定されており、その中で

```php
$_SERVER['CI_ENVIRONMENT'] = 'development';
```
と、 CI_ENVIRONMENT が設定されています。`.env` に書きたくない、というのはココにあります。

前述の
> `$_ENV`, `$_SERVER` それぞれ、その環境変数名が空だったら、値をセットするようになっています。なので、 Web サーバーの設定値が優先されます。

を思い出して下さい。そうです、`$_SERVER['CI_ENVIRONMENT']` には値がセットされているので、`.env` に `CI_ENVIRONMENT` を設定しても、`development` となります。

仮に、`.env` に `CI_ENVIRONMENT = "hoge"` として `serve` から起動した場合、

```php
var_dump($_SERVER['CI_ENVIRONMENT']); // "development"
var_dump($_ENV['CI_ENVIRONMENT'] // "hoge"
```

という、チグハグな状態になります。作りとしては、これら二値は同じ値であることが前提と思われるので、この運用はよろしくない。
**いずれの環境においても、`.env` に `CI_ENVIRONMENT` を設定しない**
とするのが正のように思います。

---

## 設定 (Config)

User Guide: [Working With Configuration Files](https://bcit-ci.github.io/CodeIgniter4/general/configuration.html)

User Guide に従い、データベースの設定を読み出してみます

```php
$db_config = new \Config\Database();
var_dump($db_config);
```

```php
object(Config\Database)[40]
  public 'filesPath' => string '/XXX/XXX/XXX/application/Database/' (length=47)
  public 'defaultGroup' => string 'default' (length=7)
  public 'default' => 
    array (size=19)
      'DSN' => string '' (length=0)
      'hostname' => string 'localhost' (length=9)
      'username' => string '' (length=0)
      'password' => string '' (length=0)
      'database' => string '' (length=0)
      'DBDriver' => string 'MySQLi' (length=6)
      'DBPrefix' => string '' (length=0)
      'pConnect' => boolean false
      'DBDebug' => boolean true
      'cacheOn' => boolean false
      'cacheDir' => string '' (length=0)
      'charset' => string 'utf8' (length=4)
      'DBCollat' => string 'utf8_general_ci' (length=15)
      'swapPre' => string '' (length=0)
      'encrypt' => boolean false
      'compress' => boolean false
      'strictOn' => boolean false
      'failover' => 
        array (size=0)
          empty
      'port' => int 3306
  public 'tests' => 
    array (size=19)
      'DSN' => string '' (length=0)
      'hostname' => string '127.0.0.1' (length=9)
      'username' => string '' (length=0)
      'password' => string '' (length=0)
      'database' => string '' (length=0)
      'DBDriver' => string '' (length=0)
      'DBPrefix' => string 'db_' (length=3)
      'pConnect' => boolean false
      'DBDebug' => boolean true
      'cacheOn' => boolean false
      'cacheDir' => string '' (length=0)
      'charset' => string 'utf8' (length=4)
      'DBCollat' => string 'utf8_general_ci' (length=15)
      'swapPre' => string '' (length=0)
      'encrypt' => boolean false
      'compress' => boolean false
      'strictOn' => boolean false
      'failover' => 
        array (size=0)
          empty
      'port' => int 3306
  protected 'registrars' => null
```

`application/Config/Database.php` の値が取得できています。というか、クラスまんまですね。

User Guide によれば、自作の設定ファイルを作ることができるそうです。以下 User Guide から引用。

```application/Config/App.php
<?php namespace Config;

class App extends \CodeIgniter\Config\BaseConfig {

        public $siteName = 'My Great Site';
        public $siteEmail = 'webmaster@example.com';

}
```

ただし、 `application/Config/App.php`, `\Config\App` はデフォルトで用意されていて、コアで使われているので別の名前にする必要があります。(User Guide も違う例文にしたほうが良いと思う…)

さらに、特に影響のない範囲ですが、デフォルトの Config では `use CodeIgniter\Config\BaseConfig;` しています。できれば前へ倣えしたほうがいいかと。

### 環境ごとの設定

どの環境でも同じ設定値で済むものについては、`application/Config/` 以下に設定していけばよいですが、先述のデータベースなどは、本番と開発で接続先が異なることがほとんどです。

その取り回しについては、前項 **環境 (Environment)** で言及している `.env` が使えます。

`CI_ENVIRONMENT` は `.env` に設定しないことを決めましたが、他方、設定値についてはとても有用です。

単純な使い方として、

```ini:.env
HOGE_VAR=hoge
```

```php
$hoge_var = getenv('HOGE_VAR');
$hoge_var = $_SERVER['HOGE_VAR'];
$hoge_var = $_ENV['HOGE_VAR'];
```

というふうに使えるよう、環境変数にセットされます。

ここで紹介したい、**便利な使い方**は、`.env` から Config に値を設定する方法です。(もちろん、User Guide に書いてありますが)

データベースの接続設定を例にします。

```ini:.env
# Database
database.default.hostname = "localhost"
database.default.port = "3306"
database.default.username = "mysql_user"
database.default.password = "mysql_pass"
database.default.database = "app_db"
database.default.DBDriver = "MySQLi"
```

`application/Config/Database.php` についてはデフォルトのまま変更せず、先ほどと同じように設定値を `var_dump` してみます。 (長いので `$db_config->default` のみ抜粋)

```php
array (size=19)
  'DSN' => string '' (length=0)
  'hostname' => string 'localhost' (length=9)
  'username' => string 'mysql_user' (length=10)
  'password' => string 'mysql_pass' (length=10)
  'database' => string 'app_db' (length=6)
  'DBDriver' => string 'MySQLi' (length=6)
  'DBPrefix' => string '' (length=0)
  'pConnect' => boolean false
  'DBDebug' => boolean true
  'cacheOn' => boolean false
  'cacheDir' => string '' (length=0)
  'charset' => string 'utf8' (length=4)
  'DBCollat' => string 'utf8_general_ci' (length=15)
  'swapPre' => string '' (length=0)
  'encrypt' => boolean false
  'compress' => boolean false
  'strictOn' => boolean false
  'failover' => 
    array (size=0)
      empty
  'port' => string '3306' (length=4)
```

ご覧の通り、値が変わりました。`.` でつなげてネストすることで、配列の値が設定されます。

あとは、本番、開発などの環境ごとに `.env` が変わればよいわけですね。

環境変数に値を設定することもできるし、CodeIgniter の Config の値を設定することもできるので、たとえば AWS などのサービスで使う **取扱いに注意しなければならないキーやパスワード**を `.env` ひとつにまとめることができます。

当然のことながら、この **`.env` は バージョン管理に含めてはならない**運用が必要です。

#### `application/Config/*.php` を編集する必要

`.env` で設定を管理するにしても、**デフォルトで用意されていない値**の場合、そのクラスのプロパティは宣言しておくとよいでしょう。たとえば、`App` に `hoge` という設定を作るとします。

```.env
app.hoge = "HogeHoge"
```

`.env` はこれでいいとして、`\Config\App` にも `hoge` を追加しましょう。

```application/Config/App.php
<?php namespace Config;

use CodeIgniter\Config\BaseConfig;

class App extends BaseConfig
{
// ...略...
	public $hoge = 'default';
// ...略...
}
```

**確実に `.env` に設定する** ことが保証されるなら、問題はありません。しかし、前述のとおりバージョン管理に含めないので、プロパティを追加することで **「どんな設定値を追加したか」** のログができます。
デフォルト値も設定しておけば、フェイルセーフとして機能するかもしれません。

また、IDEがプロパティがあることを認識するので、補完が効くようになります。

---

## まとめ

以上、CodeIgniter4 での環境切替/設定の取り回しについてまとめてみました。

先日 参加させていただいた CodeIgniter Night ([第4回](https://codeigniter-talk.doorkeeper.jp/events/62801)) で聞いた、「User Guide に書かれていることがすべて」 のとおりで、この記事は結局 User Guide の焼き直しみたいなものでした。

しかし、CodeIgniter はだいぶ自由度が高い印象を受けます。なので、自分が考える 「きれいな」 とか 「安全な」 開発運用のため、必要と思ったのでまとめた次第です。
これから CodeIgniter を使う誰かの一助にでもなれば。