Browse Source

PHP: Add Enum methods for converting to/from strings (#5342)

* adds string-to-int and int-to-string methods to enums

* remove check for valueToName property in EnumTrait

* Remove unused imports

* Update to avoid using EnumTrait

* Remove EnumTrait

* Update enum types

* Move name and value methods into generated classes

* Remove functions from GPBUtil

* Test well known enums

* Implement enum value to/from name in c extension

* Only generate use statement when namespace is present
michaelbausor 6 years ago
parent
commit
0b9af83dae

+ 224 - 0
php/ext/google/protobuf/message.c

@@ -862,6 +862,8 @@ static void init_file_wrappers(TSRMLS_D) {
 // -----------------------------------------------------------------------------
 
 static zend_function_entry field_cardinality_methods[] = {
+  PHP_ME(Field_Cardinality, name, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Field_Cardinality, value, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
   {NULL, NULL, NULL}
 };
 
@@ -886,11 +888,60 @@ PHP_PROTO_INIT_ENUMCLASS_START("Google\\Protobuf\\Field\\Cardinality",
 #endif
 PHP_PROTO_INIT_ENUMCLASS_END
 
+PHP_METHOD(Field_Cardinality, name) {
+  PHP_PROTO_LONG value;
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &value) ==
+      FAILURE) {
+    return;
+  }
+  switch (value) {
+    case 0:
+      PHP_PROTO_RETURN_STRING("CARDINALITY_UNKNOWN", 1);
+    case 1:
+      PHP_PROTO_RETURN_STRING("CARDINALITY_OPTIONAL", 1);
+    case 2:
+      PHP_PROTO_RETURN_STRING("CARDINALITY_REQUIRED", 1);
+    case 3:
+      PHP_PROTO_RETURN_STRING("CARDINALITY_REPEATED", 1);
+    default:
+      zend_throw_exception(
+          NULL,
+          "Enum Google\\Protobuf\\Field_Cardinality has no name "
+          "defined for value %d.",
+          value,
+          0 TSRMLS_CC);
+  }
+}
+
+PHP_METHOD(Field_Cardinality, value) {
+  char *name = NULL;
+  PHP_PROTO_SIZE name_len;
+
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &name_len) ==
+      FAILURE) {
+    return;
+  }
+
+  if (strncmp(name, "CARDINALITY_UNKNOWN", name_len) == 0) RETURN_LONG(0);
+  if (strncmp(name, "CARDINALITY_OPTIONAL", name_len) == 0) RETURN_LONG(1);
+  if (strncmp(name, "CARDINALITY_REQUIRED", name_len) == 0) RETURN_LONG(2);
+  if (strncmp(name, "CARDINALITY_REPEATED", name_len) == 0) RETURN_LONG(3);
+
+  zend_throw_exception(
+      NULL,
+      "Enum Google\\Protobuf\\Field_Cardinality has no value "
+      "defined for name %s.",
+      name,
+      0 TSRMLS_CC);
+}
+
 // -----------------------------------------------------------------------------
 // Field_Kind
 // -----------------------------------------------------------------------------
 
 static zend_function_entry field_kind_methods[] = {
+  PHP_ME(Field_Kind, name, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Field_Kind, value, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
   {NULL, NULL, NULL}
 };
 
@@ -945,11 +996,105 @@ PHP_PROTO_INIT_ENUMCLASS_START("Google\\Protobuf\\Field\\Kind",
 #endif
 PHP_PROTO_INIT_ENUMCLASS_END
 
+PHP_METHOD(Field_Kind, name) {
+  PHP_PROTO_LONG value;
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &value) ==
+      FAILURE) {
+    return;
+  }
+  switch (value) {
+    case 0:
+      PHP_PROTO_RETURN_STRING("TYPE_UNKNOWN", 1);
+    case 1:
+      PHP_PROTO_RETURN_STRING("TYPE_DOUBLE", 1);
+    case 2:
+      PHP_PROTO_RETURN_STRING("TYPE_FLOAT", 1);
+    case 3:
+      PHP_PROTO_RETURN_STRING("TYPE_INT64", 1);
+    case 4:
+      PHP_PROTO_RETURN_STRING("TYPE_UINT64", 1);
+    case 5:
+      PHP_PROTO_RETURN_STRING("TYPE_INT32", 1);
+    case 6:
+      PHP_PROTO_RETURN_STRING("TYPE_FIXED64", 1);
+    case 7:
+      PHP_PROTO_RETURN_STRING("TYPE_FIXED32", 1);
+    case 8:
+      PHP_PROTO_RETURN_STRING("TYPE_BOOL", 1);
+    case 9:
+      PHP_PROTO_RETURN_STRING("TYPE_STRING", 1);
+    case 10:
+      PHP_PROTO_RETURN_STRING("TYPE_GROUP", 1);
+    case 11:
+      PHP_PROTO_RETURN_STRING("TYPE_MESSAGE", 1);
+    case 12:
+      PHP_PROTO_RETURN_STRING("TYPE_BYTES", 1);
+    case 13:
+      PHP_PROTO_RETURN_STRING("TYPE_UINT32", 1);
+    case 14:
+      PHP_PROTO_RETURN_STRING("TYPE_ENUM", 1);
+    case 15:
+      PHP_PROTO_RETURN_STRING("TYPE_SFIXED32", 1);
+    case 16:
+      PHP_PROTO_RETURN_STRING("TYPE_SFIXED64", 1);
+    case 17:
+      PHP_PROTO_RETURN_STRING("TYPE_SINT32", 1);
+    case 18:
+      PHP_PROTO_RETURN_STRING("TYPE_SINT64", 1);
+    default:
+      zend_throw_exception(
+          NULL,
+          "Enum Google\\Protobuf\\Field_Kind has no name "
+          "defined for value %d.",
+          value,
+          0 TSRMLS_CC);
+  }
+}
+
+PHP_METHOD(Field_Kind, value) {
+  char *name = NULL;
+  PHP_PROTO_SIZE name_len;
+
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &name_len) ==
+      FAILURE) {
+    return;
+  }
+
+  if (strncmp(name, "TYPE_UNKNOWN", name_len) == 0) RETURN_LONG(0);
+  if (strncmp(name, "TYPE_DOUBLE", name_len) == 0) RETURN_LONG(1);
+  if (strncmp(name, "TYPE_FLOAT", name_len) == 0) RETURN_LONG(2);
+  if (strncmp(name, "TYPE_INT64", name_len) == 0) RETURN_LONG(3);
+  if (strncmp(name, "TYPE_UINT64", name_len) == 0) RETURN_LONG(4);
+  if (strncmp(name, "TYPE_INT32", name_len) == 0) RETURN_LONG(5);
+  if (strncmp(name, "TYPE_FIXED64", name_len) == 0) RETURN_LONG(6);
+  if (strncmp(name, "TYPE_FIXED32", name_len) == 0) RETURN_LONG(7);
+  if (strncmp(name, "TYPE_BOOL", name_len) == 0) RETURN_LONG(8);
+  if (strncmp(name, "TYPE_STRING", name_len) == 0) RETURN_LONG(9);
+  if (strncmp(name, "TYPE_GROUP", name_len) == 0) RETURN_LONG(10);
+  if (strncmp(name, "TYPE_MESSAGE", name_len) == 0) RETURN_LONG(11);
+  if (strncmp(name, "TYPE_BYTES", name_len) == 0) RETURN_LONG(12);
+  if (strncmp(name, "TYPE_UINT32", name_len) == 0) RETURN_LONG(13);
+  if (strncmp(name, "TYPE_ENUM", name_len) == 0) RETURN_LONG(14);
+  if (strncmp(name, "TYPE_SFIXED32", name_len) == 0) RETURN_LONG(15);
+  if (strncmp(name, "TYPE_SFIXED64", name_len) == 0) RETURN_LONG(16);
+  if (strncmp(name, "TYPE_SINT32", name_len) == 0) RETURN_LONG(17);
+  if (strncmp(name, "TYPE_SINT64", name_len) == 0) RETURN_LONG(18);
+
+  zend_throw_exception(
+      NULL,
+      "Enum Google\\Protobuf\\Field_Kind has no value "
+      "defined for name %s.",
+      name,
+      0 TSRMLS_CC);
+}
+
 // -----------------------------------------------------------------------------
 // NullValue
 // -----------------------------------------------------------------------------
 
 static zend_function_entry null_value_methods[] = {
+  PHP_ME(NullValue, name, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(NullValue, value, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
   {NULL, NULL, NULL}
 };
 
@@ -962,11 +1107,51 @@ PHP_PROTO_INIT_ENUMCLASS_START("Google\\Protobuf\\NullValue",
                                    "NULL_VALUE", 10, 0 TSRMLS_CC);
 PHP_PROTO_INIT_ENUMCLASS_END
 
+PHP_METHOD(NullValue, name) {
+  PHP_PROTO_LONG value;
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &value) ==
+      FAILURE) {
+    return;
+  }
+  switch (value) {
+    case 0:
+      PHP_PROTO_RETURN_STRING("NULL_VALUE", 1);
+    default:
+      zend_throw_exception(
+          NULL,
+          "Enum Google\\Protobuf\\NullValue has no name "
+          "defined for value %d.",
+          value,
+          0 TSRMLS_CC);
+  }
+}
+
+PHP_METHOD(NullValue, value) {
+  char *name = NULL;
+  PHP_PROTO_SIZE name_len;
+
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &name_len) ==
+      FAILURE) {
+    return;
+  }
+
+  if (strncmp(name, "NULL_VALUE", name_len) == 0) RETURN_LONG(0);
+
+  zend_throw_exception(
+      NULL,
+      "Enum Google\\Protobuf\\NullValue has no value "
+      "defined for name %s.",
+      name,
+      0 TSRMLS_CC);
+}
+
 // -----------------------------------------------------------------------------
 // Syntax
 // -----------------------------------------------------------------------------
 
 static zend_function_entry syntax_methods[] = {
+  PHP_ME(Syntax, name, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Syntax, value, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
   {NULL, NULL, NULL}
 };
 
@@ -981,7 +1166,46 @@ PHP_PROTO_INIT_ENUMCLASS_START("Google\\Protobuf\\Syntax",
                                    "SYNTAX_PROTO3", 13, 1 TSRMLS_CC);
 PHP_PROTO_INIT_ENUMCLASS_END
 
+PHP_METHOD(Syntax, name) {
+  PHP_PROTO_LONG value;
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &value) ==
+      FAILURE) {
+    return;
+  }
+  switch (value) {
+    case 0:
+      PHP_PROTO_RETURN_STRING("SYNTAX_PROTO2", 1);
+    case 1:
+      PHP_PROTO_RETURN_STRING("SYNTAX_PROTO3", 1);
+    default:
+      zend_throw_exception(
+          NULL,
+          "Enum Google\\Protobuf\\Syntax has no name "
+          "defined for value %d.",
+          value,
+          0 TSRMLS_CC);
+  }
+}
+
+PHP_METHOD(Syntax, value) {
+  char *name = NULL;
+  PHP_PROTO_SIZE name_len;
 
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &name_len) ==
+      FAILURE) {
+    return;
+  }
+
+  if (strncmp(name, "SYNTAX_PROTO2", name_len) == 0) RETURN_LONG(0);
+  if (strncmp(name, "SYNTAX_PROTO3", name_len) == 0) RETURN_LONG(1);
+
+  zend_throw_exception(
+      NULL,
+      "Enum Google\\Protobuf\\Syntax has no value "
+      "defined for name %s.",
+      name,
+      0 TSRMLS_CC);
+}
 
 // -----------------------------------------------------------------------------
 // Define message

