From 7413beae1b60b773413802eb6b95d3d4a7226447 Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Mon, 20 Apr 2026 09:06:00 -0700 Subject: [PATCH] fix: php deprecations for dynamic properties --- composer.json | 1 + src/Ast/PhpClass.php | 2 + src/Ast/PhpConstant.php | 3 + src/Ast/PhpFunction.php | 2 + src/Ast/PhpMethod.php | 2 + src/Ast/PhpProperty.php | 2 + src/Protobuf/Internal/Descriptor.php | 216 +++++++++++++++ src/Protobuf/Internal/EnumDescriptor.php | 119 +++++++++ src/Protobuf/Internal/FieldDescriptor.php | 311 ++++++++++++++++++++++ 9 files changed, 658 insertions(+) create mode 100644 src/Protobuf/Internal/Descriptor.php create mode 100644 src/Protobuf/Internal/EnumDescriptor.php create mode 100644 src/Protobuf/Internal/FieldDescriptor.php diff --git a/composer.json b/composer.json index d9e0ca646..1eac65810 100644 --- a/composer.json +++ b/composer.json @@ -4,6 +4,7 @@ "Google\\PostProcessor\\": "src/PostProcessor", "Google\\Generator\\": "src", "Google\\Generator\\Tests\\": "tests", + "Google\\Protobuf\\Internal\\": "src/Protobuf/Internal", "Google\\": "generated/Google", "Grpc\\": "generated/Grpc", "GPBMetadata\\": "generated/GPBMetadata" diff --git a/src/Ast/PhpClass.php b/src/Ast/PhpClass.php index 1b6b8b8a0..1ebaa2312 100644 --- a/src/Ast/PhpClass.php +++ b/src/Ast/PhpClass.php @@ -18,6 +18,7 @@ namespace Google\Generator\Ast; +use AllowDynamicProperties; use Exception; use Google\Generator\Collections\Set; use Google\Generator\Collections\Vector; @@ -26,6 +27,7 @@ use RuntimeException; /** A class definition. */ +#[AllowDynamicProperties] final class PhpClass extends AST { use HasPhpDoc; diff --git a/src/Ast/PhpConstant.php b/src/Ast/PhpConstant.php index 3cd852c6e..5087436d8 100644 --- a/src/Ast/PhpConstant.php +++ b/src/Ast/PhpConstant.php @@ -18,7 +18,10 @@ namespace Google\Generator\Ast; +use AllowDynamicProperties; + /** A constant within a class. */ +#[AllowDynamicProperties] final class PhpConstant extends PhpClassMember { private mixed $value; diff --git a/src/Ast/PhpFunction.php b/src/Ast/PhpFunction.php index 6d32db873..4b0198f76 100644 --- a/src/Ast/PhpFunction.php +++ b/src/Ast/PhpFunction.php @@ -18,6 +18,7 @@ namespace Google\Generator\Ast; +use AllowDynamicProperties; use Google\Generator\Collections\Vector; use Google\Generator\Utils\ResolvedType; @@ -25,6 +26,7 @@ * A function that can be placed in any block of code. Please use * {@see PhpMethod} if you intend to add a function to a class. */ +#[AllowDynamicProperties] final class PhpFunction extends AST implements ShouldNotApplySemicolonInterface { use HasPhpDoc; diff --git a/src/Ast/PhpMethod.php b/src/Ast/PhpMethod.php index c358ee8ef..0932f963a 100644 --- a/src/Ast/PhpMethod.php +++ b/src/Ast/PhpMethod.php @@ -18,10 +18,12 @@ namespace Google\Generator\Ast; +use AllowDynamicProperties; use Google\Generator\Collections\Vector; use Google\Generator\Utils\ResolvedType; /** A method within a class. */ +#[AllowDynamicProperties] final class PhpMethod extends PhpClassMember { public function __construct(string $name) diff --git a/src/Ast/PhpProperty.php b/src/Ast/PhpProperty.php index f85897906..f8e93122f 100644 --- a/src/Ast/PhpProperty.php +++ b/src/Ast/PhpProperty.php @@ -18,9 +18,11 @@ namespace Google\Generator\Ast; +use AllowDynamicProperties; use Google\Generator\Utils\ResolvedType; /** A property within a class. */ +#[AllowDynamicProperties] final class PhpProperty extends PhpClassMember { private ?ResolvedType $type; diff --git a/src/Protobuf/Internal/Descriptor.php b/src/Protobuf/Internal/Descriptor.php new file mode 100644 index 000000000..6632c5f38 --- /dev/null +++ b/src/Protobuf/Internal/Descriptor.php @@ -0,0 +1,216 @@ +public_desc = new \Google\Protobuf\Descriptor($this); + } + + public function addOneofDecl($oneof) + { + $this->oneof_decl[] = $oneof; + } + + public function getOneofDecl() + { + return $this->oneof_decl; + } + + public function setFullName($full_name) + { + $this->full_name = $full_name; + } + + public function getFullName() + { + return $this->full_name; + } + + public function addField($field) + { + $this->field[$field->getNumber()] = $field; + $this->json_to_field[$field->getJsonName() ?? ''] = $field; + $this->name_to_field[$field->getName()] = $field; + $this->index_to_field[] = $field; + } + + public function getField() + { + return $this->field; + } + + public function addNestedType($desc) + { + $this->nested_type[] = $desc; + } + + public function getNestedType() + { + return $this->nested_type; + } + + public function addEnumType($desc) + { + $this->enum_type[] = $desc; + } + + public function getEnumType() + { + return $this->enum_type; + } + + public function getFieldByNumber($number) + { + if (!isset($this->field[$number])) { + return NULL; + } else { + return $this->field[$number]; + } + } + + public function getFieldByJsonName($json_name) + { + if (!isset($this->json_to_field[$json_name])) { + return NULL; + } else { + return $this->json_to_field[$json_name]; + } + } + + public function getFieldByName($name) + { + if (!isset($this->name_to_field[$name])) { + return NULL; + } else { + return $this->name_to_field[$name]; + } + } + + public function getFieldByIndex($index) + { + if (count($this->index_to_field) <= $index) { + return NULL; + } else { + return $this->index_to_field[$index]; + } + } + + public function setClass($klass) + { + $this->klass = $klass; + } + + public function getClass() + { + return $this->klass; + } + + public function setLegacyClass($klass) + { + $this->legacy_klass = $klass; + } + + public function getLegacyClass() + { + return $this->legacy_klass; + } + + public function setPreviouslyUnreservedClass($klass) + { + $this->previous_klass = $klass; + } + + public function getPreviouslyUnreservedClass() + { + return $this->previous_klass; + } + + public function setOptions($options) + { + $this->options = $options; + } + + public function getOptions() + { + return $this->options; + } + + public static function buildFromProto($proto, $file_proto, $containing) + { + $desc = new Descriptor(); + + $message_name_without_package = ""; + $classname = ""; + $legacy_classname = ""; + $previous_classname = ""; + $fullname = ""; + GPBUtil::getFullClassName( + $proto, + $containing, + $file_proto, + $message_name_without_package, + $classname, + $legacy_classname, + $fullname, + $previous_classname); + $desc->setFullName($fullname); + $desc->setClass($classname); + $desc->setLegacyClass($legacy_classname); + $desc->setPreviouslyUnreservedClass($previous_classname); + $desc->setOptions($proto->getOptions()); + + foreach ($proto->getField() as $field_proto) { + $desc->addField(FieldDescriptor::buildFromProto($field_proto)); + } + + // Handle nested types. + foreach ($proto->getNestedType() as $nested_proto) { + $desc->addNestedType(Descriptor::buildFromProto( + $nested_proto, $file_proto, $message_name_without_package)); + } + + // Handle nested enum. + foreach ($proto->getEnumType() as $enum_proto) { + $desc->addEnumType(EnumDescriptor::buildFromProto( + $enum_proto, $file_proto, $message_name_without_package)); + } + + // Handle oneof fields. + $index = 0; + foreach ($proto->getOneofDecl() as $oneof_proto) { + $desc->addOneofDecl( + OneofDescriptor::buildFromProto($oneof_proto, $desc, $index)); + $index++; + } + + return $desc; + } +} diff --git a/src/Protobuf/Internal/EnumDescriptor.php b/src/Protobuf/Internal/EnumDescriptor.php new file mode 100644 index 000000000..55cbf3139 --- /dev/null +++ b/src/Protobuf/Internal/EnumDescriptor.php @@ -0,0 +1,119 @@ +public_desc = new \Google\Protobuf\EnumDescriptor($this); + } + + public function setFullName($full_name) + { + $this->full_name = $full_name; + } + + public function getFullName() + { + return $this->full_name; + } + + public function addValue($number, $value) + { + $this->value[$number] = $value; + $this->name_to_value[$value->getName()] = $value; + $this->value_descriptor[] = new EnumValueDescriptor($value->getName(), $number); + } + + public function getValueByNumber($number) + { + if (isset($this->value[$number])) { + return $this->value[$number]; + } + return null; + } + + public function getValueByName($name) + { + if (isset($this->name_to_value[$name])) { + return $this->name_to_value[$name]; + } + return null; + } + + public function getValueDescriptorByIndex($index) + { + if (isset($this->value_descriptor[$index])) { + return $this->value_descriptor[$index]; + } + return null; + } + + public function getValueCount() + { + return count($this->value); + } + + public function setClass($klass) + { + $this->klass = $klass; + } + + public function getClass() + { + return $this->klass; + } + + public function setLegacyClass($klass) + { + $this->legacy_klass = $klass; + } + + public function getLegacyClass() + { + return $this->legacy_klass; + } + + public static function buildFromProto($proto, $file_proto, $containing) + { + $desc = new EnumDescriptor(); + + $enum_name_without_package = ""; + $classname = ""; + $legacy_classname = ""; + $fullname = ""; + GPBUtil::getFullClassName( + $proto, + $containing, + $file_proto, + $enum_name_without_package, + $classname, + $legacy_classname, + $fullname, + $unused_previous_classname); + $desc->setFullName($fullname); + $desc->setClass($classname); + $desc->setLegacyClass($legacy_classname); + $values = $proto->getValue(); + foreach ($values as $value) { + $desc->addValue($value->getNumber(), $value); + } + + return $desc; + } +} diff --git a/src/Protobuf/Internal/FieldDescriptor.php b/src/Protobuf/Internal/FieldDescriptor.php new file mode 100644 index 000000000..b603888e4 --- /dev/null +++ b/src/Protobuf/Internal/FieldDescriptor.php @@ -0,0 +1,311 @@ +public_desc = new \Google\Protobuf\FieldDescriptor($this); + } + + public function setOneofIndex($index) + { + $this->oneof_index = $index; + } + + public function getOneofIndex() + { + return $this->oneof_index; + } + + public function setName($name) + { + $this->name = $name; + } + + public function getName() + { + return $this->name; + } + + public function setJsonName($json_name) + { + $this->json_name = $json_name; + } + + public function getJsonName() + { + return $this->json_name; + } + + public function setSetter($setter) + { + $this->setter = $setter; + } + + public function getSetter() + { + return $this->setter; + } + + public function setGetter($getter) + { + $this->getter = $getter; + } + + public function getGetter() + { + return $this->getter; + } + + public function setNumber($number) + { + $this->number = $number; + } + + public function getNumber() + { + return $this->number; + } + + public function setLabel($label) + { + $this->label = $label; + } + + public function getLabel() + { + return $this->label; + } + + public function isRequired() + { + return $this->label === GPBLabel::REQUIRED; + } + + public function isRepeated() + { + return $this->label === GPBLabel::REPEATED; + } + + public function setType($type) + { + $this->type = $type; + } + + public function getType() + { + return $this->type; + } + + public function setMessageType($message_type) + { + $this->message_type = $message_type; + } + + public function getMessageType() + { + return $this->message_type; + } + + public function setEnumType($enum_type) + { + $this->enum_type = $enum_type; + } + + public function getEnumType() + { + return $this->enum_type; + } + + public function setPacked($packed) + { + $this->packed = $packed; + } + + public function getPacked() + { + return $this->packed; + } + + public function getProto3Optional() + { + return $this->proto3_optional; + } + + public function setProto3Optional($proto3_optional) + { + $this->proto3_optional = $proto3_optional; + } + + public function getContainingOneof() + { + return $this->containing_oneof; + } + + public function setContainingOneof($containing_oneof) + { + $this->containing_oneof = $containing_oneof; + } + + public function getRealContainingOneof() + { + return !is_null($this->containing_oneof) && !$this->containing_oneof->isSynthetic() + ? $this->containing_oneof : null; + } + + public function isPackable() + { + return $this->isRepeated() && self::isTypePackable($this->type); + } + + public function isMap() + { + return $this->getType() == GPBType::MESSAGE && + !is_null($this->getMessageType()->getOptions()) && + $this->getMessageType()->getOptions()->getMapEntry(); + } + + public function isTimestamp() + { + return $this->getType() == GPBType::MESSAGE && + $this->getMessageType()->getClass() === "Google\Protobuf\Timestamp"; + } + + public function isWrapperType() + { + if ($this->getType() == GPBType::MESSAGE) { + $class = $this->getMessageType()->getClass(); + return in_array($class, [ + "Google\Protobuf\DoubleValue", + "Google\Protobuf\FloatValue", + "Google\Protobuf\Int64Value", + "Google\Protobuf\UInt64Value", + "Google\Protobuf\Int32Value", + "Google\Protobuf\UInt32Value", + "Google\Protobuf\BoolValue", + "Google\Protobuf\StringValue", + "Google\Protobuf\BytesValue", + ]); + } + return false; + } + + private static function isTypePackable($field_type) + { + return ($field_type !== GPBType::STRING && + $field_type !== GPBType::GROUP && + $field_type !== GPBType::MESSAGE && + $field_type !== GPBType::BYTES); + } + + /** + * @param FieldDescriptorProto $proto + * @return FieldDescriptor + */ + public static function getFieldDescriptor($proto) + { + $type_name = null; + $type = $proto->getType(); + switch ($type) { + case GPBType::MESSAGE: + case GPBType::GROUP: + case GPBType::ENUM: + $type_name = $proto->getTypeName(); + break; + default: + break; + } + + $oneof_index = $proto->hasOneofIndex() ? $proto->getOneofIndex() : -1; + // TODO: once proto2 is supported, this default should be false + // for proto2. + if ($proto->getLabel() === GPBLabel::REPEATED && + $proto->getType() !== GPBType::MESSAGE && + $proto->getType() !== GPBType::GROUP && + $proto->getType() !== GPBType::STRING && + $proto->getType() !== GPBType::BYTES) { + $packed = true; + } else { + $packed = false; + } + $options = $proto->getOptions(); + if ($options !== null) { + $packed = $options->getPacked(); + } + + $field = new FieldDescriptor(); + $field->setName($proto->getName()); + + if ($proto->hasJsonName()) { + $json_name = $proto->getJsonName(); + } else { + $proto_name = $proto->getName(); + $json_name = implode('', array_map('ucwords', explode('_', $proto_name))); + if ($proto_name[0] !== "_" && !ctype_upper($proto_name[0])) { + $json_name = lcfirst($json_name); + } + } + $field->setJsonName($json_name); + + $camel_name = implode('', array_map('ucwords', explode('_', $proto->getName()))); + $field->setGetter('get' . $camel_name); + $field->setSetter('set' . $camel_name); + $field->setType($proto->getType()); + $field->setNumber($proto->getNumber()); + $field->setLabel($proto->getLabel()); + $field->setPacked($packed); + $field->setOneofIndex($oneof_index); + $field->setProto3Optional($proto->getProto3Optional()); + + // At this time, the message/enum type may have not been added to pool. + // So we use the type name as place holder and will replace it with the + // actual descriptor in cross building. + switch ($type) { + case GPBType::MESSAGE: + $field->setMessageType($type_name); + break; + case GPBType::ENUM: + $field->setEnumType($type_name); + break; + default: + break; + } + + return $field; + } + + public static function buildFromProto($proto) + { + return FieldDescriptor::getFieldDescriptor($proto); + } +}