File indexing completed on 2024-05-12 06:02:21

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category  Zend
0016  * @package   Zend_Config
0017  * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0018  * @license   http://framework.zend.com/license/new-bsd     New BSD License
0019  * @version   $Id$
0020  */
0021 
0022 /**
0023  * @see Zend_Config
0024  */
0025 // require_once 'Zend/Config.php';
0026 
0027 /**
0028  * YAML Adapter for Zend_Config
0029  *
0030  * @category  Zend
0031  * @package   Zend_Config
0032  * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0033  * @license   http://framework.zend.com/license/new-bsd     New BSD License
0034  */
0035 class Zend_Config_Yaml extends Zend_Config
0036 {
0037     /**
0038      * Attribute name that indicates what section a config extends from
0039      */
0040     const EXTENDS_NAME = "_extends";
0041 
0042     /**
0043      * Whether to skip extends or not
0044      *
0045      * @var boolean
0046      */
0047     protected $_skipExtends = false;
0048 
0049     /**
0050      * What to call when we need to decode some YAML?
0051      *
0052      * @var callable
0053      */
0054     protected $_yamlDecoder = array(__CLASS__, 'decode');
0055 
0056     /**
0057      * Whether or not to ignore constants in parsed YAML
0058      * @var bool
0059      */
0060     protected static $_ignoreConstants = false;
0061 
0062     /**
0063      * Indicate whether parser should ignore constants or not
0064      *
0065      * @param  bool $flag
0066      * @return void
0067      */
0068     public static function setIgnoreConstants($flag)
0069     {
0070         self::$_ignoreConstants = (bool) $flag;
0071     }
0072 
0073     /**
0074      * Whether parser should ignore constants or not
0075      *
0076      * @return bool
0077      */
0078     public static function ignoreConstants()
0079     {
0080         return self::$_ignoreConstants;
0081     }
0082 
0083     /**
0084      * Get callback for decoding YAML
0085      *
0086      * @return callable
0087      */
0088     public function getYamlDecoder()
0089     {
0090         return $this->_yamlDecoder;
0091     }
0092 
0093     /**
0094      * Set callback for decoding YAML
0095      *
0096      * @param  callable $yamlDecoder the decoder to set
0097      * @return Zend_Config_Yaml
0098      */
0099     public function setYamlDecoder($yamlDecoder)
0100     {
0101         if (!is_callable($yamlDecoder)) {
0102             // require_once 'Zend/Config/Exception.php';
0103             throw new Zend_Config_Exception('Invalid parameter to setYamlDecoder() - must be callable');
0104         }
0105 
0106         $this->_yamlDecoder = $yamlDecoder;
0107         return $this;
0108     }
0109 
0110     /**
0111      * Loads the section $section from the config file encoded as YAML
0112      *
0113      * Sections are defined as properties of the main object
0114      *
0115      * In order to extend another section, a section defines the "_extends"
0116      * property having a value of the section name from which the extending
0117      * section inherits values.
0118      *
0119      * Note that the keys in $section will override any keys of the same
0120      * name in the sections that have been included via "_extends".
0121      *
0122      * Options may include:
0123      * - allow_modifications: whether or not the config object is mutable
0124      * - skip_extends: whether or not to skip processing of parent configuration
0125      * - yaml_decoder: a callback to use to decode the Yaml source
0126      *
0127      * @param  string        $yaml     YAML file to process
0128      * @param  mixed         $section  Section to process
0129      * @param  array|boolean $options
0130      */
0131     public function __construct($yaml, $section = null, $options = false)
0132     {
0133         if (empty($yaml)) {
0134             // require_once 'Zend/Config/Exception.php';
0135             throw new Zend_Config_Exception('Filename is not set');
0136         }
0137 
0138         $ignoreConstants    = $staticIgnoreConstants = self::ignoreConstants();
0139         $allowModifications = false;
0140         if (is_bool($options)) {
0141             $allowModifications = $options;
0142         } elseif (is_array($options)) {
0143             foreach ($options as $key => $value) {
0144                 switch (strtolower($key)) {
0145                     case 'allow_modifications':
0146                     case 'allowmodifications':
0147                         $allowModifications = (bool) $value;
0148                         break;
0149                     case 'skip_extends':
0150                     case 'skipextends':
0151                         $this->_skipExtends = (bool) $value;
0152                         break;
0153                     case 'ignore_constants':
0154                     case 'ignoreconstants':
0155                         $ignoreConstants = (bool) $value;
0156                         break;
0157                     case 'yaml_decoder':
0158                     case 'yamldecoder':
0159                         $this->setYamlDecoder($value);
0160                         break;
0161                     default:
0162                         break;
0163                 }
0164             }
0165         }
0166 
0167         // Suppress warnings and errors while loading file
0168         set_error_handler(array($this, '_loadFileErrorHandler'));
0169         $yaml = file_get_contents($yaml);
0170         restore_error_handler();
0171 
0172         // Check if there was a error while loading file
0173         if ($this->_loadFileErrorStr !== null) {
0174             // require_once 'Zend/Config/Exception.php';
0175             throw new Zend_Config_Exception($this->_loadFileErrorStr);
0176         }
0177 
0178         // Override static value for ignore_constants if provided in $options
0179         self::setIgnoreConstants($ignoreConstants);
0180 
0181         // Parse YAML
0182         $config = call_user_func($this->getYamlDecoder(), $yaml);
0183 
0184         // Reset original static state of ignore_constants
0185         self::setIgnoreConstants($staticIgnoreConstants);
0186 
0187         if (null === $config) {
0188             // decode failed
0189             // require_once 'Zend/Config/Exception.php';
0190             throw new Zend_Config_Exception("Error parsing YAML data");
0191         }
0192 
0193         if (null === $section) {
0194             $dataArray = array();
0195             foreach ($config as $sectionName => $sectionData) {
0196                 $dataArray[$sectionName] = $this->_processExtends($config, $sectionName);
0197             }
0198             parent::__construct($dataArray, $allowModifications);
0199         } elseif (is_array($section)) {
0200             $dataArray = array();
0201             foreach ($section as $sectionName) {
0202                 if (!isset($config[$sectionName])) {
0203                     // require_once 'Zend/Config/Exception.php';
0204                     throw new Zend_Config_Exception(sprintf(
0205                         'Section "%s" cannot be found', 
0206                         implode(' ', (array)$section)
0207                     ));
0208                 }
0209 
0210                 $dataArray = array_merge($this->_processExtends($config, $sectionName), $dataArray);
0211             }
0212             parent::__construct($dataArray, $allowModifications);
0213         } else {
0214             if (!isset($config[$section])) {
0215                 // require_once 'Zend/Config/Exception.php';
0216                 throw new Zend_Config_Exception(sprintf(
0217                     'Section "%s" cannot be found', 
0218                     implode(' ', (array)$section)
0219                 ));
0220             }
0221 
0222             $dataArray = $this->_processExtends($config, $section);
0223             if (!is_array($dataArray)) {
0224                 // Section in the yaml data contains just one top level string
0225                 $dataArray = array($section => $dataArray);
0226             }
0227             parent::__construct($dataArray, $allowModifications);
0228         }
0229 
0230         $this->_loadedSection = $section;
0231     }
0232 
0233     /**
0234      * Helper function to process each element in the section and handle
0235      * the "_extends" inheritance attribute.
0236      *
0237      * @param  array            $data Data array to process
0238      * @param  string           $section Section to process
0239      * @param  array            $config  Configuration which was parsed yet
0240      * @return array
0241      * @throws Zend_Config_Exception When $section cannot be found
0242      */
0243     protected function _processExtends(array $data, $section, array $config = array())
0244     {
0245         if (!isset($data[$section])) {
0246             // require_once 'Zend/Config/Exception.php';
0247             throw new Zend_Config_Exception(sprintf('Section "%s" cannot be found', $section));
0248         }
0249 
0250         $thisSection  = $data[$section];
0251 
0252         if (is_array($thisSection) && isset($thisSection[self::EXTENDS_NAME])) {
0253             $this->_assertValidExtend($section, $thisSection[self::EXTENDS_NAME]);
0254 
0255             if (!$this->_skipExtends) {
0256                 $config = $this->_processExtends($data, $thisSection[self::EXTENDS_NAME], $config);
0257             }
0258             unset($thisSection[self::EXTENDS_NAME]);
0259         }
0260 
0261         $config = $this->_arrayMergeRecursive($config, $thisSection);
0262 
0263         return $config;
0264     }
0265 
0266     /**
0267      * Very dumb YAML parser
0268      *
0269      * Until we have Zend_Yaml...
0270      *
0271      * @param  string $yaml YAML source
0272      * @return array Decoded data
0273      */
0274     public static function decode($yaml)
0275     {
0276         $lines = explode("\n", $yaml);
0277         reset($lines);
0278         return self::_decodeYaml(0, $lines);
0279     }
0280 
0281     /**
0282      * Service function to decode YAML
0283      *
0284      * @param  int $currentIndent Current indent level
0285      * @param  array $lines  YAML lines
0286      * @return array|string
0287      */
0288     protected static function _decodeYaml($currentIndent, &$lines)
0289     {
0290         $config   = array();
0291         $inIndent = false;
0292         while (list($n, $line) = each($lines)) {
0293             $lineno = $n + 1;
0294 
0295             $line = rtrim(preg_replace("/#.*$/", "", $line));
0296             if (strlen($line) == 0) {
0297                 continue;
0298             }
0299 
0300             $indent = strspn($line, " ");
0301 
0302             // line without the spaces
0303             $line = trim($line);
0304             if (strlen($line) == 0) {
0305                 continue;
0306             }
0307 
0308             if ($indent < $currentIndent) {
0309                 // this level is done
0310                 prev($lines);
0311                 return $config;
0312             }
0313 
0314             if (!$inIndent) {
0315                 $currentIndent = $indent;
0316                 $inIndent      = true;
0317             }
0318 
0319             if (preg_match("/(?!-)([\w\-]+):\s*(.*)/", $line, $m)) {
0320                 // key: value
0321                 if (strlen($m[2])) {
0322                     // simple key: value
0323                     $value = preg_replace("/#.*$/", "", $m[2]);
0324                     $value = self::_parseValue($value);
0325                 } else {
0326                     // key: and then values on new lines
0327                     $value = self::_decodeYaml($currentIndent + 1, $lines);
0328                     if (is_array($value) && !count($value)) {
0329                         $value = "";
0330                     }
0331                 }
0332                 $config[$m[1]] = $value;
0333             } elseif ($line[0] == "-") {
0334                 // item in the list:
0335                 // - FOO
0336                 if (strlen($line) > 2) {
0337                     $value = substr($line, 2);
0338 
0339                     $config[] = self::_parseValue($value);
0340                 } else {
0341                     $config[] = self::_decodeYaml($currentIndent + 1, $lines);
0342                 }
0343             } else {
0344                 // require_once 'Zend/Config/Exception.php';
0345                 throw new Zend_Config_Exception(sprintf(
0346                     'Error parsing YAML at line %d - unsupported syntax: "%s"',
0347                     $lineno, $line
0348                 ));
0349             }
0350         }
0351         return $config;
0352     }
0353 
0354     /**
0355      * Parse values
0356      *
0357      * @param string $value
0358      * @return string
0359      */
0360     protected static function _parseValue($value)
0361     {
0362         $value = trim($value);
0363 
0364         // remove quotes from string.
0365         if ('"' == $value['0']) {
0366             if ('"' == $value[count($value) -1]) {
0367                 $value = substr($value, 1, -1);
0368             }
0369         } elseif ('\'' == $value['0'] && '\'' == $value[count($value) -1]) {
0370             $value = strtr($value, array("''" => "'", "'" => ''));
0371         }
0372 
0373         // Check for booleans and constants
0374         if (preg_match('/^(t(rue)?|on|y(es)?)$/i', $value)) {
0375             $value = true;
0376         } elseif (preg_match('/^(f(alse)?|off|n(o)?)$/i', $value)) {
0377             $value = false;
0378         } elseif (strcasecmp($value, 'null') === 0) {
0379             $value = null;
0380         } elseif (!self::$_ignoreConstants) {
0381             // test for constants
0382             $value = self::_replaceConstants($value);
0383         }
0384 
0385         return $value;
0386     }
0387 
0388     /**
0389      * Replace any constants referenced in a string with their values
0390      *
0391      * @param  string $value
0392      * @return string
0393      */
0394     protected static function _replaceConstants($value)
0395     {
0396         foreach (self::_getConstants() as $constant) {
0397             if (strstr($value, $constant)) {
0398                 $value = str_replace($constant, constant($constant), $value);
0399             }
0400         }
0401         return $value;
0402     }
0403 
0404     /**
0405      * Get (reverse) sorted list of defined constant names
0406      *
0407      * @return array
0408      */
0409     protected static function _getConstants()
0410     {
0411         $constants = array_keys(get_defined_constants());
0412         rsort($constants, SORT_STRING);
0413         return $constants;
0414     }
0415 }