+ 12 - 0
php/ext/google/protobuf/protobuf.h

@@ -1301,6 +1301,12 @@ PHP_METHOD(Field, setJsonName);
 PHP_METHOD(Field, getDefaultValue);
 PHP_METHOD(Field, setDefaultValue);
 
+PHP_METHOD(Field_Cardinality, name);
+PHP_METHOD(Field_Cardinality, value);
+
+PHP_METHOD(Field_Kind, name);
+PHP_METHOD(Field_Kind, value);
+
 PHP_METHOD(FloatValue, __construct);
 PHP_METHOD(FloatValue, getValue);
 PHP_METHOD(FloatValue, setValue);
@@ -1341,6 +1347,9 @@ PHP_METHOD(Mixin, setName);
 PHP_METHOD(Mixin, getRoot);
 PHP_METHOD(Mixin, setRoot);
 
+PHP_METHOD(NullValue, name);
+PHP_METHOD(NullValue, value);
+
 PHP_METHOD(Option, __construct);
 PHP_METHOD(Option, getName);
 PHP_METHOD(Option, setName);
@@ -1359,6 +1368,9 @@ PHP_METHOD(Struct, __construct);
 PHP_METHOD(Struct, getFields);
 PHP_METHOD(Struct, setFields);
 
+PHP_METHOD(Syntax, name);
+PHP_METHOD(Syntax, value);
+
 PHP_METHOD(Type, __construct);
 PHP_METHOD(Type, getName);
 PHP_METHOD(Type, setName);

