PHP で実装するコンフィグツール
ソースコードには含めたくない設定内容を外部ファイルから読み込みたい、というケースはあらゆるアプリケーションで発生します。
今までも、各種 PHP スクリプトで活用できる Config ツールを書いてきており、その内容は単一の config.json ファイルを読み込み、すべての設定をスタティックで保持し、値もスタティック関数で読み込む、という単純なものでした。
それらは今までのアプリケーションによく適応できましたが、アプリケーション規模が大きくなるにつれ、単一の設定ファイルだけのサポートでは見通しが悪くなってきました。
そこで、以下の様なコンフィグツールを設計してみます。
仕様
- 複数の設定ファイルは名前で区別して連想配列に保持
- 指定されたディレクトリに存在する指定された拡張子のコンフィグをロードする。
- 設定ファイルを区別する名前はファイル名の拡張子を除いたファイル名とする。
- サブディレクトリ下の設定ファイル読み込みはサポートしない。
- 名前単位でコンフィグインスタンスを取得することが可能
- インスタンスではプロパティのみで値を取得
- DI などは必要とせず、クラス自体をサービスロケーターとして利用する。
コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
<?php namespace Wws\Config; use Symfony\Component\Yaml\Yaml; use Yosymfony\Toml\Toml; class Config { private static $instances = []; public static function loadDir(string $dir ): void { $files = scandir( $dir ); foreach ( $files as $file ) { if (in_array([ '.' , '..' ], $file )) continue ; $ns = pathinfo ( $file , PATHINFO_FILENAME); $path = "{$dir}/{$file}" ; Config::load( $ns , $path ); } } public static function load(string $ns , string $path ): void { $instance = new static (); $instance ->loadFile( $path ); Config::setInstance( $ns , $instance ); } public static function setInstance(string $ns , Config $instance ): void { self:: $instances [ $ns ] = $instance ; } public static function getInstance(string $ns , Config $instance ): Config { return self:: $instances [ $ns ]; } const INI = 'ini' ; const YML = 'yml' ; const YAML = 'yaml' ; const JSON = 'json' ; const TOML = 'toml' ; private $_data = null; public function loadFile(string $path ): void { if (! file_exists ( $path )) { throw new \InvalidArgumentException( $path . ' not found. Please specify existing file.' ); } if (! $file = file_get_contents ( $path )) { throw new \InvalidArgumentException( $path . ' could not read. Please confirm access rights.' ); }; if (self::YAML === substr ( $path , -1 * strlen (self::YAML)) || $type == self::YAML) { $this ->_data = Yaml::parse( $file ); } elseif (self::YML === substr ( $path , -1 * strlen (self::YML)) || $type == self::YML) { $this ->_data = Yaml::parse( $file ); } elseif (self::JSON === substr ( $path ,-1 * strlen (self::JSON)) || $type == self::JSON) { $this ->_data = json_decode( $file , true); } elseif (self::INI === substr ( $path ,-1 * strlen (self::INI)) || $type == self::INI) { $this ->_data = Toml::Parse( $file ); } elseif (self::TOML === substr ( $path ,-1 * strlen (self::TOML)) || $type == self::TOML) { $this ->_data = Toml::Parse( $file ); } else { throw new \InvalidArgumentException( 'Please specify valid config file type. .yaml .json .toml' ); } } public function setValue(string $path , $obj , string $delimiter = '.' ) { $nodes = explode ( $delimiter , $path ); $value = & $this ->_data; for ( $i =0; $i < count ( $nodes ); $i ++) { $node = $nodes [ $i ]; if ( $i == count ( $nodes ) - 1) { if (preg_match( '/(?P<prop>\w+)\[(?P<idx>\d*)\]/' , $node , $matches )) { $prop = $matches [ 'prop' ]; if (! array_key_exists ( $prop , $value )) $value [ $prop ] = []; if ( '' === $matches [ 'idx' ]) { $value [ $prop ][] = $obj ; } else { $idx = (int) $matches [ 'idx' ]; $value [ $prop ][ $idx ] = $obj ; } } else { $value [ $node ] = $obj ; } } else { if (preg_match( '/(?P<prop>\w+)\[(?P<idx>\d+)\]/' , $node , $matches )) { $prop = $matches [ 'prop' ]; if (! array_key_exists ( $prop , $value )) $value [ $prop ] = []; if ( '' === $matches [ 'idx' ]) { $len = count ( $value [ $prop ]); $value [ $prop ][] = []; $value = & $value [ $prop ][ $len ]; } else { $idx = (int) $matches [ 'idx' ]; $value = & $value [ $prop ][ $idx ]; } } else { $value = & $value [ $node ]; } } } } public function hasValue(?string $path =null, string $delimiter = '.' ) { if ( is_null ( $path )) return true; try { $this ->getValue( $path , $delimiter ); return true; } catch (\OutOfRangeException $e ) { return false; } } public function getValue(?string $path =null, string $delimiter = '.' ) { if ( is_null ( $path )) return $this ->_data; $nodes = explode ( $delimiter , $path ); $value = $this ->_data; foreach ( $nodes as $node ) { if ( $node === '' ) continue ; if (preg_match( '/(?P<prop>\w+)\[(?P<idx>\d*)\]/' , $node , $matches )) { $prop = $matches [ 'prop' ]; $idx = (int) $matches [ 'idx' ]; if (! array_key_exists ( $prop , $value ) || ! array_key_exists ( $idx , $value [ $prop ])) { throw new \OutOfRangeException( 'The config has no value for ' . $path ); } $value = $value [ $prop ][ $idx ]; } else { if (! array_key_exists ( $node , $value )) { throw new \OutOfRangeException( 'The config has no value for ' . $path ); } $value = $value [ $node ]; } } return $value ; } } |