Skip to content

Commit

Permalink
[PHP] allow encoding enums as integers for json (#12707)
Browse files Browse the repository at this point in the history
The php extension already had an implementation for encoding enums as integers, but it was not exposed, so add a bitmask param to `Message::serializeToJsonString()` to enable it as well as `preserve_proto_field_names`.

Converted the existing boolean parameter to preferentially be an integer (with boolean handling for BC), which accepts a bitmask of `Google\Protobuf\PrintOptions::*`, eg `PrintOptions::ALWAYS_PRINT_ENUMS_AS_INTS | PrintOptions::PRESERVE_PROTO_FIELD_NAMES`

`PrintOptions` class name and constant names were chosen to align with protobuf-cpp's implementation.

Implemented `preserve_proto_fieldnames` in the native version, to match the existing implementation in the extension (with tests).

Closes #12707

COPYBARA_INTEGRATE_REVIEW=#12707 from brettmc:php-json-enum-as-int 515a083
PiperOrigin-RevId: 725806573
  • Loading branch information
brettmc authored and copybara-github committed Feb 12, 2025
1 parent 0717c15 commit 40ec76e
Show file tree
Hide file tree
Showing 12 changed files with 227 additions and 33 deletions.
1 change: 1 addition & 0 deletions php/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ _RUNTIME_SOURCES = [
"src/Google/Protobuf/Internal/MessageBuilderContext.php",
"src/Google/Protobuf/Internal/OneofDescriptor.php",
"src/Google/Protobuf/Internal/OneofField.php",
"src/Google/Protobuf/PrintOptions.php",
"src/Google/Protobuf/Internal/RawInputStream.php",
"src/Google/Protobuf/Internal/RepeatedField.php",
"src/Google/Protobuf/Internal/RepeatedFieldIter.php",
Expand Down
2 changes: 1 addition & 1 deletion php/ext/google/protobuf/config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ if test "$PHP_PROTOBUF" != "no"; then

PHP_NEW_EXTENSION(
protobuf,
arena.c array.c convert.c def.c map.c message.c names.c php-upb.c protobuf.c third_party/utf8_range/utf8_range.c,
arena.c array.c convert.c def.c map.c message.c names.c print_options.c php-upb.c protobuf.c third_party/utf8_range/utf8_range.c,
$ext_shared, , -std=gnu99 -I@ext_srcdir@/third_party/utf8_range)
PHP_ADD_BUILD_DIR($ext_builddir/third_party/utf8_range)

Expand Down
2 changes: 1 addition & 1 deletion php/ext/google/protobuf/config.w32
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ ARG_ENABLE("protobuf", "whether to enable Protobuf extension", "no");
if (PHP_PROTOBUF != "no") {
ADD_SOURCES(configure_module_dirname + "/third_party/utf8_range", "utf8_range.c", "PROTOBUF");
ADD_FLAG("CFLAGS_PROTOBUF", "/I" + configure_module_dirname + "/third_party/utf8_range");
EXTENSION("protobuf", "arena.c array.c convert.c def.c map.c message.c names.c php-upb.c protobuf.c");
EXTENSION("protobuf", "arena.c array.c convert.c def.c map.c message.c names.c print_options.c php-upb.c protobuf.c");
}
17 changes: 13 additions & 4 deletions php/ext/google/protobuf/message.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "def.h"
#include "map.h"
#include "php-upb.h"
#include "print_options.h"
#include "protobuf.h"

// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -754,16 +755,24 @@ PHP_METHOD(Message, serializeToJsonString) {
size_t size;
int options = 0;
char buf[1024];
zend_bool preserve_proto_fieldnames = false;
zval* flags = NULL;
upb_Status status;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b",
&preserve_proto_fieldnames) == FAILURE) {
if (zend_parse_parameters(ZEND_NUM_ARGS(), "|z", &flags) == FAILURE) {
return;
}

if (preserve_proto_fieldnames) {
if (flags == NULL || Z_TYPE_P(flags) == IS_FALSE) {
// do nothing
} else if (Z_TYPE_P(flags) == IS_TRUE) {
options |= upb_JsonEncode_UseProtoNames;
} else if (Z_TYPE_P(flags) == IS_LONG) {
if (Z_LVAL_P(flags) & ALWAYS_PRINT_ENUMS_AS_INTS) {
options |= upb_JsonEncode_FormatEnumsAsIntegers;
}
if (Z_LVAL_P(flags) & PRESERVE_PROTO_FIELD_NAMES) {
options |= upb_JsonEncode_UseProtoNames;
}
}

upb_Status_Clear(&status);
Expand Down
27 changes: 27 additions & 0 deletions php/ext/google/protobuf/print_options.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

#include "print_options.h"

#include "php.h"

zend_class_entry* options_ce;

void PrintOptions_ModuleInit() {
zend_class_entry tmp_ce;

INIT_CLASS_ENTRY(tmp_ce, "Google\\Protobuf\\PrintOptions", NULL);
options_ce = zend_register_internal_class(&tmp_ce);

// Define constants
zend_declare_class_constant_long(options_ce, "PRESERVE_PROTO_FIELD_NAMES",
sizeof("PRESERVE_PROTO_FIELD_NAMES") - 1,
PRESERVE_PROTO_FIELD_NAMES);
zend_declare_class_constant_long(options_ce, "ALWAYS_PRINT_ENUMS_AS_INTS",
sizeof("ALWAYS_PRINT_ENUMS_AS_INTS") - 1,
ALWAYS_PRINT_ENUMS_AS_INTS);
}
17 changes: 17 additions & 0 deletions php/ext/google/protobuf/print_options.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

#ifndef PHP_PROTOBUF_PRINT_OPTIONS_H_
#define PHP_PROTOBUF_PRINT_OPTIONS_H_

#define PRESERVE_PROTO_FIELD_NAMES (1 << 0)
#define ALWAYS_PRINT_ENUMS_AS_INTS (1 << 1)

// Registers the PHP PrintOptions class.
void PrintOptions_ModuleInit();

#endif // PHP_PROTOBUF_PRINT_OPTIONS_H_
2 changes: 2 additions & 0 deletions php/ext/google/protobuf/protobuf.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "map.h"
#include "message.h"
#include "names.h"
#include "print_options.h"

// -----------------------------------------------------------------------------
// Module "globals"
Expand Down Expand Up @@ -292,6 +293,7 @@ static PHP_MINIT_FUNCTION(protobuf) {
Def_ModuleInit();
Map_ModuleInit();
Message_ModuleInit();
PrintOptions_ModuleInit();
return SUCCESS;
}

Expand Down
9 changes: 8 additions & 1 deletion php/src/Google/Protobuf/Internal/CodedOutputStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,28 @@ class CodedOutputStream
private $buffer;
private $buffer_size;
private $current;
private $options;

const MAX_VARINT64_BYTES = 10;

public function __construct($size)
public function __construct($size, $options = 0)
{
$this->current = 0;
$this->buffer_size = $size;
$this->buffer = str_repeat(chr(0), $this->buffer_size);
$this->options = $options;
}

public function getData()
{
return $this->buffer;
}

public function getOptions()
{
return $this->options;
}

public function writeVarint32($value, $trim)
{
$bytes = str_repeat(chr(0), self::MAX_VARINT64_BYTES);
Expand Down
14 changes: 12 additions & 2 deletions php/src/Google/Protobuf/Internal/GPBJsonWire.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

namespace Google\Protobuf\Internal;

use Google\Protobuf\PrintOptions;

class GPBJsonWire
{

Expand All @@ -19,7 +21,7 @@ public static function serializeFieldToStream(
{
if ($has_field_name) {
$output->writeRaw("\"", 1);
$field_name = GPBJsonWire::formatFieldName($field);
$field_name = GPBJsonWire::formatFieldName($field, $output->getOptions());
$output->writeRaw($field_name, strlen($field_name));
$output->writeRaw("\":", 2);
}
Expand Down Expand Up @@ -178,6 +180,11 @@ private static function serializeSingularFieldValueToStream(
$output->writeRaw("null", 4);
break;
}
if ($output->getOptions() & PrintOptions::ALWAYS_PRINT_ENUMS_AS_INTS) {
$str_value = strval($value);
$output->writeRaw($str_value, strlen($str_value));
break;
}
$enum_value_desc = $enum_desc->getValueByNumber($value);
if (!is_null($enum_value_desc)) {
$str_value = $enum_value_desc->getName();
Expand Down Expand Up @@ -220,8 +227,11 @@ private static function serializeSingularFieldValueToStream(
return true;
}

private static function formatFieldName($field)
private static function formatFieldName($field, $options)
{
if ($options & PrintOptions::PRESERVE_PROTO_FIELD_NAMES) {
return $field->getName();
}
return $field->getJsonName();
}

Expand Down
65 changes: 41 additions & 24 deletions php/src/Google/Protobuf/Internal/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Google\Protobuf\Internal\MapEntry;
use Google\Protobuf\Internal\RepeatedField;
use Google\Protobuf\ListValue;
use Google\Protobuf\PrintOptions;
use Google\Protobuf\Value;
use Google\Protobuf\Struct;
use Google\Protobuf\NullValue;
Expand Down Expand Up @@ -1570,9 +1571,9 @@ public function serializeToString()
* Serialize the message to json string.
* @return string Serialized json protobuf data.
*/
public function serializeToJsonString()
public function serializeToJsonString($options = 0)
{
$output = new CodedOutputStream($this->jsonByteSize());
$output = new CodedOutputStream($this->jsonByteSize($options), $options);
$this->serializeToJsonStream($output);
return $output->getData();
}
Expand Down Expand Up @@ -1685,7 +1686,7 @@ private function fieldDataOnlyByteSize($field, $value)
/**
* @ignore
*/
private function fieldDataOnlyJsonByteSize($field, $value)
private function fieldDataOnlyJsonByteSize($field, $value, $options = 0)
{
$size = 0;

Expand Down Expand Up @@ -1742,13 +1743,17 @@ private function fieldDataOnlyJsonByteSize($field, $value)
$size += 4;
break;
}
$enum_value_desc = $enum_desc->getValueByNumber($value);
if (!is_null($enum_value_desc)) {
$size += 2; // size for ""
$size += strlen($enum_value_desc->getName());
if ($options & PrintOptions::ALWAYS_PRINT_ENUMS_AS_INTS) {
$size += strlen(strval($value)); // size for integer length
} else {
$str_value = strval($value);
$size += strlen($str_value);
$enum_value_desc = $enum_desc->getValueByNumber($value);
if (!is_null($enum_value_desc)) {
$size += 2; // size for ""
$size += strlen($enum_value_desc->getName());
} else {
$str_value = strval($value);
$size += strlen($str_value);
}
}
break;
case GPBType::BOOL:
Expand All @@ -1773,7 +1778,7 @@ private function fieldDataOnlyJsonByteSize($field, $value)
$size += 2; // size for \"\"
break;
case GPBType::MESSAGE:
$size += $value->jsonByteSize();
$size += $value->jsonByteSize($options);
break;
# case GPBType::GROUP:
# // TODO: Add support.
Expand Down Expand Up @@ -1851,7 +1856,7 @@ private function fieldByteSize($field)
/**
* @ignore
*/
private function fieldJsonByteSize($field)
private function fieldJsonByteSize($field, $options = 0)
{
$size = 0;

Expand All @@ -1862,7 +1867,11 @@ private function fieldJsonByteSize($field)
if ($count !== 0) {
if (!GPBUtil::hasSpecialJsonMapping($this)) {
$size += 3; // size for "\"\":".
$size += strlen($field->getJsonName()); // size for field name
if ($options & PrintOptions::PRESERVE_PROTO_FIELD_NAMES) {
$size += strlen($field->getName());
} else {
$size += strlen($field->getJsonName());
} // size for field name
}
$size += 2; // size for "{}".
$size += $count - 1; // size for commas
Expand All @@ -1886,8 +1895,8 @@ private function fieldJsonByteSize($field)
if ($additional_quote) {
$size += 2; // size for ""
}
$size += $this->fieldDataOnlyJsonByteSize($key_field, $key);
$size += $this->fieldDataOnlyJsonByteSize($value_field, $value);
$size += $this->fieldDataOnlyJsonByteSize($key_field, $key, $options);
$size += $this->fieldDataOnlyJsonByteSize($value_field, $value, $options);
$size += 1; // size for :
}
}
Expand All @@ -1898,23 +1907,31 @@ private function fieldJsonByteSize($field)
if ($count !== 0) {
if (!GPBUtil::hasSpecialJsonMapping($this)) {
$size += 3; // size for "\"\":".
$size += strlen($field->getJsonName()); // size for field name
if ($options & PrintOptions::PRESERVE_PROTO_FIELD_NAMES) {
$size += strlen($field->getName());
} else {
$size += strlen($field->getJsonName());
} // size for field name
}
$size += 2; // size for "[]".
$size += $count - 1; // size for commas
$getter = $field->getGetter();
foreach ($values as $value) {
$size += $this->fieldDataOnlyJsonByteSize($field, $value);
$size += $this->fieldDataOnlyJsonByteSize($field, $value, $options);
}
}
} elseif ($this->existField($field) || GPBUtil::hasJsonValue($this)) {
if (!GPBUtil::hasSpecialJsonMapping($this)) {
$size += 3; // size for "\"\":".
$size += strlen($field->getJsonName()); // size for field name
if ($options & PrintOptions::PRESERVE_PROTO_FIELD_NAMES) {
$size += strlen($field->getName());
} else {
$size += strlen($field->getJsonName());
} // size for field name
}
$getter = $field->getGetter();
$value = $this->$getter();
$size += $this->fieldDataOnlyJsonByteSize($field, $value);
$size += $this->fieldDataOnlyJsonByteSize($field, $value, $options);
}
return $size;
}
Expand Down Expand Up @@ -1963,7 +1980,7 @@ private function kvUpdateHelper($field, $update_key, $update_value)
/**
* @ignore
*/
public function jsonByteSize()
public function jsonByteSize($options = 0)
{
$size = 0;
if (is_a($this, 'Google\Protobuf\Any')) {
Expand All @@ -1980,9 +1997,9 @@ public function jsonByteSize()
if (GPBUtil::hasSpecialJsonMapping($value_msg)) {
// Size for "\",value\":".
$size += 9;
$size += $value_msg->jsonByteSize();
$size += $value_msg->jsonByteSize($options);
} else {
$value_size = $value_msg->jsonByteSize();
$value_size = $value_msg->jsonByteSize($options);
// size === 2 it's empty message {} which is not serialized inside any
if ($value_size !== 2) {
// Size for value. +1 for comma, -2 for "{}".
Expand All @@ -2002,7 +2019,7 @@ public function jsonByteSize()
} elseif (get_class($this) === 'Google\Protobuf\ListValue') {
$field = $this->desc->getField()[1];
if ($this->existField($field)) {
$field_size = $this->fieldJsonByteSize($field);
$field_size = $this->fieldJsonByteSize($field, $options);
$size += $field_size;
} else {
// Size for "[]".
Expand All @@ -2011,7 +2028,7 @@ public function jsonByteSize()
} elseif (get_class($this) === 'Google\Protobuf\Struct') {
$field = $this->desc->getField()[1];
if ($this->existField($field)) {
$field_size = $this->fieldJsonByteSize($field);
$field_size = $this->fieldJsonByteSize($field, $options);
$size += $field_size;
} else {
// Size for "{}".
Expand All @@ -2026,7 +2043,7 @@ public function jsonByteSize()
$fields = $this->desc->getField();
$count = 0;
foreach ($fields as $field) {
$field_size = $this->fieldJsonByteSize($field);
$field_size = $this->fieldJsonByteSize($field, $options);
$size += $field_size;
if ($field_size != 0) {
$count++;
Expand Down
16 changes: 16 additions & 0 deletions php/src/Google/Protobuf/PrintOptions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

namespace Google\Protobuf;

class PrintOptions
{
const PRESERVE_PROTO_FIELD_NAMES = 1 << 0;
const ALWAYS_PRINT_ENUMS_AS_INTS = 1 << 1;
}
Loading

0 comments on commit 40ec76e

Please sign in to comment.