+ 28 - 0
php/src/Google/Protobuf/Field/Cardinality.php

@@ -4,6 +4,8 @@
 
 namespace Google\Protobuf\Field;
 
+use UnexpectedValueException;
+
 /**
  * Whether a field is optional, required, or repeated.
  *
@@ -35,6 +37,32 @@ class Cardinality
      * Generated from protobuf enum <code>CARDINALITY_REPEATED = 3;</code>
      */
     const CARDINALITY_REPEATED = 3;
+
+    private static $valueToName = [
+        self::CARDINALITY_UNKNOWN => 'CARDINALITY_UNKNOWN',
+        self::CARDINALITY_OPTIONAL => 'CARDINALITY_OPTIONAL',
+        self::CARDINALITY_REQUIRED => 'CARDINALITY_REQUIRED',
+        self::CARDINALITY_REPEATED => 'CARDINALITY_REPEATED',
+    ];
+
+    public static function name($value)
+    {
+        if (!isset(self::$valueToName[$value])) {
+            throw new UnexpectedValueException(sprintf(
+                    'Enum %s has no name defined for value %s', __CLASS__, $value));
+        }
+        return self::$valueToName[$value];
+    }
+
+    public static function value($name)
+    {
+        $const = __CLASS__ . '::' . strtoupper($name);
+        if (!defined($const)) {
+            throw new UnexpectedValueException(sprintf(
+                    'Enum %s has no value defined for name %s', __CLASS__, $name));
+        }
+        return constant($const);
+    }
 }
 
 // Adding a class alias for backwards compatibility with the previous class name.

