Help us understand the problem. What is going on with this article?

PHPでプログラム全体の設定に使う変数の保持の仕方

PHPでちょっとしたアプリを作ったりちょっとしたフレームワークを作ったりするとき、共通の全体設定をどのように書くかという悩みがある。
頭の中を整理するためにパターン分けしてみる。説明は独断と偏見に基づいている、かもしれない。

定数

定数にはdefineconstがある。

<?php
define('FOO', 'foo');
const BAR = 'bar';

echo FOO;
echo BAR;

define

みんな大好きdefine。古くから利用されている。
単純で分かりやすく、関数が使えるのが最大のメリット。

WordPressでは

wp-config.php
define('DB_NAME', 'wordpress');
define('DB_USER', 'wordpress');
define('DB_PASSWORD', 'secret');
define('DB_HOST', 'localhost');

こんな感じで利用されている。
データベースの変数やプログラムのPATH設定などで主に利用される。

CakePHP では

config/paths.php
define('ROOT', dirname(__DIR__));
define('APP_DIR', 'src');
define('APP', ROOT . DS . APP_DIR . DS);

このようにPATHが設定されている。

何らかの条件にしたがって定数定義を切り替える場合にも利用される。

if ($_SERVER['SERVER_NAME'] == 'localhost'){
  define('ENV', 'development');
  define('DEBUG', true);
}else{
  define('ENV', 'production');
  define('DEBUG', false);
}

また、definedで定義済みかどうかを調べることができる。

if (defined('DEBUG')){
  define('ENV', 'development');
}else{
  define('ENV', 'production');
}

プログラムを配布して設定ファイルを修正、FTPでアップ、ぐらいのツールで使うには便利である。

デメリットとしては、グローバル空間が汚染されるので数が多くなってくると管理しきれないという問題がある。また、複数のライブラリで同じ定数が使われていると競合を起こす。

例えばEC-CUBE2には

data/config/config.php
define('DB_USER', 'eccube');
define('DB_PASSWORD', 'eccube');
define('DB_SERVER', 'localhost');
define('DB_NAME', 'eccube');

こういったDB設定があり、WordPressとEC-CUBEを両方読み込もうとするとエラーになる。

一応、namespaceも使うことができる。

config.php
define('App\Controller\Index\PAGE_ENV', 'page env');
src/controller/Index.php
namespace App\Controller;
class Index {
    public static function index(){
        echo Index\PAGE_ENV, "\n";
    }
}

可能というだけで、このような使い方は見かけない。おそらく下記constを使うのであろう。

const

クラス定数としてconstが利用できる。
トップレベルのグローバルに

config.php
const DEBUG = true;

のようにも書けるが、主には

Psr/Log/LogLevel.php
namespace Psr\Log;
class LogLevel
{
    const EMERGENCY = 'emergency';
    const ALERT = 'alert';
    const CRITICAL = 'critical';
    const ERROR = 'error';
    const WARNING = 'warning';
    const NOTICE = 'notice';
    const INFO = 'info';
    const DEBUG = 'debug';
}

このようにnamespaceclassconstのセットで利用される。
トップレベルやnamespace直下だとオートロードできないので、手でrequire_onceを書かないといけないのであまり嬉しくない。

今では演算子が使えるので単純な数値計算や文字列連結はできる。

class Category {
    const CATEGORY1 = 1;
    const CATEGORY2 = 2;
    const SPECIAL_CATEGORY = 999;
    const CATEGORIES = [
        self::CATEGORY1 => 'category 1',
        self::CATEGORY2 => 'category 2',
    ];
    const SPECIAL_CATEGORIES = [
        self::SPECIAL_CATEGORY => 'special',
    ];
    const ALL_CATEGORIES = self::CATEGORIES + self::SPECIAL_CATEGORIES;
}
var_dump(Category::ALL_CATEGORIES);

/*
array(3) {
  [1] =>
  string(10) "category 1"
  [2] =>
  string(10) "category 2"
  [999] =>
  string(7) "special"
}
*/

なので多少凝ったこともできる。

アプリケーションの設定として利用することはあまり無いと思われる。大抵はそのクラスにおける固定値を定義するために使う。

constは(直接は)関数呼び出しができない。

これを書いていて今気付いたのだが、どうしてもconstで関数呼び出しをしたい場合は少しトリッキーなことをすれば出来た。

Mode.php
function get_mode() {
    $host = $_SERVER['SERVER_NAME'] ?? '';
    if ($host === 'example.com')
        return 'staging';

    if (preg_match('/^[[:alnum:]]\.example\.com$/', $host))
        return 'staging';

    return 'development';
}
define('GET_MODE', get_mode());

class Mode {
    const APP_MODE = GET_MODE;
}
var_dump(Mode::APP_MODE);

defineは関数呼び出しできるので、constからdefineを経由して関数を呼び出せる。使い道は不明。

設定ファイル読み込み

XMLJSONのような特別なフォーマットの設定ファイルを用意して、それを読み込む方法もある。
また、PHPをそのまま設定ファイルのように使う方法もある。

return array

Laravel や FuelPHP、CakePHPなど主要なフレームワークはこの方式を採用しているようだ。

例えばLaravelのデータベース設定では

