feat: add support for deprecated fields to PHP compiler (#8223)

* feat: add support for deprecated fields to PHP compiler

* PR feedback 1
diff --git a/php/tests/GeneratedClassTest.php b/php/tests/GeneratedClassTest.php
index f3f74d1..c0de0ba 100644
--- a/php/tests/GeneratedClassTest.php
+++ b/php/tests/GeneratedClassTest.php
@@ -72,6 +72,33 @@
     }
 
     #########################################################
+    # Test deprecated int32 field.
+    #########################################################
+
+    public function testDeprecatedInt32Field()
+    {
+        $m = new TestMessage();
+
+        // temporarily change error handler to capture the deprecated errors
+        $deprecationCount = 0;
+        set_error_handler(function ($errno, $errstr) use (&$deprecationCount) {
+            if ($errstr === 'deprecated_optional_int32 is deprecated.') {
+                $deprecationCount++;
+            }
+        }, E_USER_DEPRECATED);
+
+        // default test set
+        $m->setDeprecatedOptionalInt32(MAX_INT32);
+        $this->assertSame(MAX_INT32, $m->getDeprecatedOptionalInt32());
+        $m->setDeprecatedOptionalInt32(MIN_INT32);
+        $this->assertSame(MIN_INT32, $m->getDeprecatedOptionalInt32());
+
+        restore_error_handler();
+
+        $this->assertSame(4, $deprecationCount);
+    }
+
+    #########################################################
     # Test optional int32 field.
     #########################################################
 
diff --git a/php/tests/GeneratedPhpdocTest.php b/php/tests/GeneratedPhpdocTest.php
index de672ac..18963a9 100644
--- a/php/tests/GeneratedPhpdocTest.php
+++ b/php/tests/GeneratedPhpdocTest.php
@@ -340,6 +340,13 @@
                 ],
                 '@param \NoNamespaceMessage $var'
             ],
+            [
+                [
+                    'setDeprecatedOptionalInt32',
+                    'getDeprecatedOptionalInt32',
+                ],
+                '@deprecated'
+            ],
         ];
     }
 }
diff --git a/php/tests/proto/test.proto b/php/tests/proto/test.proto
index 8835729..609b8cf 100644
--- a/php/tests/proto/test.proto
+++ b/php/tests/proto/test.proto
@@ -147,6 +147,9 @@
   map<string, google.protobuf.Any> map_string_any = 122;
   map<string, google.protobuf.ListValue> map_string_list = 123;
   map<string, google.protobuf.Struct> map_string_struct = 124;
+
+  // deprecated field
+  int32 deprecated_optional_int32 = 125 [deprecated=true];
 }
 
 enum TestEnum {
diff --git a/src/google/protobuf/compiler/php/php_generator.cc b/src/google/protobuf/compiler/php/php_generator.cc
index 04d8f8c..203b49f 100644
--- a/src/google/protobuf/compiler/php/php_generator.cc
+++ b/src/google/protobuf/compiler/php/php_generator.cc
@@ -644,43 +644,50 @@
   // Generate getter.
   GenerateFieldDocComment(printer, field, options, kFieldGetter);
 
+  // deprecation
+  std::string deprecation_trigger = (field->options().deprecated()) ? "@trigger_error('" +
+      field->name() + " is deprecated.', E_USER_DEPRECATED);\n        " : "";
+
   if (oneof != NULL) {
     printer->Print(
         "public function get^camel_name^()\n"
         "{\n"
-        "    return $this->readOneof(^number^);\n"
+        "    ^deprecation_trigger^return $this->readOneof(^number^);\n"
         "}\n\n"
         "public function has^camel_name^()\n"
         "{\n"
-        "    return $this->hasOneof(^number^);\n"
+        "    ^deprecation_trigger^return $this->hasOneof(^number^);\n"
         "}\n\n",
         "camel_name", UnderscoresToCamelCase(field->name(), true),
-        "number", IntToString(field->number()));
+        "number", IntToString(field->number()),
+        "deprecation_trigger", deprecation_trigger);
   } else if (field->has_presence()) {
     printer->Print(
         "public function get^camel_name^()\n"
         "{\n"
-        "    return isset($this->^name^) ? $this->^name^ : ^default_value^;\n"
+        "    ^deprecation_trigger^return isset($this->^name^) ? $this->^name^ : ^default_value^;\n"
         "}\n\n"
         "public function has^camel_name^()\n"
         "{\n"
-        "    return isset($this->^name^);\n"
+        "    ^deprecation_trigger^return isset($this->^name^);\n"
         "}\n\n"
         "public function clear^camel_name^()\n"
         "{\n"
-        "    unset($this->^name^);\n"
+        "    ^deprecation_trigger^unset($this->^name^);\n"
         "}\n\n",
         "camel_name", UnderscoresToCamelCase(field->name(), true),
         "name", field->name(),
-        "default_value", DefaultForField(field));
+        "default_value", DefaultForField(field),
+        "deprecation_trigger", deprecation_trigger);
   } else {
     printer->Print(
         "public function get^camel_name^()\n"
         "{\n"
-        "    return $this->^name^;\n"
+        "    ^deprecation_trigger^return $this->^name^;\n"
         "}\n\n",
-        "camel_name", UnderscoresToCamelCase(field->name(), true), "name",
-        field->name());
+        "camel_name", UnderscoresToCamelCase(field->name(), true),
+        "name", field->name(),
+        "deprecation_trigger", deprecation_trigger);
   }
 
   // For wrapper types, generate an additional getXXXUnwrapped getter
@@ -692,10 +699,11 @@
     printer->Print(
         "public function get^camel_name^Unwrapped()\n"
         "{\n"
-        "    return $this->readWrapperValue(\"^field_name^\");\n"
+        "    ^deprecation_trigger^return $this->readWrapperValue(\"^field_name^\");\n"
         "}\n\n",
         "camel_name", UnderscoresToCamelCase(field->name(), true),
-        "field_name", field->name());
+        "field_name", field->name(),
+        "deprecation_trigger", deprecation_trigger);
   }
 
   // Generate setter.
@@ -707,6 +715,13 @@
 
   Indent(printer);
 
+  if (field->options().deprecated()) {
+      printer->Print(
+          "^deprecation_trigger^",
+          "deprecation_trigger", deprecation_trigger
+      );
+  }
+
   // Type check.
   if (field->is_map()) {
     const Descriptor* map_entry = field->message_type();
@@ -1741,6 +1756,9 @@
       "php_type", PhpGetterTypeName(field, options),
       "maybe_null", can_return_null ? "|null" : "");
   }
+  if (field->options().deprecated()) {
+    printer->Print(" * @deprecated\n");
+  }
   printer->Print(" */\n");
 }