+ 43 - 0
php/src/Google/Protobuf/Field/Kind.php

@@ -4,6 +4,8 @@
 
 namespace Google\Protobuf\Field;
 
+use UnexpectedValueException;
+
 /**
  * Basic field types.
  *
@@ -125,6 +127,47 @@ class Kind
      * Generated from protobuf enum <code>TYPE_SINT64 = 18;</code>
      */
     const TYPE_SINT64 = 18;
+
+    private static $valueToName = [
+        self::TYPE_UNKNOWN => 'TYPE_UNKNOWN',
+        self::TYPE_DOUBLE => 'TYPE_DOUBLE',
+        self::TYPE_FLOAT => 'TYPE_FLOAT',
+        self::TYPE_INT64 => 'TYPE_INT64',
+        self::TYPE_UINT64 => 'TYPE_UINT64',
+        self::TYPE_INT32 => 'TYPE_INT32',
+        self::TYPE_FIXED64 => 'TYPE_FIXED64',
+        self::TYPE_FIXED32 => 'TYPE_FIXED32',
+        self::TYPE_BOOL => 'TYPE_BOOL',
+        self::TYPE_STRING => 'TYPE_STRING',
+        self::TYPE_GROUP => 'TYPE_GROUP',
+        self::TYPE_MESSAGE => 'TYPE_MESSAGE',
+        self::TYPE_BYTES => 'TYPE_BYTES',
+        self::TYPE_UINT32 => 'TYPE_UINT32',
+        self::TYPE_ENUM => 'TYPE_ENUM',
+        self::TYPE_SFIXED32 => 'TYPE_SFIXED32',
+        self::TYPE_SFIXED64 => 'TYPE_SFIXED64',
+        self::TYPE_SINT32 => 'TYPE_SINT32',
+        self::TYPE_SINT64 => 'TYPE_SINT64',
+    ];
+
+    public static function name($value)
+    {
+        if (!isset(self::$valueToName[$value])) {
+            throw new UnexpectedValueException(sprintf(
+                    'Enum %s has no name defined for value %s', __CLASS__, $value));
+        }
+        return self::$valueToName[$value];
+    }
+
+    public static function value($name)
+    {
+        $const = __CLASS__ . '::' . strtoupper($name);
+        if (!defined($const)) {
+            throw new UnexpectedValueException(sprintf(
+                    'Enum %s has no value defined for name %s', __CLASS__, $name));
+        }
+        return constant($const);
+    }
 }
 
 // Adding a class alias for backwards compatibility with the previous class name.

