PageRenderTime 76ms CodeModel.GetById 60ms app.highlight 11ms RepoModel.GetById 1ms app.codeStats 0ms

/monica/monica/vendor/zendframework/zendframework/library/Zend/Json/Encoder.php

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