*/ /** * Zend_Config */ require_once 'Zend/Config.php'; /** * */ class Zend_Config_Yaml extends Zend_Config { /** * directory for storing cached data parsed YAML files. */ private static $_cacheDir = null; /** * set or reset a cache directory. * * @param mixed $directory if null, not cache. if string, it will cache directory * @exception if directory not exists, is not writable */ public static function setCacheDirectory($directory) { if (null === $directory) { self::$_cacheDir = null; } else { if (realpath($directory) === false) { throw new Zend_Config_Exception('[' . $directory . '] not exists'); } if (!is_dir($directory)) { throw new Zend_Config_Exception('[' . $directory . '] is not directory'); } if (!is_writable($directory)) { throw new Zend_Config_Exception('[' . $directory . '] is not writable'); } self::$_cacheDir = realpath($directory); } } /** * Loads the section $section from the config file $filename for * access facilitated by nested object properties. * * Sections are defined in the YAML as children of the root element. * * If any keys with $section are called "extends", then the section * pointed to by the "extends" is then included into the properties. * Note that the keys in $section will override any keys of the same * name in the sections that have been included via "extends". * * example YAML file: * all: * db: * connection: database * hostname: live * * "staging : all": * hostname: staging * * after calling $data = new Zend_Config_Yaml($file, 'staging'); then * $data->hostname === "staging" * $data->db->connection === "database" * * @param string $filename * @param mixed $section * @param boolean $allowModifications * @throws Zend_Config_Exception */ public function __construct($filename, $section, $allowModifications = false) { if (empty($filename)) { throw new Zend_Config_Exception('filename is not set'); } if (!file_exists($filename) || !is_readable($filename)) { throw new Zend_Config_Exception('[' . $filename . '] not exists'); } $filename = realpath($filename); $yamlArray = $this->_loadYaml($filename); $preProcessedArray = array(); foreach ($yamlArray as $key => $data) { $bits = explode(':', $key); $numberOfBits = count($bits); $thisSection = trim($bits[0]); switch (count($bits)) { case 1: $preProcessedArray[$thisSection] = $data; break; case 2: $extendedSection = trim($bits[1]); $preProcessedArray[$thisSection] = array_merge(array(';extends'=>$extendedSection), $data); break; default: throw new Zend_Config_Exception("Section '$thisSection' may not extend multiple sections in $filename"); } } if (null === $section) { $dataArray = array(); foreach ($preProcessedArray as $sectionName => $sectionData) { $dataArray[$sectionName] = $this->_processExtends($preProcessedArray, $sectionName); } parent::__construct($dataArray, $allowModifications); } elseif (is_array($section)) { $dataArray = array(); foreach ($section as $sectionName) { if (!isset($preProcessedArray[$sectionName])) { throw new Zend_Config_Exception("Section '$sectionName' cannot be found in $filename"); } $dataArray = array_merge($this->_processExtends($preProcessedArray, $sectionName), $dataArray); } parent::__construct($dataArray, $allowModifications); } else { if (!isset($preProcessedArray[$section])) { throw new Zend_Config_Exception("Section '$section' cannot be found in $filename"); } parent::__construct($this->_processExtends($preProcessedArray, $section), $allowModifications); } $this->_loadedSection = $section; } /** * load a YAML file to php array using any yaml library. * * @param string $filename * @throws Zend_Config_Exception * @return array */ protected function _loadYaml($filename) { if ($this->_cacheFileExists($filename)) { return $this->_loadFromCache($filename); } if (extension_loaded('syck') && function_exists('syck_load')) { $yamlData = syck_load(file_get_contents($filename)); if ($this->_cacheEnabled()) { $this->_saveToCache($filename, $yamlData); } return $yamlData; } if (class_exists('spyc') && is_callable(array('Spyc', 'YAMLLoad'))) { $yamlData = Spyc::YAMLLoad($filename); if ($this->_cacheEnabled()) { $this->_saveToCache($filename, $yamlData); } return $yamlData; } throw new Zend_Config_Exception('YAML loader not found: ' . $filename); } /** * return if a cache dir enabled. * * @return boolean */ protected function _cacheEnabled() { return isset(self::$_cacheDir); } /** * return cached filename. * * @param string $filename YAML filename * @return string cached filename */ protected function _getCacheFilename($filename) { $fileinfo = pathinfo($filename); return self::$_cacheDir . '/' . md5($filename) . '-' . $fileinfo['basename']; } /** * */ protected function _cacheFileExists($filename) { if (!$this->_cacheEnabled()) { return false; } $cacheFile = $this->_getCacheFilename($filename); if (!file_exists($cacheFile)) { return false; } $srcMtime = filemtime($filename); $cacheMtime = filemtime($cacheFile); return ($srcMtime < $cacheMtime); } /** * */ protected function _loadFromCache($filename) { $cacheFile = $this->_getCacheFilename($filename); $fp = fopen($cacheFile, 'r'); if (!is_resource($fp) || !flock($fp, LOCK_SH)) { throw new Zend_Config_Exception('cache file not opened: ' . $cacheFile); } $cachedData = file_get_contents($cacheFile); fclose($fp); if (false === $cachedData) { throw new Zend_Config_Exception('cache data not loaded: ' . $cacheFile); } $yamlData = unserialize($cachedData); if (false === $yamlData) { throw new Zend_Config_Exception('cache data is broken: ' . $cacheFile); } return $yamlData; } /** * */ protected function _saveToCache($filename, $yamlData) { $cacheFile = $this->_getCacheFilename($filename); $fp = fopen($cacheFile, 'w'); if (!is_resource($fp) || !flock($fp, LOCK_EX)) { throw new Zend_Config_Exception('cache data not opened: ' . $cacheFile); } file_put_contents($cacheFile, serialize($yamlData)); fclose($fp); } /** * Helper function to process each element in the section and handle * the "extends" inheritance attribute. * * @param array $yamlArray * @param string $section * @param array $config * @throws Zend_Config_Exception * @return array */ protected function _processExtends($yamlArray, $section, $config = array()) { $thisSection = $yamlArray[$section]; foreach ($thisSection as $key => $value) { if (strtolower($key) == ';extends') { if (isset($yamlArray[$value])) { $this->_assertValidExtend($section, $value); $config = $this->_processExtends($yamlArray, $value, $config); } else { throw new Zend_Config_Exception("Section '$section' cannot be found"); } } } $config = $this->_arrayMergeRecursive($config, $thisSection); return $config; } /** * Merge two arrays recursively, overwriting keys of the same name name * in $array1 with the value in $array2. * * @param array $array1 * @param array $array2 * @return array */ protected function _arrayMergeRecursive($array1, $array2) { if (is_array($array1) && is_array($array2)) { foreach ($array2 as $key => $value) { if (isset($array1[$key])) { $array1[$key] = $this->_arrayMergeRecursive($array1[$key], $value); } else { $array1[$key] = $value; } } } else { $array1 = $array2; } return $array1; } }