+ 27 - 0
php/src/Google/Protobuf/Internal/FieldDescriptorProto/Label.php

@@ -4,6 +4,8 @@
 
 namespace Google\Protobuf\Internal\FieldDescriptorProto;
 
+use UnexpectedValueException;
+
 /**
  * Protobuf type <code>google.protobuf.FieldDescriptorProto.Label</code>
  */
@@ -23,6 +25,31 @@ class Label
      * Generated from protobuf enum <code>LABEL_REPEATED = 3;</code>
      */
     const LABEL_REPEATED = 3;
+
+    private static $valueToName = [
+        self::LABEL_OPTIONAL => 'LABEL_OPTIONAL',
+        self::LABEL_REQUIRED => 'LABEL_REQUIRED',
+        self::LABEL_REPEATED => 'LABEL_REPEATED',
+    ];
+
+    public static function name($value)
+    {
+        if (!isset(self::$valueToName[$value])) {
+            throw new UnexpectedValueException(sprintf(
+                    'Enum %s has no name defined for value %s', __CLASS__, $value));
+        }
+        return self::$valueToName[$value];
+    }
+
+    public static function value($name)
+    {
+        $const = __CLASS__ . '::' . strtoupper($name);
+        if (!defined($const)) {
+            throw new UnexpectedValueException(sprintf(
+                    'Enum %s has no value defined for name %s', __CLASS__, $name));
+        }
+        return constant($const);
+    }
 }
 
 // Adding a class alias for backwards compatibility with the previous class name.

+ 42 - 0
php/src/Google/Protobuf/Internal/FieldDescriptorProto/Type.php

@@ -4,6 +4,8 @@
 
 namespace Google\Protobuf\Internal\FieldDescriptorProto;
 
+use UnexpectedValueException;
+
 /**
  * Protobuf type <code>google.protobuf.FieldDescriptorProto.Type</code>
  */
@@ -103,6 +105,46 @@ class Type
      * Generated from protobuf enum <code>TYPE_SINT64 = 18;</code>
      */
     const TYPE_SINT64 = 18;
+
+    private static $valueToName = [
+        self::TYPE_DOUBLE => 'TYPE_DOUBLE',
+        self::TYPE_FLOAT => 'TYPE_FLOAT',
+        self::TYPE_INT64 => 'TYPE_INT64',
+        self::TYPE_UINT64 => 'TYPE_UINT64',
+        self::TYPE_INT32 => 'TYPE_INT32',
+        self::TYPE_FIXED64 => 'TYPE_FIXED64',
+        self::TYPE_FIXED32 => 'TYPE_FIXED32',
+        self::TYPE_BOOL => 'TYPE_BOOL',
+        self::TYPE_STRING => 'TYPE_STRING',
+        self::TYPE_GROUP => 'TYPE_GROUP',
+        self::TYPE_MESSAGE => 'TYPE_MESSAGE',
+        self::TYPE_BYTES => 'TYPE_BYTES',
+        self::TYPE_UINT32 => 'TYPE_UINT32',
+        self::TYPE_ENUM => 'TYPE_ENUM',
+        self::TYPE_SFIXED32 => 'TYPE_SFIXED32',
+        self::TYPE_SFIXED64 => 'TYPE_SFIXED64',
+        self::TYPE_SINT32 => 'TYPE_SINT32',
+        self::TYPE_SINT64 => 'TYPE_SINT64',
+    ];
+
+    public static function name($value)
+    {
+        if (!isset(self::$valueToName[$value])) {
+            throw new UnexpectedValueException(sprintf(
+                    'Enum %s has no name defined for value %s', __CLASS__, $value));
+        }
+        return self::$valueToName[$value];
+    }
+
+    public static function value($name)
+    {
+        $const = __CLASS__ . '::' . strtoupper($name);
+        if (!defined($const)) {
+            throw new UnexpectedValueException(sprintf(
+                    'Enum %s has no value defined for name %s', __CLASS__, $name));
+        }
+        return constant($const);
+    }
 }
 
 // Adding a class alias for backwards compatibility with the previous class name.

+ 27 - 0
php/src/Google/Protobuf/Internal/FieldOptions/CType.php

@@ -4,6 +4,8 @@
 
 namespace Google\Protobuf\Internal\FieldOptions;
 
