基本满足所有配置相关的需求。
001 | /** |
002 | * 解析.ini格式的配置文件为一个树形结构的对象 |
003 | * 配置文件不同section通过冒号继承 |
004 | * 默认根据hostname确定使用的section,如果不能确定就优先使用production |
005 | * 检测环境的时候总是优先检测production,其余section按定义顺序检测 |
006 | * |
007 | * @author ares@phpdr.net |
008 | * |
009 | */ |
010 | class Config { |
011 | /** |
012 | * 解析后的配置文件 |
013 | * |
014 | * @var stdClass |
015 | */ |
016 | private $config ; |
017 | /** |
018 | * 一个二维数组,键是配置文件的section |
019 | * 值是一个数组或回调函数 |
020 | * 如果是数组则计算hostname是否在数组中决定是否使用该section |
021 | * 如果是回调函数通过返回值true或false来确定是否使用该section |
022 | * |
023 | * @var array |
024 | */ |
025 | private $map = array (); |
026 | |
027 | /** |
028 | * section会被解析,:表示继承 |
029 | * 配置项中的'.'用来区分层级关系 |
030 | * section中的'.'不会被解析,配置中的数组不受影响。 |
031 | * |
032 | * @param string $conf |
033 | * @throws ErrorException |
034 | * @return stdClass |
035 | */ |
036 | function __construct( $conf , $map ) { |
037 | $config = $this ->parseIni ( ( object ) parse_ini_string ( $conf , true ) ); |
038 | if ( array_key_exists ( 'production' , $map )) { |
039 | $production = $map [ 'production' ]; |
040 | unset ( $map [ 'production' ] ); |
041 | $map = array_merge ( array ( |
042 | 'production' => $production ), $map ); |
043 | } else { |
044 | throw new ErrorException ( 'production section not found in config' ); |
045 | } |
046 | $section = 'production' ; |
047 | $hostname = gethostname (); |
048 | foreach ( $map as $k => $v ) { |
049 | if ( is_array ( $v )) { |
050 | foreach ( $v as $v1 ) { |
051 | if ( $v1 == $hostname ) { |
052 | $section = $k ; |
053 | break 2; |
054 | } |
055 | } |
056 | } elseif ( is_callable ( $v )) { |
057 | if (true == call_user_func ( $v )) { |
058 | $section = $k ; |
059 | break ; |
060 | } |
061 | } else { |
062 | throw new ErrorException ( 'Wrong map value in ' . __CLASS__ ); |
063 | } |
064 | } |
065 | $this ->config = $config -> $section ; |
066 | } |
067 | |
068 | /** |
069 | * 总是返回配置对象 |
070 | * |
071 | * @return mixed |
072 | */ |
073 | function __get( $key ) { |
074 | if (isset ( $this ->config-> $key )) { |
075 | return $this ->config-> $key ; |
076 | } |
077 | } |
078 | |
079 | /** |
080 | * 切分 |
081 | * |
082 | * @param stdClass $v |
083 | * @param string $k1 |
084 | * @param mixed $v1 |
085 | */ |
086 | private function split( $v , $k1 , $v1 ) { |
087 | $keys = explode ( '.' , $k1 ); |
088 | $last = array_pop ( $keys ); |
089 | $node = $v ; |
090 | foreach ( $keys as $v2 ) { |
091 | if (! isset ( $node -> $v2 )) { |
092 | $node -> $v2 = new stdClass (); |
093 | } |
094 | $node = $node -> $v2 ; |
095 | } |
096 | $node -> $last = $v1 ; |
097 | if ( count ( $keys ) > 0) { |
098 | unset ( $v -> $k1 ); |
099 | } |
100 | } |
101 | |
102 | /** |
103 | * parseIni |
104 | * |
105 | * @param object $conf |
106 | * @return stdClass |
107 | */ |
108 | private function parseIni( $conf ) { |
109 | $confObj = new stdClass (); |
110 | foreach ( $conf as $k => $v ) { |
111 | // 是section |
112 | if ( is_array ( $v )) { |
113 | $confObj -> $k = ( object ) $v ; |
114 | foreach ( $v as $k1 => $v1 ) { |
115 | call_user_func ( array ( |
116 | $this , |
117 | 'split' ), $confObj -> $k , $k1 , $v1 ); |
118 | } |
119 | } else { |
120 | call_user_func ( array ( |
121 | $this , |
122 | 'split' ), $confObj , $k , $v ); |
123 | } |
124 | } |
125 | unset ( $conf ); |
126 | // 处理继承 |
127 | foreach ( $confObj as $k => $v ) { |
128 | if (false !== strpos ( $k , ':' )) { |
129 | if (0 === strpos ( $k , ':' )) { |
130 | throw new ErrorException ( 'config ' . $k . ' is invalid, ' : ' can' t be the first char' ); |
131 | } elseif (1 < substr_count ( $k , ':' )) { |
132 | throw new ErrorException ( 'config ' . $k . ' is invalid, ' : ' can appear only once' ); |
133 | } else { |
134 | $keys = explode ( ':' , $k ); |
135 | if (! isset ( $confObj -> $keys [1] )) { |
136 | throw new ErrorException ( 'parent section ' . $keys [1] . ' doesn' t exist in config file' ); |
137 | } else { |
138 | if (isset ( $confObj -> $keys [0] )) { |
139 | throw new ErrorException ( 'config is invalid, ' . $keys [0] . ' and ' . $k . ' conflicts' ); |
140 | } else { |
141 | $confObj -> $keys [0] = $this ->deepCloneR ( $confObj -> $keys [1] ); |
142 | $this ->objectMergeR ( $confObj -> $keys [0], $v ); |
143 | unset ( $confObj -> $k ); |
144 | } |
145 | } |
146 | } |
147 | } |
148 | } |
149 | return $confObj ; |
150 | } |
151 | |
152 | /** |
153 | * php默认是浅克隆,函数实现深克隆 |
154 | * |
155 | * @param object $obj |
156 | * @return object $obj |
157 | */ |
158 | private function deepCloneR( $obj ) { |
159 | $objClone = clone $obj ; |
160 | foreach ( $objClone as $k => $v ) { |
161 | if ( is_object ( $v )) { |
162 | $objClone -> $k = $this ->deepCloneR ( $v ); |
163 | } |
164 | } |
165 | return $objClone ; |
166 | } |
167 | |
168 | /** |
169 | * 递归的合并两个对象 |
170 | * |
171 | * @param unknown $obj1 |
172 | * @param unknown $obj2 |
173 | */ private function objectMergeR( $obj1 , $obj2 ) { |
174 | foreach ( $obj2 as $k => $v ) { |
175 | if ( is_object ( $v ) && isset ( $obj1 -> $k ) && is_object ( $obj1 -> $k )) { |
176 | $this ->objectMergeR ( $obj1 -> $k , $v ); |
177 | } else { |
178 | $obj1 -> $k = $v ; |
179 | } |
180 | } |
181 | } |
182 | } |
简单使用:
1 | $_ENV [ 'config' ] = new Config ( file_get_contents ( __DIR__ . '/config.ini' ), array ( |
2 | 'development' => array ( |
3 | 'localhost.localdomain' , |
4 | 'localhost' |
5 | ), |
6 | 'production' => array () |
7 | ) ); |
配置文件示例:
01 | [product] |
02 | db. default .dsn= "mysql:host=127.0.0.1;dbname=default" |
03 | db. default .username=root |
04 | db. default .password=123456 |
05 |
06 | admin.username=admin |
07 | admin.password=123456 |
08 |
09 | php. error_reporting =E_ALL |
10 | php.display_errors=no |
11 | php.log_errors=yes |
12 | php. error_log =APP_PATH '/resource/log/phpError.log' |
13 | php.session.save_path=APP_PATH '/resource/data/session' |
14 |
15 | [development:product] |
16 | db.test1.dsn= "mysql:host=127.0.0.1;dbname=test1" |
17 | db.test1.username=root |
18 | db.test1.password=123456 |
19 | php.display_errors=yes |