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 141 142 143 144 145 146 147 | <?php namespace DrSlump\Protobuf\Codec; use DrSlump\Protobuf; /** * This codec serializes and unserializes data from/to PHP associative * arrays, allowing it to be used as a base for an arbitrary number * of different serializations (json, yaml, ini, xml ...). * */ class PhpArray implements Protobuf\CodecInterface { /** @var bool */ protected $useTagNumber = false; /** * Tells the codec to expect the array keys to contain the * field's tag number instead of the name. * * @param bool $useIt */ public function useTagNumberAsKey($useIt = true) { $this->useTagNumber = $useIt; } /** * @param \DrSlump\Protobuf\Message $message * @return array */ public function encode(Protobuf\Message $message) { return $this->encodeMessage($message); } /** * @param \DrSlump\Protobuf\Message $message * @param array $data * @return \DrSlump\Protobuf\Message */ public function decode(Protobuf\Message $message, $data) { return $this->decodeMessage($message, $data); } protected function encodeMessage(Protobuf\Message $message) { $descriptor = Protobuf::getRegistry()->getDescriptor($message); $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; } $key = $this->useTagNumber ? $field->getNumber() : $field->getName(); $v = $message->_get($tag); if ($field->isRepeated()) { // Make sure the value is an array of values $v = is_array($v) ? $v : array($v); foreach ($v as $k=>$vv) { $v[$k] = $this->filterValue($vv, $field); } } else { $v = $this->filterValue($v, $field); } $data[$key] = $v; } return $data; } protected function decodeMessage(Protobuf\Message $message, $data) { // Get message descriptor $descriptor = Protobuf::getRegistry()->getDescriptor($message); foreach ($data as $key=>$v) { // Get the field by tag number or name $field = $this->useTagNumber ? $descriptor->getField($key) : $descriptor->getFieldByName($key); // Unknown field found if (!$field) { $unknown = new PhpArray\Unknown($key, gettype($v), $v); $message->addUnknown($unknown); continue; } if ($field->isRepeated()) { // Make sure the value is an array of values $v = is_array($v) && is_int(key($v)) ? $v : array($v); foreach ($v as $k=>$vv) { $v[$k] = $this->filterValue($vv, $field); } } else { $v = $this->filterValue($v, $field); } $message->_set($field->getNumber(), $v); } return $message; } protected function filterValue($value, Protobuf\Field $field) { switch ($field->getType()) { case Protobuf::TYPE_MESSAGE: // Tell apart encoding and decoding if ($value instanceof Protobuf\Message) { return $this->encodeMessage($value); } else { $nested = $field->getReference(); return $this->decodeMessage(new $nested, $value); } case Protobuf::TYPE_BOOL: return filter_var($value, FILTER_VALIDATE_BOOLEAN); case Protobuf::TYPE_STRING: case Protobuf::TYPE_BYTES: return (string)$value; case Protobuf::TYPE_FLOAT: case Protobuf::TYPE_DOUBLE: return filter_var($value, FILTER_VALIDATE_FLOAT); // Assume the rest are ints default: return filter_var($value, FILTER_VALIDATE_INT); } } } |