+use UnexpectedValueException;
+
 /**
  * Protobuf type <code>google.protobuf.FieldOptions.CType</code>
  */
@@ -23,6 +25,31 @@ class CType
      * Generated from protobuf enum <code>STRING_PIECE = 2;</code>
      */
     const STRING_PIECE = 2;
+
+    private static $valueToName = [
+        self::STRING => 'STRING',
+        self::CORD => 'CORD',
+        self::STRING_PIECE => 'STRING_PIECE',
+    ];
+
+    public static function name($value)
+    {
+        if (!isset(self::$valueToName[$value])) {
+            throw new UnexpectedValueException(sprintf(
+                    'Enum %s has no name defined for value %s', __CLASS__, $value));
+        }
+        return self::$valueToName[$value];
+    }
+
+    public static function value($name)
+    {
+        $const = __CLASS__ . '::' . strtoupper($name);
+        if (!defined($const)) {
+            throw new UnexpectedValueException(sprintf(
+                    'Enum %s has no value defined for name %s', __CLASS__, $name));
+        }
+        return constant($const);
+    }
 }
 
 // Adding a class alias for backwards compatibility with the previous class name.

+ 27 - 0
php/src/Google/Protobuf/Internal/FieldOptions/JSType.php

@@ -4,6 +4,8 @@
 
 namespace Google\Protobuf\Internal\FieldOptions;
 
+use UnexpectedValueException;
+
 /**
  * Protobuf type <code>google.protobuf.FieldOptions.JSType</code>
  */
@@ -27,6 +29,31 @@ class JSType
      * Generated from protobuf enum <code>JS_NUMBER = 2;</code>
      */
     const JS_NUMBER = 2;
+
+    private static $valueToName = [
+        self::JS_NORMAL => 'JS_NORMAL',
+        self::JS_STRING => 'JS_STRING',
+        self::JS_NUMBER => 'JS_NUMBER',
+    ];
+
+    public static function name($value)
+    {
+        if (!isset(self::$valueToName[$value])) {
+            throw new UnexpectedValueException(sprintf(
+                    'Enum %s has no name defined for value %s', __CLASS__, $value));
+        }
+        return self::$valueToName[$value];
+    }
+
+    public static function value($name)
+    {
+        $const = __CLASS__ . '::' . strtoupper($name);
+        if (!defined($const)) {
+            throw new UnexpectedValueException(sprintf(
+                    'Enum %s has no value defined for name %s', __CLASS__, $name));
+        }
+        return constant($const);
+    }
 }
 
 // Adding a class alias for backwards compatibility with the previous class name.

+ 27 - 0
php/src/Google/Protobuf/Internal/FileOptions/OptimizeMode.php

@@ -4,6 +4,8 @@
 
 namespace Google\Protobuf\Internal\FileOptions;
 
+use UnexpectedValueException;
+
 /**
  * Generated classes can be optimized for speed or code size.
  *
@@ -29,6 +31,31 @@ class OptimizeMode
      * Generated from protobuf enum <code>LITE_RUNTIME = 3;</code>
      */
     const LITE_RUNTIME = 3;
+
+    private static $valueToName = [
+        self::SPEED => 'SPEED',
+        self::CODE_SIZE => 'CODE_SIZE',
+        self::LITE_RUNTIME => 'LITE_RUNTIME',
+    ];
+
+    public static function name($value)
+    {
+        if (!isset(self::$valueToName[$value])) {
+            throw new UnexpectedValueException(sprintf(
+                    'Enum %s has no name defined for value %s', __CLASS__, $value));
+        }
+        return self::$valueToName[$value];
+    }
+
+    public static function value($name)
+    {
+        $const = __CLASS__ . '::' . strtoupper($name);
+        if (!defined($const)) {
+            throw new UnexpectedValueException(sprintf(
+                    'Enum %s has no value defined for name %s', __CLASS__, $name));
+        }
+        return constant($const);
+    }
 }
 
 // Adding a class alias for backwards compatibility with the previous class name.

+ 27 - 0
php/src/Google/Protobuf/Internal/MethodOptions/IdempotencyLevel.php

@@ -4,6 +4,8 @@
 
 namespace Google\Protobuf\Internal\MethodOptions;
 
