PHP
PHPUnit

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

はじめに

PHPのApache関数の一つ、apache_request_headersを用いた実装をした後、phpunitでテストを実行すると下記のようなFatal Errorが発生する場合があります。その際の回避方法です。

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 リクエストヘッダを取得するメソッドです。

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

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
// 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 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を利用することができるようになります。

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

方法2: バイパスを作る

apache_request_headersが関数として定義されている場合のみapache_request_headersを実行し、未定義の場合は同様の振る舞いを行うメソッドを用意します。

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でもテスト実行可能になります。

参考