Yaml.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Config
  17. * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
  18. * @license http://framework.zend.com/license/new-bsd New BSD License
  19. */
  20. /**
  21. * @see Zend_Config
  22. */
  23. require_once 'Zend/Config.php';
  24. /**
  25. * XML Adapter for Zend_Config
  26. *
  27. * @category Zend
  28. * @package Zend_Config
  29. * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
  30. * @license http://framework.zend.com/license/new-bsd New BSD License
  31. */
  32. class Zend_Config_Yaml extends Zend_Config
  33. {
  34. /**
  35. * Attribute name that indicates what section a config extends from
  36. */
  37. const EXTENDS_NAME = "_extends";
  38. /**
  39. * Whether to skip extends or not
  40. *
  41. * @var boolean
  42. */
  43. protected $_skipExtends = false;
  44. /**
  45. * What to call when we need to decode some YAML?
  46. *
  47. * @var callable
  48. */
  49. protected $_yamlDecoder = array(__CLASS__, 'decode');
  50. /**
  51. * Whether or not to ignore constants in parsed YAML
  52. * @var bool
  53. */
  54. protected static $_ignoreConstants = false;
  55. /**
  56. * Indicate whether parser should ignore constants or not
  57. *
  58. * @param bool $flag
  59. * @return void
  60. */
  61. public static function setIgnoreConstants($flag)
  62. {
  63. self::$_ignoreConstants = (bool) $flag;
  64. }
  65. /**
  66. * Whether parser should ignore constants or not
  67. *
  68. * @return bool
  69. */
  70. public static function ignoreConstants()
  71. {
  72. return self::$_ignoreConstants;
  73. }
  74. /**
  75. * Get callback for decoding YAML
  76. *
  77. * @return callable
  78. */
  79. public function getYamlDecoder()
  80. {
  81. return $this->_yamlDecoder;
  82. }
  83. /**
  84. * Set callback for decoding YAML
  85. *
  86. * @param $yamlDecoder the decoder to set
  87. * @return Zend_Config_Yaml
  88. */
  89. public function setYamlDecoder($yamlDecoder)
  90. {
  91. if (!is_callable($yamlDecoder)) {
  92. require_once 'Zend/Config/Exception.php';
  93. throw new Zend_Config_Exception('Invalid parameter to setYamlDecoder() - must be callable');
  94. }
  95. $this->_yamlDecoder = $yamlDecoder;
  96. return $this;
  97. }
  98. /**
  99. * Loads the section $section from the config file encoded as YAML
  100. *
  101. * Sections are defined as properties of the main object
  102. *
  103. * In order to extend another section, a section defines the "_extends"
  104. * property having a value of the section name from which the extending
  105. * section inherits values.
  106. *
  107. * Note that the keys in $section will override any keys of the same
  108. * name in the sections that have been included via "_extends".
  109. *
  110. * Options may include:
  111. * - allow_modifications: whether or not the config object is mutable
  112. * - skip_extends: whether or not to skip processing of parent configuration
  113. * - yaml_decoder: a callback to use to decode the Yaml source
  114. *
  115. * @param string $yaml YAML file to process
  116. * @param mixed $section Section to process
  117. * @param boolean $options Whether modifiacations are allowed at runtime
  118. */
  119. public function __construct($yaml, $section = null, $options = false)
  120. {
  121. if (empty($yaml)) {
  122. require_once 'Zend/Config/Exception.php';
  123. throw new Zend_Config_Exception('Filename is not set');
  124. }
  125. $ignoreConstants = $staticIgnoreConstants = self::ignoreConstants();
  126. $allowModifications = false;
  127. if (is_bool($options)) {
  128. $allowModifications = $options;
  129. } elseif (is_array($options)) {
  130. foreach ($options as $key => $value) {
  131. switch (strtolower($key)) {
  132. case 'allow_modifications':
  133. case 'allowmodifications':
  134. $allowModifications = (bool) $value;
  135. break;
  136. case 'skip_extends':
  137. case 'skipextends':
  138. $this->_skipExtends = (bool) $value;
  139. break;
  140. case 'ignore_constants':
  141. case 'ignoreconstants':
  142. $ignoreConstants = (bool) $value;
  143. break;
  144. case 'yaml_decoder':
  145. case 'yamldecoder':
  146. $this->setYamlDecoder($value);
  147. break;
  148. default:
  149. break;
  150. }
  151. }
  152. }
  153. // Suppress warnings and errors while loading file
  154. set_error_handler(array($this, '_loadFileErrorHandler'));
  155. $yaml = file_get_contents($yaml);
  156. restore_error_handler();
  157. // Check if there was a error while loading file
  158. if ($this->_loadFileErrorStr !== null) {
  159. require_once 'Zend/Config/Exception.php';
  160. throw new Zend_Config_Exception($this->_loadFileErrorStr);
  161. }
  162. // Override static value for ignore_constants if provided in $options
  163. self::setIgnoreConstants($ignoreConstants);
  164. // Parse YAML
  165. $config = call_user_func($this->getYamlDecoder(), $yaml);
  166. // Reset original static state of ignore_constants
  167. self::setIgnoreConstants($staticIgnoreConstants);
  168. if (null === $config) {
  169. // decode failed
  170. require_once 'Zend/Config/Exception.php';
  171. throw new Zend_Config_Exception("Error parsing YAML data");
  172. }
  173. if (null === $section) {
  174. $dataArray = array();
  175. foreach ($config as $sectionName => $sectionData) {
  176. $dataArray[$sectionName] = $this->_processExtends($config, $sectionName);
  177. }
  178. parent::__construct($dataArray, $allowModifications);
  179. } elseif (is_array($section)) {
  180. $dataArray = array();
  181. foreach ($section as $sectionName) {
  182. if (!isset($config[$sectionName])) {
  183. require_once 'Zend/Config/Exception.php';
  184. throw new Zend_Config_Exception(sprintf('Section "%s" cannot be found', $section));
  185. }
  186. $dataArray = array_merge($this->_processExtends($config, $sectionName), $dataArray);
  187. }
  188. parent::__construct($dataArray, $allowModifications);
  189. } else {
  190. if (!isset($config[$section])) {
  191. require_once 'Zend/Config/Exception.php';
  192. throw new Zend_Config_Exception(sprintf('Section "%s" cannot be found', $section));
  193. }
  194. $dataArray = $this->_processExtends($config, $section);
  195. if (!is_array($dataArray)) {
  196. // Section in the yaml data contains just one top level string
  197. $dataArray = array($section => $dataArray);
  198. }
  199. parent::__construct($dataArray, $allowModifications);
  200. }
  201. $this->_loadedSection = $section;
  202. }
  203. /**
  204. * Helper function to process each element in the section and handle
  205. * the "_extends" inheritance attribute.
  206. *
  207. * @param array $data Data array to process
  208. * @param string $section Section to process
  209. * @param array $config Configuration which was parsed yet
  210. * @return array
  211. * @throws Zend_Config_Exception When $section cannot be found
  212. */
  213. protected function _processExtends(array $data, $section, array $config = array())
  214. {
  215. if (!isset($data[$section])) {
  216. require_once 'Zend/Config/Exception.php';
  217. throw new Zend_Config_Exception(sprintf('Section "%s" cannot be found', $section));
  218. }
  219. $thisSection = $data[$section];
  220. if (is_array($thisSection) && isset($thisSection[self::EXTENDS_NAME])) {
  221. $this->_assertValidExtend($section, $thisSection[self::EXTENDS_NAME]);
  222. if (!$this->_skipExtends) {
  223. $config = $this->_processExtends($data, $thisSection[self::EXTENDS_NAME], $config);
  224. }
  225. unset($thisSection[self::EXTENDS_NAME]);
  226. }
  227. $config = $this->_arrayMergeRecursive($config, $thisSection);
  228. return $config;
  229. }
  230. /**
  231. * Very dumb YAML parser
  232. *
  233. * Until we have Zend_Yaml...
  234. *
  235. * @param string $yaml YAML source
  236. * @return array Decoded data
  237. */
  238. public static function decode($yaml)
  239. {
  240. $lines = explode("\n", $yaml);
  241. reset($lines);
  242. return self::_decodeYaml(0, $lines);
  243. }
  244. /**
  245. * Service function to decode YAML
  246. *
  247. * @param int $currentIndent Current indent level
  248. * @param array $lines YAML lines
  249. * @return array|string
  250. */
  251. protected static function _decodeYaml($currentIndent, &$lines)
  252. {
  253. $config = array();
  254. $inIndent = false;
  255. while (list($n, $line) = each($lines)) {
  256. $lineno = $n+1;
  257. if (strlen($line) == 0) {
  258. continue;
  259. }
  260. if ($line[0] == '#') {
  261. // comment
  262. continue;
  263. }
  264. $indent = strspn($line, " ");
  265. // line without the spaces
  266. $line = trim($line);
  267. if (strlen($line) == 0) {
  268. continue;
  269. }
  270. if ($indent < $currentIndent) {
  271. // this level is done
  272. prev($lines);
  273. return $config;
  274. }
  275. if (!$inIndent) {
  276. $currentIndent = $indent;
  277. $inIndent = true;
  278. }
  279. if (preg_match("/(\w+):\s*(.*)/", $line, $m)) {
  280. // key: value
  281. if ($m[2]) {
  282. // simple key: value
  283. $value = $m[2];
  284. // Check for booleans and constants
  285. if (preg_match('/^(t(rue)?|on|y(es)?)$/i', $value)) {
  286. $value = true;
  287. } elseif (preg_match('/^(f(alse)?|off|n(o)?)$/i', $value)) {
  288. $value = false;
  289. } elseif (!self::$_ignoreConstants) {
  290. // test for constants
  291. $value = self::_replaceConstants($value);
  292. }
  293. } else {
  294. // key: and then values on new lines
  295. $value = self::_decodeYaml($currentIndent + 1, $lines);
  296. if (is_array($value) && !count($value)) {
  297. $value = "";
  298. }
  299. }
  300. $config[$m[1]] = $value;
  301. } elseif ($line[0] == "-") {
  302. // item in the list:
  303. // - FOO
  304. if (strlen($line) > 2) {
  305. $config[] = substr($line, 2);
  306. } else {
  307. $config[] = self::_decodeYaml($currentIndent + 1, $lines);
  308. }
  309. } else {
  310. require_once 'Zend/Config/Exception.php';
  311. throw new Zend_Config_Exception(sprintf(
  312. 'Error parsing YAML at line %d - unsupported syntax: "%s"',
  313. $lineno, $line
  314. ));
  315. }
  316. }
  317. return $config;
  318. }
  319. /**
  320. * Replace any constants referenced in a string with their values
  321. *
  322. * @param string $value
  323. * @return string
  324. */
  325. protected static function _replaceConstants($value)
  326. {
  327. foreach (self::_getConstants() as $constant) {
  328. if (strstr($value, $constant)) {
  329. $value = str_replace($constant, constant($constant), $value);
  330. }
  331. }
  332. return $value;
  333. }
  334. /**
  335. * Get (reverse) sorted list of defined constant names
  336. *
  337. * @return array
  338. */
  339. protected static function _getConstants()
  340. {
  341. $constants = array_keys(get_defined_constants());
  342. rsort($constants, SORT_STRING);
  343. return $constants;
  344. }
  345. }