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