web/lib/Zend/Json/Encoder.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_Json
       
    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: Encoder.php 22452 2010-06-18 18:13:23Z ralph $
       
    20  */
       
    21 
       
    22 /**
       
    23  * Encode PHP constructs to JSON
       
    24  *
       
    25  * @category   Zend
       
    26  * @package    Zend_Json
       
    27  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    28  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    29  */
       
    30 class Zend_Json_Encoder
       
    31 {
       
    32     /**
       
    33      * Whether or not to check for possible cycling
       
    34      *
       
    35      * @var boolean
       
    36      */
       
    37     protected $_cycleCheck;
       
    38 
       
    39     /**
       
    40      * Additional options used during encoding
       
    41      *
       
    42      * @var array
       
    43      */
       
    44     protected $_options = array();
       
    45 
       
    46     /**
       
    47      * Array of visited objects; used to prevent cycling.
       
    48      *
       
    49      * @var array
       
    50      */
       
    51     protected $_visited = array();
       
    52 
       
    53     /**
       
    54      * Constructor
       
    55      *
       
    56      * @param boolean $cycleCheck Whether or not to check for recursion when encoding
       
    57      * @param array $options Additional options used during encoding
       
    58      * @return void
       
    59      */
       
    60     protected function __construct($cycleCheck = false, $options = array())
       
    61     {
       
    62         $this->_cycleCheck = $cycleCheck;
       
    63         $this->_options = $options;
       
    64     }
       
    65 
       
    66     /**
       
    67      * Use the JSON encoding scheme for the value specified
       
    68      *
       
    69      * @param mixed $value The value to be encoded
       
    70      * @param boolean $cycleCheck Whether or not to check for possible object recursion when encoding
       
    71      * @param array $options Additional options used during encoding
       
    72      * @return string  The encoded value
       
    73      */
       
    74     public static function encode($value, $cycleCheck = false, $options = array())
       
    75     {
       
    76         $encoder = new self(($cycleCheck) ? true : false, $options);
       
    77 
       
    78         return $encoder->_encodeValue($value);
       
    79     }
       
    80 
       
    81     /**
       
    82      * Recursive driver which determines the type of value to be encoded
       
    83      * and then dispatches to the appropriate method. $values are either
       
    84      *    - objects (returns from {@link _encodeObject()})
       
    85      *    - arrays (returns from {@link _encodeArray()})
       
    86      *    - basic datums (e.g. numbers or strings) (returns from {@link _encodeDatum()})
       
    87      *
       
    88      * @param $value mixed The value to be encoded
       
    89      * @return string Encoded value
       
    90      */
       
    91     protected function _encodeValue(&$value)
       
    92     {
       
    93         if (is_object($value)) {
       
    94             return $this->_encodeObject($value);
       
    95         } else if (is_array($value)) {
       
    96             return $this->_encodeArray($value);
       
    97         }
       
    98 
       
    99         return $this->_encodeDatum($value);
       
   100     }
       
   101 
       
   102 
       
   103 
       
   104     /**
       
   105      * Encode an object to JSON by encoding each of the public properties
       
   106      *
       
   107      * A special property is added to the JSON object called '__className'
       
   108      * that contains the name of the class of $value. This is used to decode
       
   109      * the object on the client into a specific class.
       
   110      *
       
   111      * @param $value object
       
   112      * @return string
       
   113      * @throws Zend_Json_Exception If recursive checks are enabled and the object has been serialized previously
       
   114      */
       
   115     protected function _encodeObject(&$value)
       
   116     {
       
   117         if ($this->_cycleCheck) {
       
   118             if ($this->_wasVisited($value)) {
       
   119 
       
   120                 if (isset($this->_options['silenceCyclicalExceptions'])
       
   121                     && $this->_options['silenceCyclicalExceptions']===true) {
       
   122 
       
   123                     return '"* RECURSION (' . get_class($value) . ') *"';
       
   124 
       
   125                 } else {
       
   126                     require_once 'Zend/Json/Exception.php';
       
   127                     throw new Zend_Json_Exception(
       
   128                         'Cycles not supported in JSON encoding, cycle introduced by '
       
   129                         . 'class "' . get_class($value) . '"'
       
   130                     );
       
   131                 }
       
   132             }
       
   133 
       
   134             $this->_visited[] = $value;
       
   135         }
       
   136 
       
   137         $props = '';
       
   138 
       
   139         if ($value instanceof Iterator) {
       
   140             $propCollection = $value;
       
   141         } else {
       
   142             $propCollection = get_object_vars($value);
       
   143         }
       
   144 
       
   145         foreach ($propCollection as $name => $propValue) {
       
   146             if (isset($propValue)) {
       
   147                 $props .= ','
       
   148                         . $this->_encodeString($name)
       
   149                         . ':'
       
   150                         . $this->_encodeValue($propValue);
       
   151             }
       
   152         }
       
   153 
       
   154         return '{"__className":"' . get_class($value) . '"'
       
   155                 . $props . '}';
       
   156     }
       
   157 
       
   158 
       
   159     /**
       
   160      * Determine if an object has been serialized already
       
   161      *
       
   162      * @param mixed $value
       
   163      * @return boolean
       
   164      */
       
   165     protected function _wasVisited(&$value)
       
   166     {
       
   167         if (in_array($value, $this->_visited, true)) {
       
   168             return true;
       
   169         }
       
   170 
       
   171         return false;
       
   172     }
       
   173 
       
   174 
       
   175     /**
       
   176      * JSON encode an array value
       
   177      *
       
   178      * Recursively encodes each value of an array and returns a JSON encoded
       
   179      * array string.
       
   180      *
       
   181      * Arrays are defined as integer-indexed arrays starting at index 0, where
       
   182      * the last index is (count($array) -1); any deviation from that is
       
   183      * considered an associative array, and will be encoded as such.
       
   184      *
       
   185      * @param $array array
       
   186      * @return string
       
   187      */
       
   188     protected function _encodeArray(&$array)
       
   189     {
       
   190         $tmpArray = array();
       
   191 
       
   192         // Check for associative array
       
   193         if (!empty($array) && (array_keys($array) !== range(0, count($array) - 1))) {
       
   194             // Associative array
       
   195             $result = '{';
       
   196             foreach ($array as $key => $value) {
       
   197                 $key = (string) $key;
       
   198                 $tmpArray[] = $this->_encodeString($key)
       
   199                             . ':'
       
   200                             . $this->_encodeValue($value);
       
   201             }
       
   202             $result .= implode(',', $tmpArray);
       
   203             $result .= '}';
       
   204         } else {
       
   205             // Indexed array
       
   206             $result = '[';
       
   207             $length = count($array);
       
   208             for ($i = 0; $i < $length; $i++) {
       
   209                 $tmpArray[] = $this->_encodeValue($array[$i]);
       
   210             }
       
   211             $result .= implode(',', $tmpArray);
       
   212             $result .= ']';
       
   213         }
       
   214 
       
   215         return $result;
       
   216     }
       
   217 
       
   218 
       
   219     /**
       
   220      * JSON encode a basic data type (string, number, boolean, null)
       
   221      *
       
   222      * If value type is not a string, number, boolean, or null, the string
       
   223      * 'null' is returned.
       
   224      *
       
   225      * @param $value mixed
       
   226      * @return string
       
   227      */
       
   228     protected function _encodeDatum(&$value)
       
   229     {
       
   230         $result = 'null';
       
   231 
       
   232         if (is_int($value) || is_float($value)) {
       
   233             $result = (string) $value;
       
   234             $result = str_replace(",", ".", $result);
       
   235         } elseif (is_string($value)) {
       
   236             $result = $this->_encodeString($value);
       
   237         } elseif (is_bool($value)) {
       
   238             $result = $value ? 'true' : 'false';
       
   239         }
       
   240 
       
   241         return $result;
       
   242     }
       
   243 
       
   244 
       
   245     /**
       
   246      * JSON encode a string value by escaping characters as necessary
       
   247      *
       
   248      * @param $value string
       
   249      * @return string
       
   250      */
       
   251     protected function _encodeString(&$string)
       
   252     {
       
   253         // Escape these characters with a backslash:
       
   254         // " \ / \n \r \t \b \f
       
   255         $search  = array('\\', "\n", "\t", "\r", "\b", "\f", '"', '/');
       
   256         $replace = array('\\\\', '\\n', '\\t', '\\r', '\\b', '\\f', '\"', '\\/');
       
   257         $string  = str_replace($search, $replace, $string);
       
   258 
       
   259         // Escape certain ASCII characters:
       
   260         // 0x08 => \b
       
   261         // 0x0c => \f
       
   262         $string = str_replace(array(chr(0x08), chr(0x0C)), array('\b', '\f'), $string);
       
   263         $string = self::encodeUnicodeString($string);
       
   264 
       
   265         return '"' . $string . '"';
       
   266     }
       
   267 
       
   268 
       
   269     /**
       
   270      * Encode the constants associated with the ReflectionClass
       
   271      * parameter. The encoding format is based on the class2 format
       
   272      *
       
   273      * @param $cls ReflectionClass
       
   274      * @return string Encoded constant block in class2 format
       
   275      */
       
   276     private static function _encodeConstants(ReflectionClass $cls)
       
   277     {
       
   278         $result    = "constants : {";
       
   279         $constants = $cls->getConstants();
       
   280 
       
   281         $tmpArray = array();
       
   282         if (!empty($constants)) {
       
   283             foreach ($constants as $key => $value) {
       
   284                 $tmpArray[] = "$key: " . self::encode($value);
       
   285             }
       
   286 
       
   287             $result .= implode(', ', $tmpArray);
       
   288         }
       
   289 
       
   290         return $result . "}";
       
   291     }
       
   292 
       
   293 
       
   294     /**
       
   295      * Encode the public methods of the ReflectionClass in the
       
   296      * class2 format
       
   297      *
       
   298      * @param $cls ReflectionClass
       
   299      * @return string Encoded method fragment
       
   300      *
       
   301      */
       
   302     private static function _encodeMethods(ReflectionClass $cls)
       
   303     {
       
   304         $methods = $cls->getMethods();
       
   305         $result = 'methods:{';
       
   306 
       
   307         $started = false;
       
   308         foreach ($methods as $method) {
       
   309             if (! $method->isPublic() || !$method->isUserDefined()) {
       
   310                 continue;
       
   311             }
       
   312 
       
   313             if ($started) {
       
   314                 $result .= ',';
       
   315             }
       
   316             $started = true;
       
   317 
       
   318             $result .= '' . $method->getName(). ':function(';
       
   319 
       
   320             if ('__construct' != $method->getName()) {
       
   321                 $parameters  = $method->getParameters();
       
   322                 $paramCount  = count($parameters);
       
   323                 $argsStarted = false;
       
   324 
       
   325                 $argNames = "var argNames=[";
       
   326                 foreach ($parameters as $param) {
       
   327                     if ($argsStarted) {
       
   328                         $result .= ',';
       
   329                     }
       
   330 
       
   331                     $result .= $param->getName();
       
   332 
       
   333                     if ($argsStarted) {
       
   334                         $argNames .= ',';
       
   335                     }
       
   336 
       
   337                     $argNames .= '"' . $param->getName() . '"';
       
   338 
       
   339                     $argsStarted = true;
       
   340                 }
       
   341                 $argNames .= "];";
       
   342 
       
   343                 $result .= "){"
       
   344                          . $argNames
       
   345                          . 'var result = ZAjaxEngine.invokeRemoteMethod('
       
   346                          . "this, '" . $method->getName()
       
   347                          . "',argNames,arguments);"
       
   348                          . 'return(result);}';
       
   349             } else {
       
   350                 $result .= "){}";
       
   351             }
       
   352         }
       
   353 
       
   354         return $result . "}";
       
   355     }
       
   356 
       
   357 
       
   358     /**
       
   359      * Encode the public properties of the ReflectionClass in the class2
       
   360      * format.
       
   361      *
       
   362      * @param $cls ReflectionClass
       
   363      * @return string Encode properties list
       
   364      *
       
   365      */
       
   366     private static function _encodeVariables(ReflectionClass $cls)
       
   367     {
       
   368         $properties = $cls->getProperties();
       
   369         $propValues = get_class_vars($cls->getName());
       
   370         $result = "variables:{";
       
   371         $cnt = 0;
       
   372 
       
   373         $tmpArray = array();
       
   374         foreach ($properties as $prop) {
       
   375             if (! $prop->isPublic()) {
       
   376                 continue;
       
   377             }
       
   378 
       
   379             $tmpArray[] = $prop->getName()
       
   380                         . ':'
       
   381                         . self::encode($propValues[$prop->getName()]);
       
   382         }
       
   383         $result .= implode(',', $tmpArray);
       
   384 
       
   385         return $result . "}";
       
   386     }
       
   387 
       
   388     /**
       
   389      * Encodes the given $className into the class2 model of encoding PHP
       
   390      * classes into JavaScript class2 classes.
       
   391      * NOTE: Currently only public methods and variables are proxied onto
       
   392      * the client machine
       
   393      *
       
   394      * @param $className string The name of the class, the class must be
       
   395      * instantiable using a null constructor
       
   396      * @param $package string Optional package name appended to JavaScript
       
   397      * proxy class name
       
   398      * @return string The class2 (JavaScript) encoding of the class
       
   399      * @throws Zend_Json_Exception
       
   400      */
       
   401     public static function encodeClass($className, $package = '')
       
   402     {
       
   403         $cls = new ReflectionClass($className);
       
   404         if (! $cls->isInstantiable()) {
       
   405             require_once 'Zend/Json/Exception.php';
       
   406             throw new Zend_Json_Exception("$className must be instantiable");
       
   407         }
       
   408 
       
   409         return "Class.create('$package$className',{"
       
   410                 . self::_encodeConstants($cls)    .","
       
   411                 . self::_encodeMethods($cls)      .","
       
   412                 . self::_encodeVariables($cls)    .'});';
       
   413     }
       
   414 
       
   415 
       
   416     /**
       
   417      * Encode several classes at once
       
   418      *
       
   419      * Returns JSON encoded classes, using {@link encodeClass()}.
       
   420      *
       
   421      * @param array $classNames
       
   422      * @param string $package
       
   423      * @return string
       
   424      */
       
   425     public static function encodeClasses(array $classNames, $package = '')
       
   426     {
       
   427         $result = '';
       
   428         foreach ($classNames as $className) {
       
   429             $result .= self::encodeClass($className, $package);
       
   430         }
       
   431 
       
   432         return $result;
       
   433     }
       
   434 
       
   435     /**
       
   436      * Encode Unicode Characters to \u0000 ASCII syntax.
       
   437      *
       
   438      * This algorithm was originally developed for the
       
   439      * Solar Framework by Paul M. Jones
       
   440      *
       
   441      * @link   http://solarphp.com/
       
   442      * @link   http://svn.solarphp.com/core/trunk/Solar/Json.php
       
   443      * @param  string $value
       
   444      * @return string
       
   445      */
       
   446     public static function encodeUnicodeString($value)
       
   447     {
       
   448         $strlen_var = strlen($value);
       
   449         $ascii = "";
       
   450 
       
   451         /**
       
   452          * Iterate over every character in the string,
       
   453          * escaping with a slash or encoding to UTF-8 where necessary
       
   454          */
       
   455         for($i = 0; $i < $strlen_var; $i++) {
       
   456             $ord_var_c = ord($value[$i]);
       
   457 
       
   458             switch (true) {
       
   459                 case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
       
   460                     // characters U-00000000 - U-0000007F (same as ASCII)
       
   461                     $ascii .= $value[$i];
       
   462                     break;
       
   463 
       
   464                 case (($ord_var_c & 0xE0) == 0xC0):
       
   465                     // characters U-00000080 - U-000007FF, mask 110XXXXX
       
   466                     // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
   467                     $char = pack('C*', $ord_var_c, ord($value[$i + 1]));
       
   468                     $i += 1;
       
   469                     $utf16 = self::_utf82utf16($char);
       
   470                     $ascii .= sprintf('\u%04s', bin2hex($utf16));
       
   471                     break;
       
   472 
       
   473                 case (($ord_var_c & 0xF0) == 0xE0):
       
   474                     // characters U-00000800 - U-0000FFFF, mask 1110XXXX
       
   475                     // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
   476                     $char = pack('C*', $ord_var_c,
       
   477                                  ord($value[$i + 1]),
       
   478                                  ord($value[$i + 2]));
       
   479                     $i += 2;
       
   480                     $utf16 = self::_utf82utf16($char);
       
   481                     $ascii .= sprintf('\u%04s', bin2hex($utf16));
       
   482                     break;
       
   483 
       
   484                 case (($ord_var_c & 0xF8) == 0xF0):
       
   485                     // characters U-00010000 - U-001FFFFF, mask 11110XXX
       
   486                     // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
   487                     $char = pack('C*', $ord_var_c,
       
   488                                  ord($value[$i + 1]),
       
   489                                  ord($value[$i + 2]),
       
   490                                  ord($value[$i + 3]));
       
   491                     $i += 3;
       
   492                     $utf16 = self::_utf82utf16($char);
       
   493                     $ascii .= sprintf('\u%04s', bin2hex($utf16));
       
   494                     break;
       
   495 
       
   496                 case (($ord_var_c & 0xFC) == 0xF8):
       
   497                     // characters U-00200000 - U-03FFFFFF, mask 111110XX
       
   498                     // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
   499                     $char = pack('C*', $ord_var_c,
       
   500                                  ord($value[$i + 1]),
       
   501                                  ord($value[$i + 2]),
       
   502                                  ord($value[$i + 3]),
       
   503                                  ord($value[$i + 4]));
       
   504                     $i += 4;
       
   505                     $utf16 = self::_utf82utf16($char);
       
   506                     $ascii .= sprintf('\u%04s', bin2hex($utf16));
       
   507                     break;
       
   508 
       
   509                 case (($ord_var_c & 0xFE) == 0xFC):
       
   510                     // characters U-04000000 - U-7FFFFFFF, mask 1111110X
       
   511                     // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
   512                     $char = pack('C*', $ord_var_c,
       
   513                                  ord($value[$i + 1]),
       
   514                                  ord($value[$i + 2]),
       
   515                                  ord($value[$i + 3]),
       
   516                                  ord($value[$i + 4]),
       
   517                                  ord($value[$i + 5]));
       
   518                     $i += 5;
       
   519                     $utf16 = self::_utf82utf16($char);
       
   520                     $ascii .= sprintf('\u%04s', bin2hex($utf16));
       
   521                     break;
       
   522             }
       
   523         }
       
   524 
       
   525         return $ascii;
       
   526      }
       
   527 
       
   528     /**
       
   529      * Convert a string from one UTF-8 char to one UTF-16 char.
       
   530      *
       
   531      * Normally should be handled by mb_convert_encoding, but
       
   532      * provides a slower PHP-only method for installations
       
   533      * that lack the multibye string extension.
       
   534      *
       
   535      * This method is from the Solar Framework by Paul M. Jones
       
   536      *
       
   537      * @link   http://solarphp.com
       
   538      * @param string $utf8 UTF-8 character
       
   539      * @return string UTF-16 character
       
   540      */
       
   541     protected static function _utf82utf16($utf8)
       
   542     {
       
   543         // Check for mb extension otherwise do by hand.
       
   544         if( function_exists('mb_convert_encoding') ) {
       
   545             return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
       
   546         }
       
   547 
       
   548         switch (strlen($utf8)) {
       
   549             case 1:
       
   550                 // this case should never be reached, because we are in ASCII range
       
   551                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
   552                 return $utf8;
       
   553 
       
   554             case 2:
       
   555                 // return a UTF-16 character from a 2-byte UTF-8 char
       
   556                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
   557                 return chr(0x07 & (ord($utf8{0}) >> 2))
       
   558                      . chr((0xC0 & (ord($utf8{0}) << 6))
       
   559                          | (0x3F & ord($utf8{1})));
       
   560 
       
   561             case 3:
       
   562                 // return a UTF-16 character from a 3-byte UTF-8 char
       
   563                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
   564                 return chr((0xF0 & (ord($utf8{0}) << 4))
       
   565                          | (0x0F & (ord($utf8{1}) >> 2)))
       
   566                      . chr((0xC0 & (ord($utf8{1}) << 6))
       
   567                          | (0x7F & ord($utf8{2})));
       
   568         }
       
   569 
       
   570         // ignoring UTF-32 for now, sorry
       
   571         return '';
       
   572     }
       
   573 }
       
   574