PHPでちょっとしたアプリを作ったりちょっとしたフレームワークを作ったりするとき、共通の全体設定をどのように書くかという悩みがある。
頭の中を整理するためにパターン分けしてみる。説明は独断と偏見に基づいている、かもしれない。
定数
定数にはdefine
とconst
がある。
<?php
define('FOO', 'foo');
const BAR = 'bar';
echo FOO;
echo BAR;
define
みんな大好きdefine
。古くから利用されている。
単純で分かりやすく、関数が使えるのが最大のメリット。
WordPressでは
define('DB_NAME', 'wordpress');
define('DB_USER', 'wordpress');
define('DB_PASSWORD', 'secret');
define('DB_HOST', 'localhost');
こんな感じで利用されている。
データベースの変数やプログラムのPATH設定などで主に利用される。
CakePHP では
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には
define('DB_USER', 'eccube');
define('DB_PASSWORD', 'eccube');
define('DB_SERVER', 'localhost');
define('DB_NAME', 'eccube');
こういったDB設定があり、WordPressとEC-CUBEを両方読み込もうとするとエラーになる。
一応、namespace
も使うことができる。
define('App\Controller\Index\PAGE_ENV', 'page env');
namespace App\Controller;
class Index {
public static function index(){
echo Index\PAGE_ENV, "\n";
}
}
可能というだけで、このような使い方は見かけない。おそらく下記const
を使うのであろう。
const
クラス定数としてconst
が利用できる。
トップレベルのグローバルに
const DEBUG = true;
のようにも書けるが、主には
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';
}
このようにnamespace
+class
+const
のセットで利用される。
トップレベルや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
で関数呼び出しをしたい場合は少しトリッキーなことをすれば出来た。
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
を経由して関数を呼び出せる。使い道は不明。
設定ファイル読み込み
XML
やJSON
のような特別なフォーマットの設定ファイルを用意して、それを読み込む方法もある。
また、PHPをそのまま設定ファイルのように使う方法もある。
return array
Laravel や FuelPHP、CakePHPなど主要なフレームワークはこの方式を採用しているようだ。
例えばLaravelのデータベース設定では
return [
'default' => env('DB_CONNECTION', 'mysql'),
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
],
.....
];
このようになっている。
利用する側は
$config = include 'config/database.php';
とすれば、$config
にreturn
した値が入る。
関数も使えるしグローバルも汚染しない。シンプルでもある。ファイルを解析する必要がないので速度も速い。良いことずくめである。
敢えてデメリットを言うとすれば、このファイルがPHPなので、文法を間違えばアプリケーションが停止するしreturn [
の前に自由にPHPのプログラムを書けてしまうということだろうか。
普通の変数
限定スコープ
PHPのinclude
は呼び出し側のスコープに影響される。設定ファイルのグローバル空間に変数を定義したとしても、読み込む側が関数の中でinclude
していればグローバルが汚染されることはない。
CodeIgniter でのデータベース設定ファイルは
<?php
$active_group = 'default';
$query_builder = TRUE;
$db['default'] = array(
'dsn' => '',
'hostname' => 'localhost',
'username' => 'username',
'password' => 'password',
'database' => 'codeigniter',
...
);
このようになっていて、普通にグローバル空間に変数を定義している。
呼び出し側で
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ファイル。
個人的には好きなのだが、まったく使われていない。
[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 = 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時にシングルトンとしてコンテナに登録するのが普通だろうか。
実際のフレームワークでは、こういった設定用クラスからdefine
やconst
、return array
の各設定ファイルが呼び出されることになる。
まとめ
書いてみたけど、どれかを選ぶのはなかなか難しい。
エディタで定義にすぐジャンプできるかという視点もある。この視点だとdefine
またはconst
が強い。
設定が大量にある処理はYAMLかreturn array
、PATHのように各所で使うけど単純なものはdefine
、環境別API設定のような複雑なものはクラス、というようにそれぞれ別々の方式を併用するのがPHPらしいのかもしれない。