PHPのapache_request_headersを含む処理をphpunitでtestしたい時

# はじめに
PHPのApache関数の一つ、[apache_request_headers](http://php.net/manual/ja/function.apache-request-headers.php)を用いた実装をした後、phpunitでテストを実行すると下記のようなFatal Errorが発生する場合があります。その際の回避方法です。

```console
PHP Fatal error:  Call to undefined function apache_request_headers() in 
```

# apache_request_headersとは
http://php.net/manual/ja/function.apache-request-headers.php
すべての HTTP リクエストヘッダを取得するメソッドです。

このメソッドを実行すると下記のような返り値が得られます。

```php
array (
  'Host' => 'localhost',
  'Cache-Control' => 'max-age=0',
  'Origin' => 'https://localhost',
  'Upgrade-Insecure-Requests' => '1',
  'Content-Type' => 'application/x-www-form-urlencoded',
  'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36 BASE',
  'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
  'Referer' => 'https://localhost',
  'Accept-Encoding' => 'gzip, deflate, br',
  'Accept-Language' => 'en-US,en;q=0.9,ja;q=0.8',
  'Cookie' => '__utma=178864993.1558540938.1511488875.1513045856.1513050312.3; .....（省略）',
  'X-Forwarded-Proto' => 'https',
  'X-Forwarded-For' => '127.0.0.1',
  'X-Forwarded-Host' => 'localhost',
  'X-Forwarded-Server' => 'localhost',
  'Connection' => 'Keep-Alive',
  'Content-Length' => '2410',
)
```

変更履歴を参照する限り、CLIサーバ・FastCGI・Apacheモジュール・NSAPIサーバーモジュールとしてphpを動かす際に使える関数と説明されています。

| バージョン | 説明 |
|:--|:--|
| 5.5.7 | CLI サーバーでもこの関数が使えるようになりました。 |
| 5.4.0 | この関数は、FastCGI で使用可能になりました。 以前は、PHP が Apache モジュールあるいは Netscape/iPlanet/SunONE の NSAPI サーバーモジュール としてインストールされた時だけサポートされました。 |
http://php.net/manual/ja/function.apache-request-headers.php

# どうすればいいのか
- 方法1: phpunit実行時のみ定義する
- 方法2: バイパスを作る

## 方法1: phpunit実行時のみ定義する
phpunit実行時のみ、apache_request_headersが未定義だった場合に、独自定義する方法です。
### step1: Test/bootstrap.php
テスト実行時に最初に実行する処理をbootstrap.phpに定義します。今回は、apache_request_headersの関数定義をしたいため、下記のように書きます。

```php
<?php
// phpunitで実行した際は、apache_request_headersは未定義のため同じふるまいの関数を定義する
if (!function_exists('apache_request_headers')) {
	function apache_request_headers() {
		// apache_request_headersの振る舞いを実装する
		// @link http://php.net/manual/ja/function.apache-request-headers.php#116343
		$arh = array();
		$rx_http = '/\AHTTP_/';
		foreach($_SERVER as $key => $val) {
			if(preg_match($rx_http, $key)) {
				$arh_key = preg_replace($rx_http, '', $key);
				// do some nasty string manipulations to restore the original letter case
				// this should work in most cases
				$rx_matches = explode('_', strtolower($arh_key));
				if(count($rx_matches) > 0 and strlen($arh_key) > 2) {
					foreach($rx_matches as $ak_key => $ak_val) $rx_matches[$ak_key] = ucfirst($ak_val);
					$arh_key = implode('-', $rx_matches);
				} else {
					// go to next header
					continue;
				}
				$arh[$arh_key] = $val;
			}
		}
		if(isset($_SERVER['CONTENT_TYPE'])) $arh['Content-Type'] = $_SERVER['CONTENT_TYPE'];
		if(isset($_SERVER['CONTENT_LENGTH'])) $arh['Content-Length'] = $_SERVER['CONTENT_LENGTH'];
		return $arh;
	}
}
```

同様の振る舞いを定義するにあたり、php.netの下記のコメントを参考にしています。
http://php.net/manual/ja/function.apache-request-headers.php#116343
中では、``$_SERVER``から ``HTTP_`` を含むキーを取得・apache_request_headersで取得できる文字列形式に置換する処理を行っています。
置換例）
- ``HTTP_USER_AGENT`` -> `User-Agent`
- ``CONTENT_TYPE`` -> `Content-Type`

### step2: phpunit.xmlの設定
phpunitの設定ファイルである、phpunit.xmlにbootstrapという項目を追加し、作成したTest/bootstrap.phpを設定して下さい。

```xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/3.7/phpunit.xsd"
         colors="true"
         backupGlobals="false"
         bootstrap="./Test/bootstrap.php"
         verbose="true">
</phpunit>
```

以上の設定をすることで、phpunitでもapache_request_headersを利用することができるようになります。

```console
Configuration read from /home/www/public_html/thebase.in/current/phpunit.xml
```


## 方法2: バイパスを作る
apache_request_headersが関数として定義されている場合のみapache_request_headersを実行し、未定義の場合は同様の振る舞いを行うメソッドを用意します。

```php
private function request_headers()
{
	// apache関数が使えてかつapache_request_headersでの値取得に成功した場合
	if (function_exists('apache_request_headers')) {
		if ($headers = apache_request_headers()) {
			return $headers;
		}
	}

	// apache_request_headersの振る舞いを実装する
	// @link http://php.net/manual/ja/function.apache-request-headers.php#116343
	$arh = array();
	$rx_http = '/\AHTTP_/';
	foreach($_SERVER as $key => $val) {
		if(preg_match($rx_http, $key)) {
			$arh_key = preg_replace($rx_http, '', $key);
			// do some nasty string manipulations to restore the original letter case
			// this should work in most cases
			$rx_matches = explode('_', strtolower($arh_key));
			if(count($rx_matches) > 0 and strlen($arh_key) > 2) {
				foreach($rx_matches as $ak_key => $ak_val) $rx_matches[$ak_key] = ucfirst($ak_val);
				$arh_key = implode('-', $rx_matches);
			} else {
				// go to next header
				continue;
			}
			$arh[$arh_key] = $val;
		}
	}
	if(isset($_SERVER['CONTENT_TYPE'])) $arh['Content-Type'] = $_SERVER['CONTENT_TYPE'];
	if(isset($_SERVER['CONTENT_LENGTH'])) $arh['Content-Length'] = $_SERVER['CONTENT_LENGTH'];
	return $arh;
}
```

そして、apache_request_headers()の代わりにrequest_headers()を実行するようにすれば、phpunitでもテスト実行可能になります。

# 参考
- http://php.net/manual/ja/function.apache-request-headers.php
- https://gist.github.com/trylik/945915