← All changes
|
includes/sdk/google/google/gax/src/Serializer.php
+109
-40
1.2.11
→
1.4.1
View file →
| @@ -29,15 +29,16 @@ | ||
| 29 | 29 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 30 | 30 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 31 | 31 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 32 | 32 | */ |
| 33 | -namespace Dudlewebs\WPMCS\Google\ApiCore; | |
| 33 | +namespace Dudlewebs\WPMCS\GCP\Google\ApiCore; | |
| 34 | 34 | |
| 35 | -use Dudlewebs\WPMCS\Google\Protobuf\Any; | |
| 36 | -use Dudlewebs\WPMCS\Google\Protobuf\Descriptor; | |
| 37 | -use Dudlewebs\WPMCS\Google\Protobuf\DescriptorPool; | |
| 38 | -use Dudlewebs\WPMCS\Google\Protobuf\FieldDescriptor; | |
| 39 | -use Dudlewebs\WPMCS\Google\Protobuf\Internal\Message; | |
| 35 | +use Exception; | |
| 36 | +use Dudlewebs\WPMCS\GCP\Google\Protobuf\Any; | |
| 37 | +use Dudlewebs\WPMCS\GCP\Google\Protobuf\Descriptor; | |
| 38 | +use Dudlewebs\WPMCS\GCP\Google\Protobuf\DescriptorPool; | |
| 39 | +use Dudlewebs\WPMCS\GCP\Google\Protobuf\FieldDescriptor; | |
| 40 | +use Dudlewebs\WPMCS\GCP\Google\Protobuf\Internal\Message; | |
| 40 | 41 | use RuntimeException; |
| 41 | 42 | /** |
| 42 | 43 | * Collection of methods to help with serialization of protobuf objects |
| 43 | 44 | */ |
| @@ -45,13 +46,22 @@ | ||
| 45 | 46 | { |
| 46 | 47 | const MAP_KEY_FIELD_NAME = 'key'; |
| 47 | 48 | const MAP_VALUE_FIELD_NAME = 'value'; |
| 48 | 49 | private static $phpArraySerializer; |
| 49 | - private static $metadataKnownTypes = ['google.rpc.retryinfo-bin' => \Dudlewebs\WPMCS\Google\Rpc\RetryInfo::class, 'google.rpc.debuginfo-bin' => \Dudlewebs\WPMCS\Google\Rpc\DebugInfo::class, 'google.rpc.quotafailure-bin' => \Dudlewebs\WPMCS\Google\Rpc\QuotaFailure::class, 'google.rpc.badrequest-bin' => \Dudlewebs\WPMCS\Google\Rpc\BadRequest::class, 'google.rpc.requestinfo-bin' => \Dudlewebs\WPMCS\Google\Rpc\RequestInfo::class, 'google.rpc.resourceinfo-bin' => \Dudlewebs\WPMCS\Google\Rpc\ResourceInfo::class, 'google.rpc.errorinfo-bin' => \Dudlewebs\WPMCS\Google\Rpc\ErrorInfo::class, 'google.rpc.help-bin' => \Dudlewebs\WPMCS\Google\Rpc\Help::class, 'google.rpc.localizedmessage-bin' => \Dudlewebs\WPMCS\Google\Rpc\LocalizedMessage::class]; | |
| 50 | + // Caches for different helper functions | |
| 51 | + private static array $getterMap = []; | |
| 52 | + private static array $setterMap = []; | |
| 53 | + private static array $snakeCaseMap = []; | |
| 54 | + private static array $camelCaseMap = []; | |
| 50 | 55 | private $fieldTransformers; |
| 51 | 56 | private $messageTypeTransformers; |
| 52 | 57 | private $decodeFieldTransformers; |
| 53 | 58 | private $decodeMessageTypeTransformers; |
| 59 | + // Array of key-value pairs which specify a custom encoding function. | |
| 60 | + // The key is the proto class and the value is the function | |
| 61 | + // which will be used to convert the proto instead of the | |
| 62 | + // encodeMessage method from the Serializer class. | |
| 63 | + private $customEncoders; | |
| 54 | 64 | private $descriptorMaps = []; |
| 55 | 65 | /** |
| 56 | 66 | * Serializer constructor. |
| 57 | 67 | * |
| @@ -59,14 +69,15 @@ | ||
| 59 | 69 | * @param array $messageTypeTransformers An array mapping message names to transformation functions |
| 60 | 70 | * @param array $decodeFieldTransformers An array mapping field names to transformation functions |
| 61 | 71 | * @param array $decodeMessageTypeTransformers An array mapping message names to transformation functions |
| 62 | 72 | */ |
| 63 | - public function __construct($fieldTransformers = [], $messageTypeTransformers = [], $decodeFieldTransformers = [], $decodeMessageTypeTransformers = []) | |
| 73 | + public function __construct($fieldTransformers = [], $messageTypeTransformers = [], $decodeFieldTransformers = [], $decodeMessageTypeTransformers = [], $customEncoders = []) | |
| 64 | 74 | { |
| 65 | 75 | $this->fieldTransformers = $fieldTransformers; |
| 66 | 76 | $this->messageTypeTransformers = $messageTypeTransformers; |
| 67 | 77 | $this->decodeFieldTransformers = $decodeFieldTransformers; |
| 68 | 78 | $this->decodeMessageTypeTransformers = $decodeMessageTypeTransformers; |
| 79 | + $this->customEncoders = $customEncoders; | |
| 69 | 80 | } |
| 70 | 81 | /** |
| 71 | 82 | * Encode protobuf message as a PHP array |
| 72 | 83 | * |
| @@ -75,15 +86,22 @@ | ||
| 75 | 86 | * @throws ValidationException |
| 76 | 87 | */ |
| 77 | 88 | public function encodeMessage($message) |
| 78 | 89 | { |
| 90 | + $cls = \get_class($message); | |
| 91 | + // If we have supplied a customEncoder for this class type, | |
| 92 | + // then we use that instead of the general encodeMessage definition. | |
| 93 | + if (\array_key_exists($cls, $this->customEncoders)) { | |
| 94 | + $func = $this->customEncoders[$cls]; | |
| 95 | + return \call_user_func($func, $message); | |
| 96 | + } | |
| 79 | 97 | // Get message descriptor |
| 80 | 98 | $pool = DescriptorPool::getGeneratedPool(); |
| 81 | - $messageType = $pool->getDescriptorByClassName(get_class($message)); | |
| 99 | + $messageType = $pool->getDescriptorByClassName(\get_class($message)); | |
| 82 | 100 | try { |
| 83 | 101 | return $this->encodeMessageImpl($message, $messageType); |
| 84 | 102 | } catch (\Exception $e) { |
| 85 | - throw new ValidationException("Error encoding message: " . $e->getMessage(), $e->getCode(), $e); | |
| 103 | + throw new ValidationException('Error encoding message: ' . $e->getMessage(), $e->getCode(), $e); | |
| 86 | 104 | } |
| 87 | 105 | } |
| 88 | 106 | /** |
| 89 | 107 | * Decode PHP array into the specified protobuf message |
| @@ -96,13 +114,13 @@ | ||
| 96 | 114 | public function decodeMessage($message, array $data) |
| 97 | 115 | { |
| 98 | 116 | // Get message descriptor |
| 99 | 117 | $pool = DescriptorPool::getGeneratedPool(); |
| 100 | - $messageType = $pool->getDescriptorByClassName(get_class($message)); | |
| 118 | + $messageType = $pool->getDescriptorByClassName(\get_class($message)); | |
| 101 | 119 | try { |
| 102 | 120 | return $this->decodeMessageImpl($message, $messageType, $data); |
| 103 | 121 | } catch (\Exception $e) { |
| 104 | - throw new ValidationException("Error decoding message: " . $e->getMessage(), $e->getCode(), $e); | |
| 122 | + throw new ValidationException('Error decoding message: ' . $e->getMessage(), $e->getCode(), $e); | |
| 105 | 123 | } |
| 106 | 124 | } |
| 107 | 125 | /** |
| 108 | 126 | * @param Message $message |
| @@ -110,9 +128,9 @@ | ||
| 110 | 128 | * @throws ValidationException |
| 111 | 129 | */ |
| 112 | 130 | public static function serializeToJson(Message $message) |
| 113 | 131 | { |
| 114 | - return json_encode(self::serializeToPhpArray($message), \JSON_PRETTY_PRINT); | |
| 132 | + return \json_encode(self::serializeToPhpArray($message), \JSON_PRETTY_PRINT); | |
| 115 | 133 | } |
| 116 | 134 | /** |
| 117 | 135 | * @param Message $message |
| 118 | 136 | * @return array PHP array representation of $message |
| @@ -125,37 +143,69 @@ | ||
| 125 | 143 | /** |
| 126 | 144 | * Decode metadata received from gRPC status object |
| 127 | 145 | * |
| 128 | 146 | * @param array $metadata |
| 147 | + * @param null|array $errors | |
| 129 | 148 | * @return array |
| 130 | 149 | */ |
| 131 | - public static function decodeMetadata(array $metadata) | |
| 150 | + public static function decodeMetadata(array $metadata, ?array &$errors = null) | |
| 132 | 151 | { |
| 133 | - if (count($metadata) == 0) { | |
| 152 | + if (\count($metadata) == 0) { | |
| 134 | 153 | return []; |
| 135 | 154 | } |
| 155 | + // ensure known types are available from the descriptor pool | |
| 156 | + KnownTypes::addKnownTypesToDescriptorPool(); | |
| 136 | 157 | $result = []; |
| 158 | + // If metadata contains a "status" bin, use that instead | |
| 159 | + if (isset($metadata['grpc-status-details-bin'])) { | |
| 160 | + $status = new \Dudlewebs\WPMCS\GCP\Google\Rpc\Status(); | |
| 161 | + $status->mergeFromString($metadata['grpc-status-details-bin'][0]); | |
| 162 | + foreach ($status->getDetails() as $any) { | |
| 163 | + if (isset(KnownTypes::TYPE_URLS[$any->getTypeUrl()])) { | |
| 164 | + $class = KnownTypes::TYPE_URLS[$any->getTypeUrl()]; | |
| 165 | + new $class(); | |
| 166 | + // add known types to descriptor pool | |
| 167 | + } | |
| 168 | + try { | |
| 169 | + $error = $any->unpack(); | |
| 170 | + } catch (Exception $ex) { | |
| 171 | + // failed to unpack the $any object - keep the object as-is instead | |
| 172 | + $error = $any; | |
| 173 | + } | |
| 174 | + if (!\is_null($errors)) { | |
| 175 | + $errors[] = $error; | |
| 176 | + } | |
| 177 | + $result[] = ['@type' => $any->getTypeUrl()] + self::serializeToPhpArray($error); | |
| 178 | + } | |
| 179 | + return $result; | |
| 180 | + } | |
| 181 | + // look for individual error detail bins and decode those | |
| 182 | + // NOTE: This method SHOULD be superceeded by 'grpc-status-details-bin' in every case, but | |
| 183 | + // we are keeping it for now to be safe | |
| 137 | 184 | foreach ($metadata as $key => $values) { |
| 138 | 185 | foreach ($values as $value) { |
| 139 | 186 | $decodedValue = ['@type' => $key]; |
| 140 | 187 | if (self::hasBinaryHeaderSuffix($key)) { |
| 141 | - if (isset(self::$metadataKnownTypes[$key])) { | |
| 142 | - $class = self::$metadataKnownTypes[$key]; | |
| 188 | + if (isset(KnownTypes::BIN_TYPES[$key])) { | |
| 189 | + $class = KnownTypes::BIN_TYPES[$key]; | |
| 143 | 190 | /** @var Message $message */ |
| 144 | 191 | $message = new $class(); |
| 145 | 192 | try { |
| 146 | 193 | $message->mergeFromString($value); |
| 147 | 194 | $decodedValue += self::serializeToPhpArray($message); |
| 195 | + if (!\is_null($errors)) { | |
| 196 | + $errors[] = $message; | |
| 197 | + } | |
| 148 | 198 | } catch (\Exception $e) { |
| 149 | 199 | // We encountered an error trying to deserialize the data |
| 150 | - $decodedValue += ['data' => '<Unable to deserialize data>']; | |
| 200 | + $decodedValue['data'] = '<Unable to deserialize data>'; | |
| 151 | 201 | } |
| 152 | 202 | } else { |
| 153 | 203 | // The metadata contains an unexpected binary type |
| 154 | - $decodedValue += ['data' => '<Unknown Binary Data>']; | |
| 204 | + $decodedValue['data'] = '<Unknown Binary Data>'; | |
| 155 | 205 | } |
| 156 | 206 | } else { |
| 157 | - $decodedValue += ['data' => $value]; | |
| 207 | + $decodedValue['data'] = $value; | |
| 158 | 208 | } |
| 159 | 209 | $result[] = $decodedValue; |
| 160 | 210 | } |
| 161 | 211 | } |
| @@ -176,9 +226,8 @@ | ||
| 176 | 226 | /** @var Message $unpacked */ |
| 177 | 227 | $unpacked = $any->unpack(); |
| 178 | 228 | $results[] = self::serializeToPhpArray($unpacked); |
| 179 | 229 | } catch (\Exception $ex) { |
| 180 | - echo "{$ex}\n"; | |
| 181 | 230 | // failed to unpack the $any object - show as unknown binary data |
| 182 | 231 | $results[] = ['typeUrl' => $any->getTypeUrl(), 'value' => '<Unknown Binary Data>']; |
| 183 | 232 | } |
| 184 | 233 | } |
| @@ -193,9 +242,9 @@ | ||
| 193 | 242 | private function encodeElement(FieldDescriptor $field, $data) |
| 194 | 243 | { |
| 195 | 244 | switch ($field->getType()) { |
| 196 | 245 | case GPBType::MESSAGE: |
| 197 | - if (is_array($data)) { | |
| 246 | + if (\is_array($data)) { | |
| 198 | 247 | $result = $data; |
| 199 | 248 | } else { |
| 200 | 249 | $result = $this->encodeMessageImpl($data, $field->getMessageType()); |
| 201 | 250 | } |
| @@ -244,22 +293,22 @@ | ||
| 244 | 293 | */ |
| 245 | 294 | private function encodeMessageImpl(Message $message, Descriptor $messageType) |
| 246 | 295 | { |
| 247 | 296 | $data = []; |
| 248 | - $fieldCount = $messageType->getFieldCount(); | |
| 249 | - for ($i = 0; $i < $fieldCount; $i++) { | |
| 250 | - $field = $messageType->getField($i); | |
| 297 | + // Call the getDescriptorMaps outside of the loop to save processing. | |
| 298 | + // Use the same set of fields to loop over, instead of using field count. | |
| 299 | + list($fields, $fieldsToOneof) = $this->getDescriptorMaps($messageType); | |
| 300 | + foreach ($fields as $field) { | |
| 251 | 301 | $key = $field->getName(); |
| 252 | - $getter = $this->getGetter($key); | |
| 302 | + $getter = self::getGetter($key); | |
| 253 | 303 | $v = $message->{$getter}(); |
| 254 | - if (is_null($v)) { | |
| 304 | + if (\is_null($v)) { | |
| 255 | 305 | continue; |
| 256 | 306 | } |
| 257 | 307 | // Check and skip unset fields inside oneofs |
| 258 | - list($_, $fieldsToOneof) = $this->getDescriptorMaps($messageType); | |
| 259 | 308 | if (isset($fieldsToOneof[$key])) { |
| 260 | 309 | $oneofName = $fieldsToOneof[$key]; |
| 261 | - $oneofGetter = $this->getGetter($oneofName); | |
| 310 | + $oneofGetter = self::getGetter($oneofName); | |
| 262 | 311 | if ($message->{$oneofGetter}() !== $key) { |
| 263 | 312 | continue; |
| 264 | 313 | } |
| 265 | 314 | } |
| @@ -271,9 +320,9 @@ | ||
| 271 | 320 | foreach ($v as $k => $vv) { |
| 272 | 321 | $arr[$this->encodeElement($keyField, $k)] = $this->encodeElement($valueField, $vv); |
| 273 | 322 | } |
| 274 | 323 | $v = $arr; |
| 275 | - } elseif ($field->getLabel() === GPBLabel::REPEATED) { | |
| 324 | + } elseif ($this->checkFieldRepeated($field)) { | |
| 276 | 325 | $arr = []; |
| 277 | 326 | foreach ($v as $k => $vv) { |
| 278 | 327 | $arr[$k] = $this->encodeElement($field, $vv); |
| 279 | 328 | } |
| @@ -328,9 +377,9 @@ | ||
| 328 | 377 | // Get the field by tag number or name |
| 329 | 378 | $fieldName = self::toSnakeCase($key); |
| 330 | 379 | // Unknown field found |
| 331 | 380 | if (!isset($fieldsByName[$fieldName])) { |
| 332 | - throw new RuntimeException(sprintf("cannot handle unknown field %s on message %s", $fieldName, $messageType->getFullName())); | |
| 381 | + throw new RuntimeException(\sprintf('cannot handle unknown field %s on message %s', $fieldName, $messageType->getFullName())); | |
| 333 | 382 | } |
| 334 | 383 | /** @var FieldDescriptor $field */ |
| 335 | 384 | $field = $fieldsByName[$fieldName]; |
| 336 | 385 | if ($field->isMap()) { |
| @@ -341,9 +390,9 @@ | ||
| 341 | 390 | foreach ($v as $k => $vv) { |
| 342 | 391 | $arr[$this->decodeElement($keyField, $k)] = $this->decodeElement($valueField, $vv); |
| 343 | 392 | } |
| 344 | 393 | $value = $arr; |
| 345 | - } elseif ($field->getLabel() === GPBLabel::REPEATED) { | |
| 394 | + } elseif ($this->checkFieldRepeated($field)) { | |
| 346 | 395 | $arr = []; |
| 347 | 396 | foreach ($v as $k => $vv) { |
| 348 | 397 | $arr[$k] = $this->decodeElement($field, $vv); |
| 349 | 398 | } |
| @@ -350,9 +399,9 @@ | ||
| 350 | 399 | $value = $arr; |
| 351 | 400 | } else { |
| 352 | 401 | $value = $this->decodeElement($field, $v); |
| 353 | 402 | } |
| 354 | - $setter = $this->getSetter($field->getName()); | |
| 403 | + $setter = self::getSetter($field->getName()); | |
| 355 | 404 | $message->{$setter}($value); |
| 356 | 405 | // We must unset $value here, otherwise the protobuf c extension will mix up the references |
| 357 | 406 | // and setting one value will change all others |
| 358 | 407 | unset($value); |
| @@ -359,14 +408,25 @@ | ||
| 359 | 408 | } |
| 360 | 409 | return $message; |
| 361 | 410 | } |
| 362 | 411 | /** |
| 412 | + * @param FieldDescriptor $field | |
| 413 | + * @return bool | |
| 414 | + */ | |
| 415 | + private function checkFieldRepeated(FieldDescriptor $field) : bool | |
| 416 | + { | |
| 417 | + return \method_exists($field, 'isRepeated') ? $field->isRepeated() : $field->getLabel() === GPBLabel::REPEATED; | |
| 418 | + } | |
| 419 | + /** | |
| 363 | 420 | * @param string $name |
| 364 | 421 | * @return string Getter function |
| 365 | 422 | */ |
| 366 | 423 | public static function getGetter(string $name) |
| 367 | 424 | { |
| 368 | - return 'get' . ucfirst(self::toCamelCase($name)); | |
| 425 | + if (!isset(self::$getterMap[$name])) { | |
| 426 | + self::$getterMap[$name] = 'get' . \ucfirst(self::toCamelCase($name)); | |
| 427 | + } | |
| 428 | + return self::$getterMap[$name]; | |
| 369 | 429 | } |
| 370 | 430 | /** |
| 371 | 431 | * @param string $name |
| 372 | 432 | * @return string Setter function |
| @@ -372,9 +432,12 @@ | ||
| 372 | 432 | * @return string Setter function |
| 373 | 433 | */ |
| 374 | 434 | public static function getSetter(string $name) |
| 375 | 435 | { |
| 376 | - return 'set' . ucfirst(self::toCamelCase($name)); | |
| 436 | + if (!isset(self::$setterMap[$name])) { | |
| 437 | + self::$setterMap[$name] = 'set' . \ucfirst(self::toCamelCase($name)); | |
| 438 | + } | |
| 439 | + return self::$setterMap[$name]; | |
| 377 | 440 | } |
| 378 | 441 | /** |
| 379 | 442 | * Convert string from camelCase to snake_case |
| 380 | 443 | * |
| @@ -382,9 +445,12 @@ | ||
| 382 | 445 | * @return string |
| 383 | 446 | */ |
| 384 | 447 | public static function toSnakeCase(string $key) |
| 385 | 448 | { |
| 386 | - return strtolower(preg_replace(['/([a-z\d])([A-Z])/', '/([^_])([A-Z][a-z])/'], '$1_$2', $key)); | |
| 449 | + if (!isset(self::$snakeCaseMap[$key])) { | |
| 450 | + self::$snakeCaseMap[$key] = \strtolower(\preg_replace(['/([a-z\\d])([A-Z])/', '/([^_])([A-Z][a-z])/'], '$1_$2', $key)); | |
| 451 | + } | |
| 452 | + return self::$snakeCaseMap[$key]; | |
| 387 | 453 | } |
| 388 | 454 | /** |
| 389 | 455 | * Convert string from snake_case to camelCase |
| 390 | 456 | * |
| @@ -392,17 +458,20 @@ | ||
| 392 | 458 | * @return string |
| 393 | 459 | */ |
| 394 | 460 | public static function toCamelCase(string $key) |
| 395 | 461 | { |
| 396 | - return lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $key)))); | |
| 462 | + if (!isset(self::$camelCaseMap[$key])) { | |
| 463 | + self::$camelCaseMap[$key] = \lcfirst(\str_replace(' ', '', \ucwords(\str_replace('_', ' ', $key)))); | |
| 464 | + } | |
| 465 | + return self::$camelCaseMap[$key]; | |
| 397 | 466 | } |
| 398 | 467 | private static function hasBinaryHeaderSuffix(string $key) |
| 399 | 468 | { |
| 400 | - return substr_compare($key, "-bin", strlen($key) - 4) === 0; | |
| 469 | + return \substr_compare($key, '-bin', \strlen($key) - 4) === 0; | |
| 401 | 470 | } |
| 402 | 471 | private static function getPhpArraySerializer() |
| 403 | 472 | { |
| 404 | - if (is_null(self::$phpArraySerializer)) { | |
| 473 | + if (\is_null(self::$phpArraySerializer)) { | |
| 405 | 474 | self::$phpArraySerializer = new Serializer(); |
| 406 | 475 | } |
| 407 | 476 | return self::$phpArraySerializer; |
| 408 | 477 | } |
| @@ -407,9 +476,9 @@ | ||
| 407 | 476 | return self::$phpArraySerializer; |
| 408 | 477 | } |
| 409 | 478 | public static function loadKnownMetadataTypes() |
| 410 | 479 | { |
| 411 | - foreach (self::$metadataKnownTypes as $key => $class) { | |
| 480 | + foreach (KnownTypes::allKnownTypes() as $key => $class) { | |
| 412 | 481 | new $class(); |
| 413 | 482 | } |
| 414 | 483 | } |
| 415 | 484 | } |