+use UnexpectedValueException;
+
 /**
  * Is this method side-effect-free (or safe in HTTP parlance), or idempotent,
  * or neither? HTTP based RPC implementation may choose GET verb for safe
@@ -29,6 +31,31 @@ class IdempotencyLevel
      * Generated from protobuf enum <code>IDEMPOTENT = 2;</code>
      */
     const IDEMPOTENT = 2;
+
+    private static $valueToName = [
+        self::IDEMPOTENCY_UNKNOWN => 'IDEMPOTENCY_UNKNOWN',
+        self::NO_SIDE_EFFECTS => 'NO_SIDE_EFFECTS',
+        self::IDEMPOTENT => 'IDEMPOTENT',
+    ];
+
+    public static function name($value)
+    {
+        if (!isset(self::$valueToName[$value])) {
+            throw new UnexpectedValueException(sprintf(
+                    'Enum %s has no name defined for value %s', __CLASS__, $value));
+        }
+        return self::$valueToName[$value];
+    }
+
+    public static function value($name)
+    {
+        $const = __CLASS__ . '::' . strtoupper($name);
+        if (!defined($const)) {
+            throw new UnexpectedValueException(sprintf(
+                    'Enum %s has no value defined for name %s', __CLASS__, $name));
+        }
+        return constant($const);
+    }
 }
 
 // Adding a class alias for backwards compatibility with the previous class name.

+ 25 - 0
php/src/Google/Protobuf/NullValue.php

@@ -4,6 +4,8 @@
 
 namespace Google\Protobuf;
 
+use UnexpectedValueException;
+
 /**
  * `NullValue` is a singleton enumeration to represent the null value for the
  * `Value` type union.
@@ -19,5 +21,28 @@ class NullValue
      * Generated from protobuf enum <code>NULL_VALUE = 0;</code>
      */
     const NULL_VALUE = 0;
+
+    private static $valueToName = [
+        self::NULL_VALUE => 'NULL_VALUE',
+    ];
+
+    public static function name($value)
+    {
+        if (!isset(self::$valueToName[$value])) {
+            throw new UnexpectedValueException(sprintf(
+                    'Enum %s has no name defined for value %s', __CLASS__, $value));
+        }
+        return self::$valueToName[$value];
+    }
+
+    public static function value($name)
+    {
+        $const = __CLASS__ . '::' . strtoupper($name);
+        if (!defined($const)) {
+            throw new UnexpectedValueException(sprintf(
+                    'Enum %s has no value defined for name %s', __CLASS__, $name));
+        }
+        return constant($const);
+    }
 }
 

+ 26 - 0
php/src/Google/Protobuf/Syntax.php

@@ -4,6 +4,8 @@
 
 namespace Google\Protobuf;
 
+use UnexpectedValueException;
+
 /**
  * The syntax in which a protocol buffer element is defined.
  *
@@ -23,5 +25,29 @@ class Syntax
      * Generated from protobuf enum <code>SYNTAX_PROTO3 = 1;</code>
      */
     const SYNTAX_PROTO3 = 1;
+
+    private static $valueToName = [
+        self::SYNTAX_PROTO2 => 'SYNTAX_PROTO2',
+        self::SYNTAX_PROTO3 => 'SYNTAX_PROTO3',
+    ];
+
+    public static function name($value)
+    {
+        if (!isset(self::$valueToName[$value])) {
+            throw new UnexpectedValueException(sprintf(
+                    'Enum %s has no name defined for value %s', __CLASS__, $value));
+        }
+        return self::$valueToName[$value];
+    }
+
+    public static function value($name)
+    {
+        $const = __CLASS__ . '::' . strtoupper($name);
+        if (!defined($const)) {
+            throw new UnexpectedValueException(sprintf(
+                    'Enum %s has no value defined for name %s', __CLASS__, $name));
+        }
+        return constant($const);
+    }
 }
 

+ 22 - 0
php/tests/generated_class_test.php

@@ -232,6 +232,28 @@ class GeneratedClassTest extends TestBase
         // Set string.
         $m->setOptionalEnum("1");
         $this->assertEquals(TestEnum::ONE, $m->getOptionalEnum());