config/database.php
return [
    'default' => env('DB_CONNECTION', 'mysql'),
    'connections' => [

        'sqlite' => [
            'driver' => 'sqlite',
            'database' => env('DB_DATABASE', database_path('database.sqlite')),
            'prefix' => '',
        ],
    .....
];

このようになっている。
利用する側は

$config = include 'config/database.php';

とすれば、$configreturnした値が入る。
関数も使えるしグローバルも汚染しない。シンプルでもある。ファイルを解析する必要がないので速度も速い。良いことずくめである。

敢えてデメリットを言うとすれば、このファイルがPHPなので、文法を間違えばアプリケーションが停止するしreturn [の前に自由にPHPのプログラムを書けてしまうということだろうか。

普通の変数

限定スコープ

PHPのincludeは呼び出し側のスコープに影響される。設定ファイルのグローバル空間に変数を定義したとしても、読み込む側が関数の中でincludeしていればグローバルが汚染されることはない。

CodeIgniter でのデータベース設定ファイルは

application/config/database.php
<?php
$active_group = 'default';
$query_builder = TRUE;
$db['default'] = array(
    'dsn'   => '',
    'hostname' => 'localhost',
    'username' => 'username',
    'password' => 'password',
    'database' => 'codeigniter',
   ...
);

このようになっていて、普通にグローバル空間に変数を定義している。

呼び出し側で

system/database/DB.php
function &DB($params = '', $query_builder_override = NULL)
{
  ...
  // (編集済み)
  $file_path = APPPATH.'config/database.php';
  include($file_path);

  ...
}

このように関数の中でincludeすると、$db$query_builderはグローバルスコープではなく関数のローカルスコープとして読み込まれる。
使い方によっては便利で、テンプレートエンジンの変数設定時にはよく使われる手法だが、気を付けないと関数の中の変数が上書きされる危険もある。
全てのローカル変数とinclude時の変数展開の動作を完全に把握している場合にのみ使うべき。

グローバル変数

WordPressではグローバル変数が普通に使われる。
関数もグローバルだし、グローバル関数を呼び出したら内部状態が変更されたりする。
ある意味PHPらしい構造と言える。

function my_func(){
    global $wpdb;
    ....
}

というような冒頭にglobal宣言するような記述はWordPressではよく見かける。

ただし自分でこのような設計をするのはかなり難しい。
全部グローバルに書いて無駄に複雑になったプログラムを時々見かけた。

気を付けていれば問題ないPHP の究極系がWordPressかもしれない。

特定のフォーマットのファイル

yaml

PHP以外の言語ではよく利用されるが、PHPではあまり見かけない。組み込み関数でパースできないからだろうか。
フレームワークのSymfonyの設定で使われているので、もしYAMLを使う場合は

composer require symfony/yaml

Symfonyのパッケージを使うのが良いと思う。

json

composerの設定がjsonなのでPHPで使われているといえば使われている。
組み込み関数のjson_decodeですぐPHPの配列に変換できるのも良い。
でもアプリケーションの設定としてはあまり見かけない。異なるプログラミング言語間のデータ通信フォーマットとしてのイメージが強いからだろうか。

XML

PHPUnitなどJava由来のものに使われている。
書くのがめんどくさい。解析もめんどくさい。

DTDをがっつり定義する大規模なシステムでは使われている…?あまり知らない。
DOM (https://php.net/dom) も XML (https://php.net/xml) も組み込み関数で使える。

ini

サーバー設定ファイルなどで使われているiniファイル。
個人的には好きなのだが、まったく使われていない。

config.ini
[core]
di = "My\Container"
env = development

[app]
name = "My Application"
data[] = data1
data[] = data2
hash[app1] = app1
hash[app2] = app2

[config]
debug = false
staging = true
error = E_ALL ^ E_DEPRECATED
config.php
$config = parse_ini_file('config.ini', true);
var_dump($config);

/*
array(3) {
  'core' =>
  array(2) {
    'di' =>
    string(12) "My\Container"
    'env' =>
    string(11) "development"
  }
  'app' =>
  array(3) {
    'name' =>
    string(14) "My Application"
    'data' =>
    array(2) {
      [0] =>
      string(5) "data1"
      [1] =>
      string(5) "data2"
    }
    'hash' =>
    array(2) {
      'app1' =>
      string(4) "app1"
      'app2' =>
      string(4) "app2"
    }
  }
  'config' =>
  array(3) {
    'debug' =>
    string(0) ""
    'staging' =>
    string(1) "1"
    'error' =>
    string(5) "24575"
  }
}
*/

定数を展開するとか演算が出来てしまうなどの妙なハマりどころがあるからだろうか。
php.iniの書き方がそのまま使えるので、ある意味便利、ある意味おせっかい。

クラス

アプリケーション設定と言っていいのか分からないが、クラスにしてしまえば何でもできる。
グローバル変数的に使うのでSingletonにする。

<?php
namespace App;
class Config
{
    public $APP_ENV = 'development';
    private function __construct(){}
    public static function getInstance(){
        static $instance;
        if ($instance === null){
            $instance = new self();
        }
        return $instance;
    }
}

より細かく制御したいなら

namespace App;
class Config
{
    public static function factory(){
        switch ($name = getenv('APP_ENV')){
        case 'production':
        case 'development':
        case 'testing':
            $cls = 'App\Config\\' . ucfirst($name);
            return new $cls;
            break;

        default:
            return new App\Config\Development();
            break;
        }
    }
}

Factoryにして設定クラスを分けつつ、Auto Wiring時にシングルトンとしてコンテナに登録するのが普通だろうか。

実際のフレームワークでは、こういった設定用クラスからdefineconstreturn arrayの各設定ファイルが呼び出されることになる。

まとめ

書いてみたけど、どれかを選ぶのはなかなか難しい。
エディタで定義にすぐジャンプできるかという視点もある。この視点だとdefineまたはconstが強い。

設定が大量にある処理はYAMLかreturn array、PATHのように各所で使うけど単純なものはdefine、環境別API設定のような複雑なものはクラス、というようにそれぞれ別々の方式を併用するのがPHPらしいのかもしれない。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away