1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | <?php namespace DrSlump\Protobuf\Codec; use DrSlump\Protobuf; /** * This codec serializes and unserializes from/to Json strings * where the keys are packed as the first element of numeric arrays, * optimizing the resulting payload size. * */ class JsonIndexed extends Json implements Protobuf\CodecInterface { protected function encodeMessage(Protobuf\Message $message) { $descriptor = Protobuf::getRegistry()->getDescriptor($message); $index = ''; $data = array(); foreach ($descriptor->getFields() as $tag=>$field) { $empty = !$message->_has($tag); if ($field->isRequired() && $empty) { throw new \UnexpectedValueException( 'Message ' . get_class($message) . '\'s field tag ' . $tag . '(' . $field->getName() . ') is required but has no value' ); } if ($empty) { continue; } $index .= $this->i2c($tag + 48); $value = $message->_get($tag); if ($field->isRepeated()) { $repeats = array(); foreach ($value as $val) { if ($field->getType() !== Protobuf::TYPE_MESSAGE) { $repeats[] = $val; } else { $repeats[] = $this->encodeMessage($val); } } $data[] = $repeats; } else { if ($field->getType() === Protobuf::TYPE_MESSAGE) { $data[] = $this->encodeMessage($value); } else { $data[] = $value; } } } // Insert the index at first element array_unshift($data, $index); return $data; } protected function decodeMessage(Protobuf\Message $message, $data) { // Get message descriptor $descriptor = Protobuf::getRegistry()->getDescriptor($message); // Split the index in UTF8 characters preg_match_all('/./u', $data[0], $chars); $chars = $chars[0]; for ($i=1; $i<count($data); $i++) { $k = $this->c2i($chars[$i-1]) - 48; $v = $data[$i]; $field = $descriptor->getField($k); if (NULL === $field) { // Unknown $unknown = new PhpArray\Unknown($k, gettype($v), $v); $message->addUnknown($unknown); continue; } if ($field->getType() === Protobuf::TYPE_MESSAGE) { $nested = $field->getReference(); if ($field->isRepeated()) { foreach ($v as $vv) { $obj = $this->decodeMessage(new $nested, $vv); $message->_add($k, $obj); } } else { $obj = $this->decodeMessage(new $nested, $v); $message->_set($k, $obj); } } else { $message->_set($k, $v); } } return $message; } /** * Converts an Unicode codepoint number to an UTF-8 character * * @param int $codepoint * @return string */ protected function i2c($codepoint) { return $codepoint < 128 ? chr($codepoint) : html_entity_decode("&#$codepoint;", ENT_NOQUOTES, 'UTF-8'); } /** * Converts an UTF-8 character to an Unicode codepoint number * * @param string $char * @return int */ protected function c2i($char) { $value = ord($char[0]); if ($value < 128) return $value; if ($value < 224) { return (($value % 32) * 64) + (ord($char[1]) % 64); } else { return (($value % 16) * 4096) + ((ord($char[1]) % 64) * 64) + (ord($char[2]) % 64); } } } |