+
+        // Test Enum methods
+        $this->assertEquals('ONE', TestEnum::name(1));
+        $this->assertEquals(1, TestEnum::value('ONE'));
+    }
+
+    /**
+     * @expectedException UnexpectedValueException
+     * @expectedExceptionMessage Enum Foo\TestEnum has no name defined for value -1
+     */
+    public function testInvalidEnumValueThrowsException()
+    {
+        TestEnum::name(-1);
+    }
+
+    /**
+     * @expectedException UnexpectedValueException
+     * @expectedExceptionMessage Enum Foo\TestEnum has no value defined for name DOES_NOT_EXIST
+     */
+    public function testInvalidEnumNameThrowsException()
+    {
+        TestEnum::value('DOES_NOT_EXIST');
     }
 
     public function testNestedEnum()

+ 23 - 0
php/tests/well_known_test.php

@@ -392,4 +392,27 @@ class WellKnownTest extends TestBase {
         $m->setValue("a");
         $this->assertSame("a", $m->getValue());
     }
+
+    /**
+     * @dataProvider enumNameValueConversionDataProvider
+     */
+    public function testEnumNameValueConversion($class)
+    {
+        $reflectionClass = new ReflectionClass($class);
+        $constants = $reflectionClass->getConstants();
+        foreach ($constants as $k => $v) {
+            $this->assertSame($k, $class::name($v));
+            $this->assertSame($v, $class::value($k));
+        }
+    }
+
+    public function enumNameValueConversionDataProvider()
+    {
+        return [
+            ['\Google\Protobuf\Field\Cardinality'],
+            ['\Google\Protobuf\Field\Kind'],
+            ['\Google\Protobuf\NullValue'],
+            ['\Google\Protobuf\Syntax'],
+        ];
+    }
 }

+ 53 - 2
src/google/protobuf/compiler/php/php_generator.cc

@@ -1146,14 +1146,18 @@ void GenerateEnumFile(const FileDescriptor* file, const EnumDescriptor* en,
     printer.Print(
         "namespace ^name^;\n\n",
         "name", fullname.substr(0, lastindex));
+
+    // We only need this 'use' statement if the enum has a namespace.
+    // Otherwise, we get a warning that the use statement has no effect.
+    printer.Print("use UnexpectedValueException;\n\n");
   }
 
+  GenerateEnumDocComment(&printer, en, is_descriptor);
+
   if (lastindex != string::npos) {
     fullname = fullname.substr(lastindex + 1);
   }
 
-  GenerateEnumDocComment(&printer, en, is_descriptor);
-
   printer.Print(
       "class ^name^\n"
       "{\n",
@@ -1168,6 +1172,53 @@ void GenerateEnumFile(const FileDescriptor* file, const EnumDescriptor* en,
                   "number", IntToString(value->number()));
   }
 
+  printer.Print("\nprivate static $valueToName = [\n");
+  Indent(&printer);
+  for (int i = 0; i < en->value_count(); i++) {
+    const EnumValueDescriptor* value = en->value(i);
+    printer.Print("self::^name^ => '^name^',\n",
+                  "name", ConstantNamePrefix(value->name()) + value->name());
+  }
+  Outdent(&printer);
+  printer.Print("];\n");
+
+  printer.Print(
+      "\npublic static function name($value)\n"
+      "{\n");
+  Indent(&printer);
+  printer.Print("if (!isset(self::$valueToName[$value])) {\n");
+  Indent(&printer);
+  printer.Print("throw new UnexpectedValueException(sprintf(\n");
+  Indent(&printer);
+  Indent(&printer);
+  printer.Print("'Enum %s has no name defined for value %s', __CLASS__, $value));\n");
+  Outdent(&printer);
+  Outdent(&printer);
+  Outdent(&printer);
+  printer.Print("}\n"
+                "return self::$valueToName[$value];\n");
+  Outdent(&printer);
+  printer.Print("}\n\n");
+
+  printer.Print(
+      "\npublic static function value($name)\n"
+      "{\n");
+  Indent(&printer);
+  printer.Print("$const = __CLASS__ . '::' . strtoupper($name);\n"
+                "if (!defined($const)) {\n");
+  Indent(&printer);
+  printer.Print("throw new UnexpectedValueException(sprintf(\n");
+  Indent(&printer);
+  Indent(&printer);
+  printer.Print("'Enum %s has no value defined for name %s', __CLASS__, $name));\n");
+  Outdent(&printer);
+  Outdent(&printer);
+  Outdent(&printer);
+  printer.Print("}\n"
+                "return constant($const);\n");
+  Outdent(&printer);
+  printer.Print("}\n");
+
   Outdent(&printer);
   printer.Print("}\n\n");