IniConfig.php 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 2.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Configure\Engine;
  16. use Cake\Configure\ConfigEngineInterface;
  17. use Cake\Core\App;
  18. use Cake\Error;
  19. use Cake\Utility\Hash;
  20. /**
  21. * Ini file configuration engine.
  22. *
  23. * Since IniConfig uses parse_ini_file underneath, you should be aware that this
  24. * class shares the same behavior, especially with regards to boolean and null values.
  25. *
  26. * In addition to the native `parse_ini_file` features, IniConfig also allows you
  27. * to create nested array structures through usage of `.` delimited names. This allows
  28. * you to create nested arrays structures in an ini config file. For example:
  29. *
  30. * `db.password = secret` would turn into `['db' => ['password' => 'secret']]`
  31. *
  32. * You can nest properties as deeply as needed using `.`'s. In addition to using `.` you
  33. * can use standard ini section notation to create nested structures:
  34. *
  35. * {{{
  36. * [section]
  37. * key = value
  38. * }}}
  39. *
  40. * Once loaded into Configure, the above would be accessed using:
  41. *
  42. * `Configure::read('section.key');
  43. *
  44. * You can combine `.` separated values with sections to create more deeply
  45. * nested structures.
  46. *
  47. * IniConfig also manipulates how the special ini values of
  48. * 'yes', 'no', 'on', 'off', 'null' are handled. These values will be
  49. * converted to their boolean equivalents.
  50. *
  51. * @see http://php.net/parse_ini_file
  52. */
  53. class IniConfig implements ConfigEngineInterface {
  54. /**
  55. * The path to read ini files from.
  56. *
  57. * @var array
  58. */
  59. protected $_path;
  60. /**
  61. * The section to read, if null all sections will be read.
  62. *
  63. * @var string
  64. */
  65. protected $_section;
  66. /**
  67. * Build and construct a new ini file parser. The parser can be used to read
  68. * ini files that are on the filesystem.
  69. *
  70. * @param string $path Path to load ini config files from. Defaults to APP . 'Config' . DS
  71. * @param string $section Only get one section, leave null to parse and fetch
  72. * all sections in the ini file.
  73. */
  74. public function __construct($path = null, $section = null) {
  75. if (!$path) {
  76. $path = APP . 'Config/';
  77. }
  78. $this->_path = $path;
  79. $this->_section = $section;
  80. }
  81. /**
  82. * Read an ini file and return the results as an array.
  83. *
  84. * For backwards compatibility, acl.ini.php will be treated specially until 3.0.
  85. *
  86. * @param string $key The identifier to read from. If the key has a . it will be treated
  87. * as a plugin prefix. The chosen file must be on the engine's path.
  88. * @return array Parsed configuration values.
  89. * @throws \Cake\Error\Exception when files don't exist.
  90. * Or when files contain '..' as this could lead to abusive reads.
  91. */
  92. public function read($key) {
  93. if (strpos($key, '..') !== false) {
  94. throw new Error\Exception('Cannot load configuration files with ../ in them.');
  95. }
  96. $file = $this->_getFilePath($key);
  97. if (!is_file($file)) {
  98. throw new Error\Exception(sprintf('Could not load configuration file: %s', $file));
  99. }
  100. $contents = parse_ini_file($file, true);
  101. if (!empty($this->_section) && isset($contents[$this->_section])) {
  102. $values = $this->_parseNestedValues($contents[$this->_section]);
  103. } else {
  104. $values = array();
  105. foreach ($contents as $section => $attribs) {
  106. if (is_array($attribs)) {
  107. $values[$section] = $this->_parseNestedValues($attribs);
  108. } else {
  109. $parse = $this->_parseNestedValues(array($attribs));
  110. $values[$section] = array_shift($parse);
  111. }
  112. }
  113. }
  114. return $values;
  115. }
  116. /**
  117. * parses nested values out of keys.
  118. *
  119. * @param array $values Values to be exploded.
  120. * @return array Array of values exploded
  121. */
  122. protected function _parseNestedValues($values) {
  123. foreach ($values as $key => $value) {
  124. if ($value === '1') {
  125. $value = true;
  126. }
  127. if ($value === '') {
  128. $value = false;
  129. }
  130. unset($values[$key]);
  131. if (strpos($key, '.') !== false) {
  132. $values = Hash::insert($values, $key, $value);
  133. } else {
  134. $values[$key] = $value;
  135. }
  136. }
  137. return $values;
  138. }
  139. /**
  140. * Dumps the state of Configure data into an ini formatted string.
  141. *
  142. * @param string $key The identifier to write to. If the key has a . it will be treated
  143. * as a plugin prefix.
  144. * @param array $data The data to convert to ini file.
  145. * @return int Bytes saved.
  146. */
  147. public function dump($key, $data) {
  148. $result = array();
  149. foreach ($data as $k => $value) {
  150. $isSection = false;
  151. if ($k[0] !== '[') {
  152. $result[] = "[$k]";
  153. $isSection = true;
  154. }
  155. if (is_array($value)) {
  156. $kValues = Hash::flatten($value, '.');
  157. foreach ($kValues as $k2 => $v) {
  158. $result[] = "$k2 = " . $this->_value($v);
  159. }
  160. }
  161. if ($isSection) {
  162. $result[] = '';
  163. }
  164. }
  165. $contents = trim(implode("\n", $result));
  166. $filename = $this->_getFilePath($key);
  167. return file_put_contents($filename, $contents);
  168. }
  169. /**
  170. * Converts a value into the ini equivalent
  171. *
  172. * @param mixed $value Value to export.
  173. * @return string String value for ini file.
  174. */
  175. protected function _value($value) {
  176. if ($value === null) {
  177. return 'null';
  178. }
  179. if ($value === true) {
  180. return 'true';
  181. }
  182. if ($value === false) {
  183. return 'false';
  184. }
  185. return (string)$value;
  186. }
  187. /**
  188. * Get file path
  189. *
  190. * @param string $key The identifier to write to. If the key has a . it will be treated
  191. * as a plugin prefix.
  192. * @return string Full file path
  193. */
  194. protected function _getFilePath($key) {
  195. if (substr($key, -8) === '.ini.php') {
  196. $key = substr($key, 0, -8);
  197. list($plugin, $key) = pluginSplit($key);
  198. $key .= '.ini.php';
  199. } else {
  200. if (substr($key, -4) === '.ini') {
  201. $key = substr($key, 0, -4);
  202. }
  203. list($plugin, $key) = pluginSplit($key);
  204. $key .= '.ini';
  205. }
  206. if ($plugin) {
  207. $file = App::path('Config', $plugin)[0] . $key;
  208. } else {
  209. $file = $this->_path . $key;
  210. }
  211. return $file;
  212. }
  213. }