Commit 9e745f77 authored by Matt A's avatar Matt A Committed by Paul Yang

Support PHP generic services (#3269)

* Add php_generic_services option

* Generate PHP generic services

* Respect namespaces for generated PHP services

* Test PHP generated services

* Rename PHP generator service method doc comment function

* Correct phpdoc service method case

* Test namespaced PHP generic services

* Always use the FQCN for PHP generic service input/output

* Add generated_service_test to php test.sh

* Add php service test protos to CI

* Add php service files to php_EXTRA_DIST

* Use Interface suffix for php generic services
parent 33545583
......@@ -662,6 +662,7 @@ php_EXTRA_DIST= \
php/tests/gdb_test.sh \
php/tests/generated_class_test.php \
php/tests/generated_phpdoc_test.php \
php/tests/generated_service_test.php \
php/tests/map_field_test.php \
php/tests/memory_leak_test.php \
php/tests/php_implementation_test.php \
......@@ -672,6 +673,8 @@ php_EXTRA_DIST= \
php/tests/proto/test_no_namespace.proto \
php/tests/proto/test_php_namespace.proto \
php/tests/proto/test_prefix.proto \
php/tests/proto/test_service.proto \
php/tests/proto/test_service_namespace.proto \
php/tests/test.sh \
php/tests/test_base.php \
php/tests/test_util.php \
......
......@@ -10,6 +10,7 @@
<file>tests/generated_phpdoc_test.php</file>
<file>tests/map_field_test.php</file>
<file>tests/well_known_test.php</file>
<file>tests/generated_service_test.php</file>
</testsuite>
</testsuites>
</phpunit>
......@@ -139,6 +139,7 @@ class Descriptor
->optional('cc_generic_services', \Google\Protobuf\Internal\GPBType::BOOL, 16)
->optional('java_generic_services', \Google\Protobuf\Internal\GPBType::BOOL, 17)
->optional('py_generic_services', \Google\Protobuf\Internal\GPBType::BOOL, 18)
->optional('php_generic_services', \Google\Protobuf\Internal\GPBType::BOOL, 19)
->optional('deprecated', \Google\Protobuf\Internal\GPBType::BOOL, 23)
->optional('cc_enable_arenas', \Google\Protobuf\Internal\GPBType::BOOL, 31)
->optional('objc_class_prefix', \Google\Protobuf\Internal\GPBType::STRING, 36)
......
......@@ -109,6 +109,11 @@ class FileOptions extends \Google\Protobuf\Internal\Message
*/
private $py_generic_services = false;
private $has_py_generic_services = false;
/**
* Generated from protobuf field <code>optional bool php_generic_services = 19 [default = false];</code>
*/
private $php_generic_services = false;
private $has_php_generic_services = false;
/**
* Is this file deprecated?
* Depending on the target platform, this can emit Deprecated annotations
......@@ -548,6 +553,34 @@ class FileOptions extends \Google\Protobuf\Internal\Message
return $this->has_py_generic_services;
}
/**
* Generated from protobuf field <code>optional bool php_generic_services = 19 [default = false];</code>
* @return bool
*/
public function getPhpGenericServices()
{
return $this->php_generic_services;
}
/**
* Generated from protobuf field <code>optional bool php_generic_services = 19 [default = false];</code>
* @param bool $var
* @return $this
*/
public function setPhpGenericServices($var)
{
GPBUtil::checkBool($var);
$this->php_generic_services = $var;
$this->has_php_generic_services = true;
return $this;
}
public function hasPhpGenericServices()
{
return $this->has_php_generic_services;
}
/**
* Is this file deprecated?
* Depending on the target platform, this can emit Deprecated annotations
......
<?php
require_once('test_base.php');
require_once('test_util.php');
use Google\Protobuf\Internal\RepeatedField;
use Google\Protobuf\Internal\MapField;
use Google\Protobuf\Internal\GPBType;
use Foo\Greeter;
use Foo\HelloRequest;
use Foo\HelloReply;
class GeneratedServiceTest extends TestBase
{
/**
* @var \ReflectionClass
*/
private $serviceClass;
/**
* @var \ReflectionClass
*/
private $namespacedServiceClass;
/**
* @var array
*/
private $methodNames = [
'sayHello',
'sayHelloAgain'
];
public function setUp()
{
parent::setUp();
$this->serviceClass = new ReflectionClass('Foo\GreeterInterface');
$this->namespacedServiceClass = new ReflectionClass('Bar\OtherGreeterInterface');
}
public function testIsInterface()
{
$this->assertTrue($this->serviceClass->isInterface());
}
public function testPhpDocForClass()
{
$this->assertContains('foo.Greeter', $this->serviceClass->getDocComment());
}
public function testPhpDocForNamespacedClass()
{
$this->assertContains('foo.OtherGreeter', $this->namespacedServiceClass->getDocComment());
}
public function testServiceMethodsAreGenerated()
{
$this->assertCount(count($this->methodNames), $this->serviceClass->getMethods());
foreach ($this->methodNames as $methodName) {
$this->assertTrue($this->serviceClass->hasMethod($methodName));
}
}
public function testPhpDocForServiceMethod()
{
foreach ($this->methodNames as $methodName) {
$docComment = $this->serviceClass->getMethod($methodName)->getDocComment();
$this->assertContains($methodName, $docComment);
$this->assertContains('@param \Foo\HelloRequest $request', $docComment);
$this->assertContains('@return \Foo\HelloReply', $docComment);
}
}
public function testPhpDocForServiceMethodInNamespacedClass()
{
foreach ($this->methodNames as $methodName) {
$docComment = $this->namespacedServiceClass->getMethod($methodName)->getDocComment();
$this->assertContains($methodName, $docComment);
$this->assertContains('@param \Foo\HelloRequest $request', $docComment);
$this->assertContains('@return \Foo\HelloReply', $docComment);
}
}
public function testParamForServiceMethod()
{
foreach ($this->methodNames as $methodName) {
$method = $this->serviceClass->getMethod($methodName);
$this->assertCount(1, $method->getParameters());
$param = $method->getParameters()[0];
$this->assertFalse($param->isOptional());
$this->assertSame('request', $param->getName());
// ReflectionParameter::getType only exists in PHP 7+, so get the type from __toString
$this->assertContains('Foo\HelloRequest $request', (string) $param);
}
}
public function testParamForServiceMethodInNamespacedClass()
{
foreach ($this->methodNames as $methodName) {
$method = $this->serviceClass->getMethod($methodName);
$this->assertCount(1, $method->getParameters());
$param = $method->getParameters()[0];
$this->assertFalse($param->isOptional());
$this->assertSame('request', $param->getName());
// ReflectionParameter::getType only exists in PHP 7+, so get the type from __toString
$this->assertContains('Foo\HelloRequest $request', (string) $param);
}
}
}
syntax = "proto3";
package foo;
option php_generic_services = true;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
syntax = "proto3";
import "proto/test_service.proto";
package foo;
option php_generic_services = true;
option php_namespace = "Bar";
service OtherGreeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}
......@@ -8,7 +8,7 @@ set -e
phpize && ./configure CFLAGS='-g -O0' && make
popd
tests=( array_test.php encode_decode_test.php generated_class_test.php generated_phpdoc_test.php map_field_test.php well_known_test.php )
tests=( array_test.php encode_decode_test.php generated_class_test.php generated_phpdoc_test.php map_field_test.php well_known_test.php generated_service_test.php )
for t in "${tests[@]}"
do
......
......@@ -82,6 +82,10 @@ void GenerateEnumDocComment(io::Printer* printer, const EnumDescriptor* enum_,
int is_descriptor);
void GenerateEnumValueDocComment(io::Printer* printer,
const EnumValueDescriptor* value);
void GenerateServiceDocComment(io::Printer* printer,
const ServiceDescriptor* service);
void GenerateServiceMethodDocComment(io::Printer* printer,
const MethodDescriptor* method);
std::string RenameEmpty(const std::string& name) {
if (name == "Empty") {
......@@ -139,17 +143,9 @@ std::string ClassNamePrefix(const string& classname,
return "";
}
template <typename DescriptorType>
std::string FullClassName(const DescriptorType* desc, bool is_descriptor) {
string classname = desc->name();
const Descriptor* containing = desc->containing_type();
while (containing != NULL) {
classname = containing->name() + '_' + classname;
containing = containing->containing_type();
}
classname = ClassNamePrefix(classname, desc) + classname;
std::string NamespacedName(const string& classname,
const DescriptorType* desc, bool is_descriptor) {
if (desc->file()->options().has_php_namespace()) {
const string& php_namespace = desc->file()->options().php_namespace();
if (php_namespace != "") {
......@@ -167,6 +163,24 @@ std::string FullClassName(const DescriptorType* desc, bool is_descriptor) {
}
}
template <typename DescriptorType>
std::string FullClassName(const DescriptorType* desc, bool is_descriptor) {
string classname = desc->name();
const Descriptor* containing = desc->containing_type();
while (containing != NULL) {
classname = containing->name() + '_' + classname;
containing = containing->containing_type();
}
classname = ClassNamePrefix(classname, desc) + classname;
return NamespacedName(classname, desc, is_descriptor);
}
std::string FullClassName(const ServiceDescriptor* desc, bool is_descriptor) {
string classname = desc->name();
classname = ClassNamePrefix(classname, desc) + classname;
return NamespacedName(classname, desc, is_descriptor);
}
std::string PhpName(const std::string& full_name, bool is_descriptor) {
if (is_descriptor) {
return kDescriptorPackageName;
......@@ -272,6 +286,17 @@ std::string GeneratedEnumFileName(const EnumDescriptor* en,
return result + ".php";
}
std::string GeneratedServiceFileName(const ServiceDescriptor* service,
bool is_descriptor) {
std::string result = FullClassName(service, is_descriptor) + "Interface";
for (int i = 0; i < result.size(); i++) {
if (result[i] == '\\') {
result[i] = '/';
}
}
return result + ".php";
}
std::string IntToString(int32 value) {
std::ostringstream os;
os << value;
......@@ -660,6 +685,16 @@ void GenerateEnumToPool(const EnumDescriptor* en, io::Printer* printer) {
Outdent(printer);
}
void GenerateServiceMethod(const MethodDescriptor* method,
io::Printer* printer) {
printer->Print(
"public function ^camel_name^(\\^request_name^ $request);\n\n",
"camel_name", UnderscoresToCamelCase(method->name(), false),
"request_name", FullClassName(
method->input_type(), false)
);
}
void GenerateMessageToPool(const string& name_prefix, const Descriptor* message,
io::Printer* printer) {
// Don't generate MapEntry messages -- we use the PHP extension's native
......@@ -1061,6 +1096,58 @@ void GenerateMessageFile(const FileDescriptor* file, const Descriptor* message,
}
}
void GenerateServiceFile(const FileDescriptor* file,
const ServiceDescriptor* service, bool is_descriptor,
GeneratorContext* generator_context) {
std::string filename = GeneratedServiceFileName(service, is_descriptor);
scoped_ptr<io::ZeroCopyOutputStream> output(
generator_context->Open(filename));
io::Printer printer(output.get(), '^');
GenerateHead(file, &printer);
std::string fullname = FilenameToClassname(filename);
int lastindex = fullname.find_last_of("\\");
if (file->options().has_php_namespace()) {
const string& php_namespace = file->options().php_namespace();
if (!php_namespace.empty()) {
printer.Print(
"namespace ^name^;\n\n",
"name", php_namespace);
}
} else if (!file->package().empty()) {
printer.Print(
"namespace ^name^;\n\n",
"name", fullname.substr(0, lastindex));
}
GenerateServiceDocComment(&printer, service);
if (lastindex != string::npos) {
printer.Print(
"interface ^name^\n"
"{\n",
"name", fullname.substr(lastindex + 1));
} else {
printer.Print(
"interface ^name^\n"
"{\n",
"name", fullname);
}
Indent(&printer);
for (int i = 0; i < service->method_count(); i++) {
const MethodDescriptor* method = service->method(i);
GenerateServiceMethodDocComment(&printer, method);
GenerateServiceMethod(method, &printer);
}
Outdent(&printer);
printer.Print("}\n\n");
}
void GenerateFile(const FileDescriptor* file, bool is_descriptor,
GeneratorContext* generator_context) {
GenerateMetadataFile(file, is_descriptor, generator_context);
......@@ -1072,6 +1159,12 @@ void GenerateFile(const FileDescriptor* file, bool is_descriptor,
GenerateEnumFile(file, file->enum_type(i), is_descriptor,
generator_context);
}
if (file->options().php_generic_services()) {
for (int i = 0; i < file->service_count(); i++) {
GenerateServiceFile(file, file->service(i), is_descriptor,
generator_context);
}
}
}
static string EscapePhpdoc(const string& input) {
......@@ -1180,6 +1273,16 @@ void GenerateMessageDocComment(io::Printer* printer,
"messagename", EscapePhpdoc(message->full_name()));
}
void GenerateServiceDocComment(io::Printer* printer,
const ServiceDescriptor* service) {
printer->Print("/**\n");
GenerateDocCommentBody(printer, service);
printer->Print(
" * Protobuf type <code>^fullname^</code>\n"
" */\n",
"fullname", EscapePhpdoc(service->full_name()));
}
void GenerateFieldDocComment(io::Printer* printer, const FieldDescriptor* field,
int is_descriptor, int function_type) {
// In theory we should have slightly different comments for setters, getters,
......@@ -1226,6 +1329,23 @@ void GenerateEnumValueDocComment(io::Printer* printer,
"def", EscapePhpdoc(FirstLineOf(value->DebugString())));
}
void GenerateServiceMethodDocComment(io::Printer* printer,
const MethodDescriptor* method) {
printer->Print("/**\n");
GenerateDocCommentBody(printer, method);
printer->Print(
" * Method <code>^method_name^</code>\n"
" *\n",
"method_name", EscapePhpdoc(UnderscoresToCamelCase(method->name(), false)));
printer->Print(
" * @param \\^input_type^ $request\n",
"input_type", EscapePhpdoc(FullClassName(method->input_type(), false)));
printer->Print(
" * @return \\^return_type^\n"
" */\n",
"return_type", EscapePhpdoc(FullClassName(method->output_type(), false)));
}
bool Generator::Generate(const FileDescriptor* file, const string& parameter,
GeneratorContext* generator_context,
string* error) const {
......
This diff is collapsed.
......@@ -2290,6 +2290,13 @@ class LIBPROTOBUF_EXPORT FileOptions : public ::google::protobuf::Message /* @@p
bool py_generic_services() const;
void set_py_generic_services(bool value);
// optional bool php_generic_services = 19 [default = false];
bool has_php_generic_services() const;
void clear_php_generic_services();
static const int kPhpGenericServicesFieldNumber = 19;
bool php_generic_services() const;
void set_php_generic_services(bool value);
// optional bool deprecated = 23 [default = false];
bool has_deprecated() const;
void clear_deprecated();
......@@ -2334,6 +2341,8 @@ class LIBPROTOBUF_EXPORT FileOptions : public ::google::protobuf::Message /* @@p
void clear_has_java_generic_services();
void set_has_py_generic_services();
void clear_has_py_generic_services();
void set_has_php_generic_services();
void clear_has_php_generic_services();
void set_has_deprecated();
void clear_has_deprecated();
void set_has_cc_enable_arenas();
......@@ -2369,6 +2378,7 @@ class LIBPROTOBUF_EXPORT FileOptions : public ::google::protobuf::Message /* @@p
bool cc_generic_services_;
bool java_generic_services_;
bool py_generic_services_;
bool php_generic_services_;
bool deprecated_;
bool cc_enable_arenas_;
int optimize_for_;
......@@ -6644,13 +6654,13 @@ inline void FileOptions::set_java_string_check_utf8(bool value) {
// optional .google.protobuf.FileOptions.OptimizeMode optimize_for = 9 [default = SPEED];
inline bool FileOptions::has_optimize_for() const {
return (_has_bits_[0] & 0x00010000u) != 0;
return (_has_bits_[0] & 0x00020000u) != 0;
}
inline void FileOptions::set_has_optimize_for() {
_has_bits_[0] |= 0x00010000u;
_has_bits_[0] |= 0x00020000u;
}
inline void FileOptions::clear_has_optimize_for() {
_has_bits_[0] &= ~0x00010000u;
_has_bits_[0] &= ~0x00020000u;
}
inline void FileOptions::clear_optimize_for() {
optimize_for_ = 1;
......@@ -6802,15 +6812,39 @@ inline void FileOptions::set_py_generic_services(bool value) {
// @@protoc_insertion_point(field_set:google.protobuf.FileOptions.py_generic_services)
}
// optional bool php_generic_services = 19 [default = false];
inline bool FileOptions::has_php_generic_services() const {
return (_has_bits_[0] & 0x00004000u) != 0;
}
inline void FileOptions::set_has_php_generic_services() {
_has_bits_[0] |= 0x00004000u;
}
inline void FileOptions::clear_has_php_generic_services() {
_has_bits_[0] &= ~0x00004000u;
}
inline void FileOptions::clear_php_generic_services() {
php_generic_services_ = false;
clear_has_php_generic_services();
}
inline bool FileOptions::php_generic_services() const {
// @@protoc_insertion_point(field_get:google.protobuf.FileOptions.php_generic_services)
return php_generic_services_;
}
inline void FileOptions::set_php_generic_services(bool value) {
set_has_php_generic_services();
php_generic_services_ = value;
// @@protoc_insertion_point(field_set:google.protobuf.FileOptions.php_generic_services)
}
// optional bool deprecated = 23 [default = false];
inline bool FileOptions::has_deprecated() const {
return (_has_bits_[0] & 0x00004000u) != 0;
return (_has_bits_[0] & 0x00008000u) != 0;
}
inline void FileOptions::set_has_deprecated() {
_has_bits_[0] |= 0x00004000u;
_has_bits_[0] |= 0x00008000u;
}
inline void FileOptions::clear_has_deprecated() {
_has_bits_[0] &= ~0x00004000u;
_has_bits_[0] &= ~0x00008000u;
}
inline void FileOptions::clear_deprecated() {
deprecated_ = false;
......@@ -6828,13 +6862,13 @@ inline void FileOptions::set_deprecated(bool value) {
// optional bool cc_enable_arenas = 31 [default = false];
inline bool FileOptions::has_cc_enable_arenas() const {
return (_has_bits_[0] & 0x00008000u) != 0;
return (_has_bits_[0] & 0x00010000u) != 0;
}
inline void FileOptions::set_has_cc_enable_arenas() {
_has_bits_[0] |= 0x00008000u;
_has_bits_[0] |= 0x00010000u;
}
inline void FileOptions::clear_has_cc_enable_arenas() {
_has_bits_[0] &= ~0x00008000u;
_has_bits_[0] &= ~0x00010000u;
}
inline void FileOptions::clear_cc_enable_arenas() {
cc_enable_arenas_ = false;
......
......@@ -351,6 +351,7 @@ message FileOptions {
optional bool cc_generic_services = 16 [default=false];
optional bool java_generic_services = 17 [default=false];
optional bool py_generic_services = 18 [default=false];
optional bool php_generic_services = 19 [default=false];
// Is this file deprecated?
// Depending on the target platform, this can emit Deprecated annotations
......
......@@ -346,7 +346,7 @@ generate_php_test_proto() {
# Generate test file
rm -rf generated
mkdir generated
../../src/protoc --php_out=generated proto/test.proto proto/test_include.proto proto/test_no_namespace.proto proto/test_prefix.proto proto/test_php_namespace.proto proto/test_empty_php_namespace.proto
../../src/protoc --php_out=generated proto/test.proto proto/test_include.proto proto/test_no_namespace.proto proto/test_prefix.proto proto/test_php_namespace.proto proto/test_empty_php_namespace.proto proto/test_service.proto proto/test_service_namespace.proto
pushd ../../src
./protoc --php_out=../php/tests/generated google/protobuf/empty.proto
./protoc --php_out=../php/tests/generated -I../php/tests -I. ../php/tests/proto/test_import_descriptor_proto.proto
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment