Skip to content
This repository
Browse code

Fix MongoIds with PECL driver version < 1.0.11.

Prepares response data by converting MongoId instances into `array('$id' => (string) $id)` before JSON encoding.

Fixes #8
  • Loading branch information...
commit 18f907d13d830bdc08057729fcd1c9d95772d1b8 1 parent a032cb2
Justin Hileman authored

Showing 2 changed files with 13 additions and 1 deletion. Show diff stats Hide diff stats

  1. +1 1  genghis.php
  2. +12 0 src/php/JsonResponse.php
2  genghis.php
@@ -14,7 +14,7 @@ class App { protected $assets = array(); protected $assetEtags = array(); protec
14 14 class AssetResponse extends Response { protected $headers; public function __construct($name, $content, $headers = array()) { parent::__construct($content); $this->name = $name; $this->headers = array_merge(array('Content-type' => $this->getContentType()), $headers); } protected function getContentType() { $parts = explode('.', $this->name); $ext = array_pop($parts); switch ($ext) { case "js": return "application/x-javascript"; case "json": return "application/json"; case "css": return "text/css"; case "html": case "htm": case "php": return "text/html"; case "txt": return "text/plain"; default: return "unknown/" . trim($ext); } } }
15 15 class HttpException extends Exception { protected $status; public function __construct($status = 500, $msg = '') { $this->status = $status; parent::__construct(empty($msg) ? Response::getStatusText($status) : $msg); } public function getStatus() { return $this->status; } }
16 16 class JsonDecoder { const SLICE = 1; const IN_STR = 2; const IN_ARR = 3; const IN_OBJ = 4; const IN_CMT = 5; const IN_REGEX = 6; function __construct() { $this->_mb_strlen = function_exists('mb_strlen'); $this->_mb_convert_encoding = function_exists('mb_convert_encoding'); $this->_mb_substr = function_exists('mb_substr'); } var $_mb_strlen = false; var $_mb_substr = false; var $_mb_convert_encoding = false; function utf162utf8($utf16) { if($this->_mb_convert_encoding) { return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); } $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); switch(true) { case ((0x7F & $bytes) == $bytes): return chr(0x7F & $bytes); case (0x07FF & $bytes) == $bytes: return chr(0xC0 | (($bytes >> 6) & 0x1F)) . chr(0x80 | ($bytes & 0x3F)); case (0xFFFF & $bytes) == $bytes: return chr(0xE0 | (($bytes >> 12) & 0x0F)) . chr(0x80 | (($bytes >> 6) & 0x3F)) . chr(0x80 | ($bytes & 0x3F)); } return ''; } function utf82utf16($utf8) { if($this->_mb_convert_encoding) { return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); } switch($this->strlen8($utf8)) { case 1: return $utf8; case 2: return chr(0x07 & (ord($utf8{0}) >> 2)) . chr((0xC0 & (ord($utf8{0}) << 6)) | (0x3F & ord($utf8{1}))); case 3: return chr((0xF0 & (ord($utf8{0}) << 4)) | (0x0F & (ord($utf8{1}) >> 2))) . chr((0xC0 & (ord($utf8{1}) << 6)) | (0x7F & ord($utf8{2}))); } return ''; } function reduce_string($str) { $str = preg_replace(array( '#^\s*//(.+)$#m', '#^\s*/\*(.+)\*/#Us', '#/\*(.+)\*/\s*$#Us' ), '', $str); return trim($str); } function decode($str) { $str = $this->reduce_string($str); switch (strtolower($str)) { case 'true': return true; case 'false': return false; case 'null': return null; default: $m = array(); if (is_numeric($str)) { return ((float)$str == (integer)$str) ? (integer)$str : (float)$str; } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { $delim = $this->substr8($str, 0, 1); $chrs = $this->substr8($str, 1, -1); $utf8 = ''; $strlen_chrs = $this->strlen8($chrs); for ($c = 0; $c < $strlen_chrs; ++$c) { $substr_chrs_c_2 = $this->substr8($chrs, $c, 2); $ord_chrs_c = ord($chrs{$c}); switch (true) { case $substr_chrs_c_2 == '\b': $utf8 .= chr(0x08); ++$c; break; case $substr_chrs_c_2 == '\t': $utf8 .= chr(0x09); ++$c; break; case $substr_chrs_c_2 == '\n': $utf8 .= chr(0x0A); ++$c; break; case $substr_chrs_c_2 == '\f': $utf8 .= chr(0x0C); ++$c; break; case $substr_chrs_c_2 == '\r': $utf8 .= chr(0x0D); ++$c; break; case $substr_chrs_c_2 == '\\"': case $substr_chrs_c_2 == '\\\'': case $substr_chrs_c_2 == '\\\\': case $substr_chrs_c_2 == '\\/': if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || ($delim == "'" && $substr_chrs_c_2 != '\\"')) { $utf8 .= $chrs{++$c}; } break; case preg_match('/\\\u[0-9A-F]{4}/i', $this->substr8($chrs, $c, 6)): $utf16 = chr(hexdec($this->substr8($chrs, ($c + 2), 2))) . chr(hexdec($this->substr8($chrs, ($c + 4), 2))); $utf8 .= $this->utf162utf8($utf16); $c += 5; break; case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): $utf8 .= $chrs{$c}; break; case ($ord_chrs_c & 0xE0) == 0xC0: $utf8 .= $this->substr8($chrs, $c, 2); ++$c; break; case ($ord_chrs_c & 0xF0) == 0xE0: $utf8 .= $this->substr8($chrs, $c, 3); $c += 2; break; case ($ord_chrs_c & 0xF8) == 0xF0: $utf8 .= $this->substr8($chrs, $c, 4); $c += 3; break; case ($ord_chrs_c & 0xFC) == 0xF8: $utf8 .= $this->substr8($chrs, $c, 5); $c += 4; break; case ($ord_chrs_c & 0xFE) == 0xFC: $utf8 .= $this->substr8($chrs, $c, 6); $c += 5; break; } } return $utf8; } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { if ($str{0} == '[') { $stk = array(self::IN_ARR); $arr = array(); } else { $stk = array(self::IN_OBJ); $obj = array(); } array_push($stk, array('what' => self::SLICE, 'where' => 0, 'delim' => false)); $chrs = $this->substr8($str, 1, -1); $chrs = $this->reduce_string($chrs); if ($chrs == '') { if (reset($stk) == self::IN_ARR) { return $arr; } else { return $obj; } } $strlen_chrs = $this->strlen8($chrs); for ($c = 0; $c <= $strlen_chrs; ++$c) { $top = end($stk); $substr_chrs_c_2 = $this->substr8($chrs, $c, 2); if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == self::SLICE))) { $slice = $this->substr8($chrs, $top['where'], ($c - $top['where'])); array_push($stk, array('what' => self::SLICE, 'where' => ($c + 1), 'delim' => false)); if (reset($stk) == self::IN_ARR) { array_push($arr, $this->decode($slice)); } elseif (reset($stk) == self::IN_OBJ) { $parts = array(); if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:/Uis', $slice, $parts)) { $key = $this->decode($parts[1]); $val = $this->decode(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B")); $obj[$key] = $val; } elseif (preg_match('/^\s*([\p{L}\p{Nl}$_][\p{L}\p{Nl}$\p{Mn}\p{Mc}\p{Nd}\p{Pc}]*)\s*:/Uis', $slice, $parts)) { $key = $parts[1]; $val = $this->decode(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B")); $obj[$key] = $val; } else { throw new JsonException(); } } } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != self::IN_STR)) { array_push($stk, array('what' => self::IN_STR, 'where' => $c, 'delim' => $chrs{$c})); } elseif (($chrs{$c} == $top['delim']) && ($top['what'] == self::IN_STR) && (($this->strlen8($this->substr8($chrs, 0, $c)) - $this->strlen8(rtrim($this->substr8($chrs, 0, $c), '\\'))) % 2 != 1)) { array_pop($stk); } elseif (($chrs{$c} == '[') && in_array($top['what'], array(self::SLICE, self::IN_ARR, self::IN_OBJ))) { array_push($stk, array('what' => self::IN_ARR, 'where' => $c, 'delim' => false)); } elseif (($chrs{$c} == ']') && ($top['what'] == self::IN_ARR)) { array_pop($stk); } elseif (($chrs{$c} == '{') && in_array($top['what'], array(self::SLICE, self::IN_ARR, self::IN_OBJ))) { array_push($stk, array('what' => self::IN_OBJ, 'where' => $c, 'delim' => false)); } elseif (($chrs{$c} == '}') && ($top['what'] == self::IN_OBJ)) { array_pop($stk); } elseif (($substr_chrs_c_2 == '/*') && in_array($top['what'], array(self::SLICE, self::IN_ARR, self::IN_OBJ))) { array_push($stk, array('what' => self::IN_CMT, 'where' => $c, 'delim' => false)); $c++; } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == self::IN_CMT)) { array_pop($stk); $c++; for ($i = $top['where']; $i <= $c; ++$i) $chrs = substr_replace($chrs, ' ', $i, 1); } } if (in_array($top['what'], array(self::IN_CMT, self::IN_STR))) { throw new JsonException(); } if (reset($stk) == self::IN_ARR) { return $arr; } elseif (reset($stk) == self::IN_OBJ) { return $obj; } } elseif (preg_match('/^\/.*\/$/s', $str)) { return new JsonRegex($str); } else { throw new JsonException(); } } } function strlen8( $str ) { if ( $this->_mb_strlen ) { return mb_strlen( $str, "8bit" ); } return strlen( $str ); } function substr8( $string, $start, $length=false ) { if ( $length === false ) { $length = $this->strlen8( $string ) - $start; } if ( $this->_mb_substr ) { return mb_substr( $string, $start, $length, "8bit" ); } return substr( $string, $start, $length ); } } class JsonRegex { public $pattern; public function __construct($pattern) { $this->pattern = $pattern; } public function __toString() { return $this->getPattern(); } } class JsonException extends Exception {}
17   -class JsonResponse extends Response { public function renderHeaders() { $this->headers['Content-type'] = 'application/json'; $this->headers['Cache-Control'] = 'no-cache, must-revalidate'; $this->headers['Expires'] = 'Wed, 04 Aug 1982 00:00:00 GMT'; parent::renderHeaders(); } public function renderContent() { print(json_encode($this->data)); } }
  17 +class JsonResponse extends Response { public function renderHeaders() { $this->headers['Content-type'] = 'application/json'; $this->headers['Cache-Control'] = 'no-cache, must-revalidate'; $this->headers['Expires'] = 'Wed, 04 Aug 1982 00:00:00 GMT'; parent::renderHeaders(); } public function renderContent() { if (version_compare(Mongo::VERSION, '1.0.11', '<')) { array_walk_recursive($this->data, array(__CLASS__, 'prepareData'), ini_get('mongo.cmd')); } print(json_encode($this->data)); } private static function prepareData(&$data, $key, $cmd = '$') { if (is_object($data) && $data instanceof MongoId) { $data = array($cmd.'id' => (string) $data); } } }
18 18 class RedirectResponse extends Response { public function __construct($url, $status = 301) { parent::__construct($url, $status); } public function render() { header(sprintf('Location: %s', $this->data), $this->status); } }
19 19 class Response { protected static $statusCodes = array( 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 204 => 'No Content', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 400 => 'Bad Request', 401 => 'Unauthorized', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 412 => 'Precondition Failed', 415 => 'Unsupported Media Type', 417 => 'Expectation Failed', 500 => 'Internal Server Error', 501 => 'Not Implemented', ); protected $data = ''; protected $status = 200; protected $headers = array(); public function __construct($data, $status = 200, $headers = array()) { $this->data = $data; $this->status = $status; $this->headers = $headers; } public function render() { $this->renderHeaders(); $this->renderContent(); } public static function getStatusText($status) { if (isset(self::$statusCodes[$status])) { return self::$statusCodes[$status]; } } protected function renderHeaders() { header(sprintf('HTTP/1.0 %s %s', $this->status, self::$statusCodes[$this->status])); foreach ($this->headers as $name => $val) { header(sprintf('%s: %s', $name, $val)); } } protected function renderContent() { print((string) $this->data); } }
20 20
12 src/php/JsonResponse.php
@@ -13,6 +13,18 @@ public function renderHeaders()
13 13
14 14 public function renderContent()
15 15 {
  16 + // json encoding a MongoId with PECL Mongo driver < 1.0.11 returns '{}' ...
  17 + if (version_compare(Mongo::VERSION, '1.0.11', '<')) {
  18 + array_walk_recursive($this->data, array(__CLASS__, 'prepareData'), ini_get('mongo.cmd'));
  19 + }
  20 +
16 21 print(json_encode($this->data));
17 22 }
  23 +
  24 + private static function prepareData(&$data, $key, $cmd = '$')
  25 + {
  26 + if (is_object($data) && $data instanceof MongoId) {
  27 + $data = array($cmd.'id' => (string) $data);
  28 + }
  29 + }
18 30 }

0 comments on commit 18f907d

Please sign in to comment.
Something went wrong with that request. Please try again.