1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc. All rights reserved.
3 //
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd
7
8 #include "google/protobuf/compiler/php/php_generator.h"
9
10 #include <algorithm>
11 #include <sstream>
12 #include <string>
13 #include <vector>
14
15 #include "absl/container/flat_hash_map.h"
16 #include "absl/container/flat_hash_set.h"
17 #include "absl/log/absl_log.h"
18 #include "absl/strings/ascii.h"
19 #include "absl/strings/escaping.h"
20 #include "absl/strings/str_cat.h"
21 #include "absl/strings/str_replace.h"
22 #include "absl/strings/str_split.h"
23 #include "absl/strings/string_view.h"
24 #include "google/protobuf/compiler/code_generator.h"
25 #include "google/protobuf/compiler/php/names.h"
26 #include "google/protobuf/compiler/retention.h"
27 #include "google/protobuf/descriptor.h"
28 #include "google/protobuf/descriptor.pb.h"
29 #include "google/protobuf/io/printer.h"
30 #include "google/protobuf/io/zero_copy_stream.h"
31
32 constexpr absl::string_view kDescriptorFile =
33 "google/protobuf/descriptor.proto";
34 constexpr absl::string_view kEmptyFile = "google/protobuf/empty.proto";
35 constexpr absl::string_view kEmptyMetadataFile =
36 "GPBMetadata/Google/Protobuf/GPBEmpty.php";
37 constexpr absl::string_view kDescriptorMetadataFile =
38 "GPBMetadata/Google/Protobuf/Internal/Descriptor.php";
39 constexpr absl::string_view kDescriptorPackageName =
40 "Google\\Protobuf\\Internal";
41 constexpr absl::string_view kValidConstantNames[] = {
42 "int", "float", "bool", "string", "true", "false",
43 "null", "void", "iterable", "parent", "self", "readonly"};
44 const int kValidConstantNamesSize = 12;
45 const int kFieldSetter = 1;
46 const int kFieldGetter = 2;
47 const int kFieldProperty = 3;
48
49 namespace google {
50 namespace protobuf {
51 namespace compiler {
52 namespace php {
53
54 struct Options {
55 bool is_descriptor = false;
56 bool aggregate_metadata = false;
57 bool gen_c_wkt = false;
58 absl::flat_hash_set<std::string> aggregate_metadata_prefixes;
59 };
60
61 namespace {
62
63 // Forward decls.
64 std::string PhpName(absl::string_view full_name, const Options& options);
65 std::string IntToString(int32_t value);
66 std::string FilenameToClassname(absl::string_view filename);
67 std::string GeneratedMetadataFileName(const FileDescriptor* file,
68 const Options& options);
69 std::string UnderscoresToCamelCase(absl::string_view name,
70 bool cap_first_letter);
71 void Indent(io::Printer* printer);
72 void Outdent(io::Printer* printer);
73 void GenerateAddFilesToPool(const FileDescriptor* file, const Options& options,
74 io::Printer* printer);
75 void GenerateMessageDocComment(io::Printer* printer, const Descriptor* message,
76 const Options& options);
77 void GenerateMessageConstructorDocComment(io::Printer* printer,
78 const Descriptor* message,
79 const Options& options);
80 void GenerateFieldDocComment(io::Printer* printer, const FieldDescriptor* field,
81 const Options& options, int function_type);
82 void GenerateWrapperFieldGetterDocComment(io::Printer* printer,
83 const FieldDescriptor* field);
84 void GenerateWrapperFieldSetterDocComment(io::Printer* printer,
85 const FieldDescriptor* field);
86 void GenerateEnumDocComment(io::Printer* printer, const EnumDescriptor* enum_,
87 const Options& options);
88 void GenerateEnumValueDocComment(io::Printer* printer,
89 const EnumValueDescriptor* value);
90
91 template <typename DescriptorType>
DescriptorFullName(const DescriptorType * desc,bool is_internal)92 std::string DescriptorFullName(const DescriptorType* desc, bool is_internal) {
93 absl::string_view full_name = desc->full_name();
94 if (is_internal) {
95 constexpr absl::string_view replace = "google.protobuf";
96 size_t index = full_name.find(replace);
97 if (index != std::string::npos) {
98 return absl::StrCat(full_name.substr(0, index),
99 "google.protobuf.internal",
100 full_name.substr(index + replace.size()));
101 }
102 }
103 return std::string(full_name);
104 }
105
ConstantNamePrefix(absl::string_view classname)106 std::string ConstantNamePrefix(absl::string_view classname) {
107 bool is_reserved = false;
108
109 std::string lower = absl::AsciiStrToLower(classname);
110
111 is_reserved = IsReservedName(lower);
112
113 for (int i = 0; i < kValidConstantNamesSize; i++) {
114 if (lower == kValidConstantNames[i]) {
115 is_reserved = false;
116 break;
117 }
118 }
119
120 if (is_reserved) {
121 return "PB";
122 }
123
124 return "";
125 }
126
127 template <typename DescriptorType>
RootPhpNamespace(const DescriptorType * desc,const Options & options)128 std::string RootPhpNamespace(const DescriptorType* desc,
129 const Options& options) {
130 if (desc->file()->options().has_php_namespace()) {
131 absl::string_view php_namespace = desc->file()->options().php_namespace();
132 if (!php_namespace.empty()) {
133 return std::string(php_namespace);
134 }
135 return "";
136 }
137
138 if (!desc->file()->package().empty()) {
139 return PhpName(desc->file()->package(), options);
140 }
141 return "";
142 }
143
144 template <typename DescriptorType>
FullClassName(const DescriptorType * desc,const Options & options)145 std::string FullClassName(const DescriptorType* desc, const Options& options) {
146 std::string classname = GeneratedClassName(desc);
147 std::string php_namespace = RootPhpNamespace(desc, options);
148 if (!php_namespace.empty()) {
149 return absl::StrCat(php_namespace, "\\", classname);
150 }
151 return classname;
152 }
153
154 template <typename DescriptorType>
FullClassName(const DescriptorType * desc,bool is_descriptor)155 std::string FullClassName(const DescriptorType* desc, bool is_descriptor) {
156 Options options;
157 options.is_descriptor = is_descriptor;
158 return FullClassName(desc, options);
159 }
160
PhpNamePrefix(absl::string_view classname)161 std::string PhpNamePrefix(absl::string_view classname) {
162 if (IsReservedName(classname)) return "PB";
163 return "";
164 }
165
PhpName(absl::string_view full_name,const Options & options)166 std::string PhpName(absl::string_view full_name, const Options& options) {
167 if (options.is_descriptor) {
168 return std::string(kDescriptorPackageName);
169 }
170
171 std::string segment;
172 std::string result;
173 bool cap_next_letter = true;
174 for (int i = 0; i < full_name.size(); i++) {
175 if ('a' <= full_name[i] && full_name[i] <= 'z' && cap_next_letter) {
176 segment += full_name[i] + ('A' - 'a');
177 cap_next_letter = false;
178 } else if (full_name[i] == '.') {
179 result += PhpNamePrefix(segment) + segment + '\\';
180 segment = "";
181 cap_next_letter = true;
182 } else {
183 segment += full_name[i];
184 cap_next_letter = false;
185 }
186 }
187 result += PhpNamePrefix(segment) + segment;
188 return result;
189 }
190
DefaultForField(const FieldDescriptor * field)191 std::string DefaultForField(const FieldDescriptor* field) {
192 switch (field->type()) {
193 case FieldDescriptor::TYPE_INT32:
194 case FieldDescriptor::TYPE_INT64:
195 case FieldDescriptor::TYPE_UINT32:
196 case FieldDescriptor::TYPE_UINT64:
197 case FieldDescriptor::TYPE_SINT32:
198 case FieldDescriptor::TYPE_SINT64:
199 case FieldDescriptor::TYPE_FIXED32:
200 case FieldDescriptor::TYPE_FIXED64:
201 case FieldDescriptor::TYPE_SFIXED32:
202 case FieldDescriptor::TYPE_SFIXED64:
203 case FieldDescriptor::TYPE_ENUM:
204 return "0";
205 case FieldDescriptor::TYPE_DOUBLE:
206 case FieldDescriptor::TYPE_FLOAT:
207 return "0.0";
208 case FieldDescriptor::TYPE_BOOL:
209 return "false";
210 case FieldDescriptor::TYPE_STRING:
211 case FieldDescriptor::TYPE_BYTES:
212 return "''";
213 case FieldDescriptor::TYPE_MESSAGE:
214 case FieldDescriptor::TYPE_GROUP:
215 return "null";
216 default:
217 assert(false);
218 return "";
219 }
220 }
221
DeprecatedConditionalForField(const FieldDescriptor * field)222 std::string DeprecatedConditionalForField(const FieldDescriptor* field) {
223 if (field->is_repeated()) {
224 return absl::StrCat("$this->", field->name(), "->count() !== 0");
225 }
226 if (field->real_containing_oneof() != nullptr) {
227 return absl::StrCat("$this->hasOneof(", field->number(), ")");
228 }
229 if (field->has_presence()) {
230 return absl::StrCat("isset($this->", field->name(), ")");
231 }
232 return absl::StrCat("$this->", field->name(), " !== ",
233 field->has_presence() ? "null" : DefaultForField(field));
234 }
235
GeneratedMetadataFileName(const FileDescriptor * file,const Options & options)236 std::string GeneratedMetadataFileName(const FileDescriptor* file,
237 const Options& options) {
238 absl::string_view proto_file = file->name();
239 int start_index = 0;
240 int first_index = proto_file.find_first_of('/', start_index);
241 std::string result = "";
242 std::string segment = "";
243
244 if (proto_file == kEmptyFile) {
245 return std::string(kEmptyMetadataFile);
246 }
247 if (options.is_descriptor) {
248 return std::string(kDescriptorMetadataFile);
249 }
250
251 // Append directory name.
252 absl::string_view file_no_suffix;
253 int lastindex = proto_file.find_last_of('.');
254 if (proto_file == kEmptyFile) {
255 return std::string(kEmptyMetadataFile);
256 } else {
257 file_no_suffix = proto_file.substr(0, lastindex);
258 }
259
260 if (file->options().has_php_metadata_namespace()) {
261 absl::string_view php_metadata_namespace =
262 file->options().php_metadata_namespace();
263 if (!php_metadata_namespace.empty() && php_metadata_namespace != "\\") {
264 absl::StrAppend(&result, php_metadata_namespace);
265 std::replace(result.begin(), result.end(), '\\', '/');
266 if (result.at(result.size() - 1) != '/') {
267 absl::StrAppend(&result, "/");
268 }
269 }
270 } else {
271 absl::StrAppend(&result, "GPBMetadata/");
272 while (first_index != std::string::npos) {
273 segment = UnderscoresToCamelCase(
274 file_no_suffix.substr(start_index, first_index - start_index), true);
275 absl::StrAppend(&result, ReservedNamePrefix(segment, file), segment, "/");
276 start_index = first_index + 1;
277 first_index = file_no_suffix.find_first_of('/', start_index);
278 }
279 }
280
281 // Append file name.
282 int file_name_start = file_no_suffix.find_last_of('/');
283 if (file_name_start == std::string::npos) {
284 file_name_start = 0;
285 } else {
286 file_name_start += 1;
287 }
288 segment = UnderscoresToCamelCase(
289 file_no_suffix.substr(file_name_start, first_index - file_name_start),
290 true);
291
292 return absl::StrCat(result, ReservedNamePrefix(segment, file), segment,
293 ".php");
294 }
295
GeneratedMetadataFileName(const FileDescriptor * file,bool is_descriptor)296 std::string GeneratedMetadataFileName(const FileDescriptor* file,
297 bool is_descriptor) {
298 Options options;
299 options.is_descriptor = is_descriptor;
300 return GeneratedMetadataFileName(file, options);
301 }
302
303 template <typename DescriptorType>
GeneratedClassFileName(const DescriptorType * desc,const Options & options)304 std::string GeneratedClassFileName(const DescriptorType* desc,
305 const Options& options) {
306 std::string result = FullClassName(desc, options);
307 for (int i = 0; i < result.size(); i++) {
308 if (result[i] == '\\') {
309 result[i] = '/';
310 }
311 }
312 return absl::StrCat(result, ".php");
313 }
314
IntToString(int32_t value)315 std::string IntToString(int32_t value) {
316 std::ostringstream os;
317 os << value;
318 return os.str();
319 }
320
LabelForField(const FieldDescriptor * field)321 std::string LabelForField(const FieldDescriptor* field) {
322 switch (field->label()) {
323 case FieldDescriptor::LABEL_OPTIONAL:
324 return "optional";
325 case FieldDescriptor::LABEL_REQUIRED:
326 return "required";
327 case FieldDescriptor::LABEL_REPEATED:
328 return "repeated";
329 default:
330 assert(false);
331 return "";
332 }
333 }
334
PhpSetterTypeName(const FieldDescriptor * field,const Options & options)335 std::string PhpSetterTypeName(const FieldDescriptor* field,
336 const Options& options) {
337 if (field->is_map()) {
338 return "array|\\Google\\Protobuf\\Internal\\MapField";
339 }
340 std::string type;
341 switch (field->type()) {
342 case FieldDescriptor::TYPE_INT32:
343 case FieldDescriptor::TYPE_UINT32:
344 case FieldDescriptor::TYPE_SINT32:
345 case FieldDescriptor::TYPE_FIXED32:
346 case FieldDescriptor::TYPE_SFIXED32:
347 case FieldDescriptor::TYPE_ENUM:
348 type = "int";
349 break;
350 case FieldDescriptor::TYPE_INT64:
351 case FieldDescriptor::TYPE_UINT64:
352 case FieldDescriptor::TYPE_SINT64:
353 case FieldDescriptor::TYPE_FIXED64:
354 case FieldDescriptor::TYPE_SFIXED64:
355 type = "int|string";
356 break;
357 case FieldDescriptor::TYPE_DOUBLE:
358 case FieldDescriptor::TYPE_FLOAT:
359 type = "float";
360 break;
361 case FieldDescriptor::TYPE_BOOL:
362 type = "bool";
363 break;
364 case FieldDescriptor::TYPE_STRING:
365 case FieldDescriptor::TYPE_BYTES:
366 type = "string";
367 break;
368 case FieldDescriptor::TYPE_MESSAGE:
369 type = absl::StrCat("\\", FullClassName(field->message_type(), options));
370 break;
371 case FieldDescriptor::TYPE_GROUP:
372 return "null";
373 default:
374 assert(false);
375 return "";
376 }
377 if (field->is_repeated()) {
378 // accommodate for edge case with multiple types.
379 size_t start_pos = type.find('|');
380 if (start_pos != std::string::npos) {
381 type.replace(start_pos, 1, ">|array<");
382 }
383 type = absl::StrCat("array<", type,
384 ">|\\Google\\Protobuf\\Internal\\RepeatedField");
385 }
386 return type;
387 }
388
PhpSetterTypeName(const FieldDescriptor * field,bool is_descriptor)389 std::string PhpSetterTypeName(const FieldDescriptor* field,
390 bool is_descriptor) {
391 Options options;
392 options.is_descriptor = is_descriptor;
393 return PhpSetterTypeName(field, options);
394 }
395
PhpGetterTypeName(const FieldDescriptor * field,const Options & options)396 std::string PhpGetterTypeName(const FieldDescriptor* field,
397 const Options& options) {
398 if (field->is_map()) {
399 return "\\Google\\Protobuf\\Internal\\MapField";
400 }
401 if (field->is_repeated()) {
402 return "\\Google\\Protobuf\\Internal\\RepeatedField";
403 }
404 switch (field->type()) {
405 case FieldDescriptor::TYPE_INT32:
406 case FieldDescriptor::TYPE_UINT32:
407 case FieldDescriptor::TYPE_SINT32:
408 case FieldDescriptor::TYPE_FIXED32:
409 case FieldDescriptor::TYPE_SFIXED32:
410 case FieldDescriptor::TYPE_ENUM:
411 return "int";
412 case FieldDescriptor::TYPE_INT64:
413 case FieldDescriptor::TYPE_UINT64:
414 case FieldDescriptor::TYPE_SINT64:
415 case FieldDescriptor::TYPE_FIXED64:
416 case FieldDescriptor::TYPE_SFIXED64:
417 return "int|string";
418 case FieldDescriptor::TYPE_DOUBLE:
419 case FieldDescriptor::TYPE_FLOAT:
420 return "float";
421 case FieldDescriptor::TYPE_BOOL:
422 return "bool";
423 case FieldDescriptor::TYPE_STRING:
424 case FieldDescriptor::TYPE_BYTES:
425 return "string";
426 case FieldDescriptor::TYPE_MESSAGE:
427 return absl::StrCat("\\", FullClassName(field->message_type(), options));
428 case FieldDescriptor::TYPE_GROUP:
429 return "null";
430 default:
431 assert(false);
432 return "";
433 }
434 }
435
PhpGetterTypeName(const FieldDescriptor * field,bool is_descriptor)436 std::string PhpGetterTypeName(const FieldDescriptor* field,
437 bool is_descriptor) {
438 Options options;
439 options.is_descriptor = is_descriptor;
440 return PhpGetterTypeName(field, options);
441 }
442
EnumOrMessageSuffix(const FieldDescriptor * field,const Options & options)443 std::string EnumOrMessageSuffix(const FieldDescriptor* field,
444 const Options& options) {
445 if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
446 return absl::StrCat(
447 ", '", DescriptorFullName(field->message_type(), options.is_descriptor),
448 "'");
449 }
450 if (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) {
451 return absl::StrCat(
452 ", '", DescriptorFullName(field->enum_type(), options.is_descriptor),
453 "'");
454 }
455 return "";
456 }
457
EnumOrMessageSuffix(const FieldDescriptor * field,bool is_descriptor)458 std::string EnumOrMessageSuffix(const FieldDescriptor* field,
459 bool is_descriptor) {
460 Options options;
461 options.is_descriptor = is_descriptor;
462 return EnumOrMessageSuffix(field, options);
463 }
464
465 // Converts a name to camel-case. If cap_first_letter is true, capitalize the
466 // first letter.
UnderscoresToCamelCase(absl::string_view name,bool cap_first_letter)467 std::string UnderscoresToCamelCase(absl::string_view name,
468 bool cap_first_letter) {
469 std::string result;
470 for (int i = 0; i < name.size(); i++) {
471 if ('a' <= name[i] && name[i] <= 'z') {
472 if (cap_first_letter) {
473 result += name[i] + ('A' - 'a');
474 } else {
475 result += name[i];
476 }
477 cap_first_letter = false;
478 } else if ('A' <= name[i] && name[i] <= 'Z') {
479 if (i == 0 && !cap_first_letter) {
480 // Force first letter to lower-case unless explicitly told to
481 // capitalize it.
482 result += name[i] + ('a' - 'A');
483 } else {
484 // Capital letters after the first are left as-is.
485 result += name[i];
486 }
487 cap_first_letter = false;
488 } else if ('0' <= name[i] && name[i] <= '9') {
489 result += name[i];
490 cap_first_letter = true;
491 } else {
492 cap_first_letter = true;
493 }
494 }
495 // Add a trailing "_" if the name should be altered.
496 if (name[name.size() - 1] == '#') {
497 result += '_';
498 }
499 return result;
500 }
501
Indent(io::Printer * printer)502 void Indent(io::Printer* printer) {
503 printer->Indent();
504 printer->Indent();
505 }
Outdent(io::Printer * printer)506 void Outdent(io::Printer* printer) {
507 printer->Outdent();
508 printer->Outdent();
509 }
510
BinaryToPhpString(const std::string & src)511 std::string BinaryToPhpString(const std::string& src) {
512 std::string dest;
513 size_t i;
514 unsigned char symbol[16] = {
515 '0', '1', '2', '3', '4', '5', '6', '7',
516 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
517 };
518
519 dest.reserve(src.size() * 1.2);
520
521 for (i = 0; i < src.size(); i++) {
522 // To escape:
523 // - escape sequences
524 // - variable expansion: "hello $username";
525 // - string termination
526 if (src[i] == '\\' || src[i] == '$' || src[i] == '"') {
527 dest += '\\';
528 dest += src[i];
529 } else if (absl::ascii_isprint(src[i])) {
530 dest += src[i];
531 } else {
532 dest += '\\';
533 dest += 'x';
534 dest += symbol[(src[i] & 0xf0) >> 4];
535 dest += symbol[src[i] & 0x0f];
536 }
537 }
538
539 return dest;
540 }
541
GenerateField(const FieldDescriptor * field,io::Printer * printer,const Options & options,std::string * error)542 bool GenerateField(const FieldDescriptor* field, io::Printer* printer,
543 const Options& options, std::string* error) {
544 if (field->is_required()) {
545 *error = absl::StrCat("Can't generate PHP code for required field ",
546 field->full_name(), ".\n");
547 return false;
548 }
549 if (field->type() == FieldDescriptor::TYPE_GROUP) {
550 *error = absl::StrCat("Can't generate PHP code for group field ",
551 field->full_name(),
552 ". Use regular message encoding instead.\n");
553 return false;
554 }
555
556 if (field->is_repeated()) {
557 GenerateFieldDocComment(printer, field, options, kFieldProperty);
558 printer->Print("private $^name^;\n", "name", field->name());
559 } else if (field->real_containing_oneof()) {
560 // Oneof fields are handled by GenerateOneofField.
561 return true;
562 } else {
563 std::string initial_value =
564 field->has_presence() ? "null" : DefaultForField(field);
565 GenerateFieldDocComment(printer, field, options, kFieldProperty);
566 printer->Print("protected $^name^ = ^initial_value^;\n", "name",
567 field->name(), "initial_value", initial_value);
568 }
569 return true;
570 }
571
GenerateOneofField(const OneofDescriptor * oneof,io::Printer * printer)572 void GenerateOneofField(const OneofDescriptor* oneof, io::Printer* printer) {
573 // Oneof property needs to be protected in order to be accessed by parent
574 // class in implementation.
575 printer->Print("protected $^name^;\n", "name", oneof->name());
576 }
577
GenerateFieldAccessor(const FieldDescriptor * field,const Options & options,io::Printer * printer)578 void GenerateFieldAccessor(const FieldDescriptor* field, const Options& options,
579 io::Printer* printer) {
580 const OneofDescriptor* oneof = field->real_containing_oneof();
581
582 // Generate getter.
583 GenerateFieldDocComment(printer, field, options, kFieldGetter);
584
585 // deprecation
586 std::string deprecation_trigger =
587 (field->options().deprecated())
588 ? absl::StrCat("@trigger_error('", field->name(),
589 " is deprecated.', E_USER_DEPRECATED);\n ")
590 : "";
591 std::string deprecation_trigger_with_conditional =
592 (field->options().deprecated())
593 ? absl::StrCat("if (" + DeprecatedConditionalForField(field),
594 ") {\n ", deprecation_trigger,
595 "}\n ")
596 : "";
597
598 // Emit getter.
599 if (oneof != nullptr) {
600 printer->Print(
601 "public function get^camel_name^()\n"
602 "{\n"
603 " ^deprecation_trigger^return $this->readOneof(^number^);\n"
604 "}\n\n",
605 "camel_name", UnderscoresToCamelCase(field->name(), true), "number",
606 IntToString(field->number()), "deprecation_trigger",
607 deprecation_trigger_with_conditional);
608 } else if (field->has_presence() && !field->message_type()) {
609 printer->Print(
610 "public function get^camel_name^()\n"
611 "{\n"
612 " ^deprecation_trigger^return isset($this->^name^) ? $this->^name^ "
613 ": ^default_value^;\n"
614 "}\n\n",
615 "camel_name", UnderscoresToCamelCase(field->name(), true), "name",
616 field->name(), "default_value", DefaultForField(field),
617 "deprecation_trigger", deprecation_trigger_with_conditional);
618 } else {
619 printer->Print(
620 "public function get^camel_name^()\n"
621 "{\n"
622 " ^deprecation_trigger^return $this->^name^;\n"
623 "}\n\n",
624 "camel_name", UnderscoresToCamelCase(field->name(), true), "name",
625 field->name(), "deprecation_trigger",
626 deprecation_trigger_with_conditional);
627 }
628
629 // Emit hazzers/clear.
630 if (oneof) {
631 printer->Print(
632 "public function has^camel_name^()\n"
633 "{\n"
634 " ^deprecation_trigger^return $this->hasOneof(^number^);\n"
635 "}\n\n",
636 "camel_name", UnderscoresToCamelCase(field->name(), true), "number",
637 IntToString(field->number()), "deprecation_trigger",
638 deprecation_trigger_with_conditional);
639 } else if (field->has_presence()) {
640 printer->Print(
641 "public function has^camel_name^()\n"
642 "{\n"
643 " ^deprecation_trigger_with_conditional^return isset($this->^name^);"
644 "\n}\n\n"
645 "public function clear^camel_name^()\n"
646 "{\n"
647 " ^deprecation_trigger^unset($this->^name^);\n"
648 "}\n\n",
649 "camel_name", UnderscoresToCamelCase(field->name(), true), "name",
650 field->name(), "default_value", DefaultForField(field),
651 "deprecation_trigger", deprecation_trigger,
652 "deprecation_trigger_with_conditional",
653 deprecation_trigger_with_conditional);
654 }
655
656 // For wrapper types, generate an additional getXXXUnwrapped getter
657 if (!field->is_map() && !field->is_repeated() &&
658 field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE &&
659 IsWrapperType(field)) {
660 GenerateWrapperFieldGetterDocComment(printer, field);
661 printer->Print(
662 "public function get^camel_name^Unwrapped()\n"
663 "{\n"
664 " ^deprecation_trigger^return "
665 "$this->readWrapperValue(\"^field_name^\");\n"
666 "}\n\n",
667 "camel_name", UnderscoresToCamelCase(field->name(), true), "field_name",
668 field->name(), "deprecation_trigger",
669 deprecation_trigger_with_conditional);
670 }
671
672 // Generate setter.
673 GenerateFieldDocComment(printer, field, options, kFieldSetter);
674 printer->Print(
675 "public function set^camel_name^($var)\n"
676 "{\n",
677 "camel_name", UnderscoresToCamelCase(field->name(), true));
678
679 Indent(printer);
680
681 if (field->options().deprecated() && !field->is_map() &&
682 !field->is_repeated()) {
683 printer->Print("^deprecation_trigger^", "deprecation_trigger",
684 deprecation_trigger);
685 }
686
687 // Type check.
688 if (field->is_map()) {
689 const Descriptor* map_entry = field->message_type();
690 const FieldDescriptor* key = map_entry->map_key();
691 const FieldDescriptor* value = map_entry->map_value();
692 printer->Print(
693 "$arr = GPBUtil::checkMapField($var, "
694 "\\Google\\Protobuf\\Internal\\GPBType::^key_type^, "
695 "\\Google\\Protobuf\\Internal\\GPBType::^value_type^",
696 "key_type", absl::AsciiStrToUpper(key->type_name()), "value_type",
697 absl::AsciiStrToUpper(value->type_name()));
698 if (value->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
699 printer->Print(", \\^class_name^);\n", "class_name",
700 absl::StrCat(FullClassName(value->message_type(), options),
701 "::class"));
702 } else if (value->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) {
703 printer->Print(
704 ", \\^class_name^);\n", "class_name",
705 absl::StrCat(FullClassName(value->enum_type(), options), "::class"));
706 } else {
707 printer->Print(");\n");
708 }
709 } else if (field->is_repeated()) {
710 printer->Print(
711 "$arr = GPBUtil::checkRepeatedField($var, "
712 "\\Google\\Protobuf\\Internal\\GPBType::^type^",
713 "type", absl::AsciiStrToUpper(field->type_name()));
714 if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
715 printer->Print(", \\^class_name^);\n", "class_name",
716 absl::StrCat(FullClassName(field->message_type(), options),
717 "::class"));
718 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) {
719 printer->Print(
720 ", \\^class_name^);\n", "class_name",
721 absl::StrCat(FullClassName(field->enum_type(), options), "::class"));
722 } else {
723 printer->Print(");\n");
724 }
725 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
726 printer->Print("GPBUtil::checkMessage($var, \\^class_name^::class);\n",
727 "class_name", FullClassName(field->message_type(), options));
728 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) {
729 printer->Print("GPBUtil::checkEnum($var, \\^class_name^::class);\n",
730 "class_name", FullClassName(field->enum_type(), options));
731 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_STRING) {
732 printer->Print(
733 "GPBUtil::checkString($var, ^utf8^);\n", "utf8",
734 field->type() == FieldDescriptor::TYPE_STRING ? "True" : "False");
735 } else {
736 printer->Print("GPBUtil::check^type^($var);\n", "type",
737 UnderscoresToCamelCase(field->cpp_type_name(), true));
738 }
739
740 if (field->options().deprecated() &&
741 (field->is_map() || field->is_repeated())) {
742 printer->Print("if ($arr->count() !== 0) {\n ^deprecation_trigger^}\n",
743 "deprecation_trigger", deprecation_trigger);
744 }
745
746 if (oneof != nullptr) {
747 printer->Print("$this->writeOneof(^number^, $var);\n", "number",
748 IntToString(field->number()));
749 } else if (field->is_repeated()) {
750 printer->Print("$this->^name^ = $arr;\n", "name", field->name());
751 } else {
752 printer->Print("$this->^name^ = $var;\n", "name", field->name());
753 }
754
755 printer->Print("\nreturn $this;\n");
756
757 Outdent(printer);
758
759 printer->Print("}\n\n");
760
761 // For wrapper types, generate an additional setXXXValue getter
762 if (!field->is_map() && !field->is_repeated() &&
763 field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE &&
764 IsWrapperType(field)) {
765 GenerateWrapperFieldSetterDocComment(printer, field);
766 printer->Print(
767 "public function set^camel_name^Unwrapped($var)\n"
768 "{\n"
769 " $this->writeWrapperValue(\"^field_name^\", $var);\n"
770 " return $this;"
771 "}\n\n",
772 "camel_name", UnderscoresToCamelCase(field->name(), true), "field_name",
773 field->name());
774 }
775 }
776
GenerateEnumToPool(const EnumDescriptor * en,io::Printer * printer)777 void GenerateEnumToPool(const EnumDescriptor* en, io::Printer* printer) {
778 printer->Print(
779 "$pool->addEnum('^name^', "
780 "\\Google\\Protobuf\\Internal\\^class_name^::class)\n",
781 "name", DescriptorFullName(en, true), "class_name", en->name());
782 Indent(printer);
783
784 for (int i = 0; i < en->value_count(); i++) {
785 const EnumValueDescriptor* value = en->value(i);
786 printer->Print(
787 "->value(\"^name^\", ^number^)\n", "name",
788 absl::StrCat(ConstantNamePrefix(value->name()), value->name()),
789 "number", IntToString(value->number()));
790 }
791 printer->Print("->finalizeToPool();\n\n");
792 Outdent(printer);
793 }
794
GenerateMessageToPool(absl::string_view name_prefix,const Descriptor * message,io::Printer * printer)795 void GenerateMessageToPool(absl::string_view name_prefix,
796 const Descriptor* message, io::Printer* printer) {
797 // Don't generate MapEntry messages -- we use the PHP extension's native
798 // support for map fields instead.
799 if (message->options().map_entry()) {
800 return;
801 }
802 std::string class_name = absl::StrCat(
803 name_prefix.empty() ? "" : absl::StrCat(name_prefix, "\\"),
804 ReservedNamePrefix(message->name(), message->file()), message->name());
805
806 printer->Print(
807 "$pool->addMessage('^message^', "
808 "\\Google\\Protobuf\\Internal\\^class_name^::class)\n",
809 "message", DescriptorFullName(message, true), "class_name", class_name);
810
811 Indent(printer);
812
813 for (int i = 0; i < message->field_count(); i++) {
814 const FieldDescriptor* field = message->field(i);
815 if (field->is_map()) {
816 const FieldDescriptor* key = field->message_type()->map_key();
817 const FieldDescriptor* val = field->message_type()->map_value();
818 printer->Print(
819 "->map('^field^', \\Google\\Protobuf\\Internal\\GPBType::^key^, "
820 "\\Google\\Protobuf\\Internal\\GPBType::^value^, ^number^^other^)\n",
821 "field", field->name(), "key",
822 absl::AsciiStrToUpper(key->type_name()), "value",
823 absl::AsciiStrToUpper(val->type_name()), "number",
824 absl::StrCat(field->number()), "other",
825 EnumOrMessageSuffix(val, true));
826 } else if (!field->real_containing_oneof()) {
827 printer->Print(
828 "->^label^('^field^', "
829 "\\Google\\Protobuf\\Internal\\GPBType::^type^, ^number^^other^)\n",
830 "field", field->name(), "label", LabelForField(field), "type",
831 absl::AsciiStrToUpper(field->type_name()), "number",
832 absl::StrCat(field->number()), "other",
833 EnumOrMessageSuffix(field, true));
834 }
835 }
836
837 // oneofs.
838 for (int i = 0; i < message->real_oneof_decl_count(); i++) {
839 const OneofDescriptor* oneof = message->oneof_decl(i);
840 printer->Print("->oneof(^name^)\n", "name", oneof->name());
841 Indent(printer);
842 for (int index = 0; index < oneof->field_count(); index++) {
843 const FieldDescriptor* field = oneof->field(index);
844 printer->Print(
845 "->value('^field^', "
846 "\\Google\\Protobuf\\Internal\\GPBType::^type^, ^number^^other^)\n",
847 "field", field->name(), "type",
848 absl::AsciiStrToUpper(field->type_name()), "number",
849 absl::StrCat(field->number()), "other",
850 EnumOrMessageSuffix(field, true));
851 }
852 printer->Print("->finish()\n");
853 Outdent(printer);
854 }
855
856 printer->Print("->finalizeToPool();\n");
857
858 Outdent(printer);
859
860 printer->Print("\n");
861
862 for (int i = 0; i < message->nested_type_count(); i++) {
863 GenerateMessageToPool(class_name, message->nested_type(i), printer);
864 }
865 for (int i = 0; i < message->enum_type_count(); i++) {
866 GenerateEnumToPool(message->enum_type(i), printer);
867 }
868 }
869
GenerateAddFileToPool(const FileDescriptor * file,const Options & options,io::Printer * printer)870 void GenerateAddFileToPool(const FileDescriptor* file, const Options& options,
871 io::Printer* printer) {
872 printer->Print(
873 "public static $is_initialized = false;\n\n"
874 "public static function initOnce() {\n");
875 Indent(printer);
876
877 if (options.aggregate_metadata) {
878 GenerateAddFilesToPool(file, options, printer);
879 } else {
880 printer->Print(
881 "$pool = \\Google\\Protobuf\\Internal\\"
882 "DescriptorPool::getGeneratedPool();\n\n"
883 "if (static::$is_initialized == true) {\n"
884 " return;\n"
885 "}\n");
886
887 if (options.is_descriptor) {
888 for (int i = 0; i < file->message_type_count(); i++) {
889 GenerateMessageToPool("", file->message_type(i), printer);
890 }
891 for (int i = 0; i < file->enum_type_count(); i++) {
892 GenerateEnumToPool(file->enum_type(i), printer);
893 }
894
895 printer->Print("$pool->finish();\n");
896 } else {
897 for (int i = 0; i < file->dependency_count(); i++) {
898 absl::string_view name = file->dependency(i)->name();
899 // Currently, descriptor.proto is not ready for external usage. Skip to
900 // import it for now, so that its dependencies can still work as long as
901 // they don't use protos defined in descriptor.proto.
902 if (name == kDescriptorFile) {
903 continue;
904 }
905 std::string dependency_filename =
906 GeneratedMetadataFileName(file->dependency(i), options);
907 printer->Print("\\^name^::initOnce();\n", "name",
908 FilenameToClassname(dependency_filename));
909 }
910
911 // Add messages and enums to descriptor pool.
912 FileDescriptorSet files;
913 FileDescriptorProto* file_proto = files.add_file();
914 *file_proto = StripSourceRetentionOptions(*file);
915
916 // Filter out descriptor.proto as it cannot be depended on for now.
917 RepeatedPtrField<std::string>* dependency =
918 file_proto->mutable_dependency();
919 for (RepeatedPtrField<std::string>::iterator it = dependency->begin();
920 it != dependency->end(); ++it) {
921 if (*it != kDescriptorFile) {
922 dependency->erase(it);
923 break;
924 }
925 }
926
927 // Filter out all extensions, since we do not support extension yet.
928 file_proto->clear_extension();
929 RepeatedPtrField<DescriptorProto>* message_type =
930 file_proto->mutable_message_type();
931 for (RepeatedPtrField<DescriptorProto>::iterator it =
932 message_type->begin();
933 it != message_type->end(); ++it) {
934 it->clear_extension();
935 }
936
937 std::string files_data;
938 files.SerializeToString(&files_data);
939
940 printer->Print("$pool->internalAddGeneratedFile(\n");
941 Indent(printer);
942 printer->Print("\"^data^\"\n", "data", BinaryToPhpString(files_data));
943 Outdent(printer);
944 printer->Print(", true);\n\n");
945 }
946 printer->Print("static::$is_initialized = true;\n");
947 }
948
949 Outdent(printer);
950 printer->Print("}\n");
951 }
952
AnalyzeDependencyForFile(const FileDescriptor * file,absl::flat_hash_set<const FileDescriptor * > * nodes_without_dependency,absl::flat_hash_map<const FileDescriptor *,absl::flat_hash_set<const FileDescriptor * >> * deps,absl::flat_hash_map<const FileDescriptor *,int> * dependency_count)953 static void AnalyzeDependencyForFile(
954 const FileDescriptor* file,
955 absl::flat_hash_set<const FileDescriptor*>* nodes_without_dependency,
956 absl::flat_hash_map<const FileDescriptor*,
957 absl::flat_hash_set<const FileDescriptor*>>* deps,
958 absl::flat_hash_map<const FileDescriptor*, int>* dependency_count) {
959 int count = file->dependency_count();
960 for (int i = 0; i < file->dependency_count(); i++) {
961 const FileDescriptor* dependency = file->dependency(i);
962 if (dependency->name() == kDescriptorFile) {
963 count--;
964 break;
965 }
966 }
967
968 if (count == 0) {
969 nodes_without_dependency->insert(file);
970 } else {
971 (*dependency_count)[file] = count;
972 for (int i = 0; i < file->dependency_count(); i++) {
973 const FileDescriptor* dependency = file->dependency(i);
974 if (dependency->name() == kDescriptorFile) {
975 continue;
976 }
977 if (deps->find(dependency) == deps->end()) {
978 (*deps)[dependency] = {};
979 }
980 (*deps)[dependency].insert(file);
981 AnalyzeDependencyForFile(dependency, nodes_without_dependency, deps,
982 dependency_count);
983 }
984 }
985 }
986
NeedsUnwrapping(const FileDescriptor * file,const Options & options)987 static bool NeedsUnwrapping(const FileDescriptor* file,
988 const Options& options) {
989 bool has_aggregate_metadata_prefix = false;
990 if (options.aggregate_metadata_prefixes.empty()) {
991 has_aggregate_metadata_prefix = true;
992 } else {
993 for (const auto& prefix : options.aggregate_metadata_prefixes) {
994 if (absl::StartsWith(file->package(), prefix)) {
995 has_aggregate_metadata_prefix = true;
996 break;
997 }
998 }
999 }
1000
1001 return has_aggregate_metadata_prefix;
1002 }
1003
GenerateAddFilesToPool(const FileDescriptor * file,const Options & options,io::Printer * printer)1004 void GenerateAddFilesToPool(const FileDescriptor* file, const Options& options,
1005 io::Printer* printer) {
1006 printer->Print(
1007 "$pool = \\Google\\Protobuf\\Internal\\"
1008 "DescriptorPool::getGeneratedPool();\n"
1009 "if (static::$is_initialized == true) {\n"
1010 " return;\n"
1011 "}\n");
1012
1013 // Sort files according to dependency
1014 absl::flat_hash_map<const FileDescriptor*,
1015 absl::flat_hash_set<const FileDescriptor*>>
1016 deps;
1017 absl::flat_hash_map<const FileDescriptor*, int> dependency_count;
1018 absl::flat_hash_set<const FileDescriptor*> nodes_without_dependency;
1019 FileDescriptorSet sorted_file_set;
1020
1021 AnalyzeDependencyForFile(file, &nodes_without_dependency, &deps,
1022 &dependency_count);
1023
1024 while (!nodes_without_dependency.empty()) {
1025 auto file_node = *nodes_without_dependency.begin();
1026 nodes_without_dependency.erase(file_node);
1027 for (auto dependent : deps[file_node]) {
1028 if (dependency_count[dependent] == 1) {
1029 dependency_count.erase(dependent);
1030 nodes_without_dependency.insert(dependent);
1031 } else {
1032 dependency_count[dependent] -= 1;
1033 }
1034 }
1035
1036 bool needs_aggregate = NeedsUnwrapping(file_node, options);
1037
1038 if (needs_aggregate) {
1039 auto file_proto = sorted_file_set.add_file();
1040 *file_proto = StripSourceRetentionOptions(*file_node);
1041
1042 // Filter out descriptor.proto as it cannot be depended on for now.
1043 RepeatedPtrField<std::string>* dependency =
1044 file_proto->mutable_dependency();
1045 for (RepeatedPtrField<std::string>::iterator it = dependency->begin();
1046 it != dependency->end(); ++it) {
1047 if (*it != kDescriptorFile) {
1048 dependency->erase(it);
1049 break;
1050 }
1051 }
1052
1053 // Filter out all extensions, since we do not support extension yet.
1054 file_proto->clear_extension();
1055 RepeatedPtrField<DescriptorProto>* message_type =
1056 file_proto->mutable_message_type();
1057 for (RepeatedPtrField<DescriptorProto>::iterator it =
1058 message_type->begin();
1059 it != message_type->end(); ++it) {
1060 it->clear_extension();
1061 }
1062 } else {
1063 std::string dependency_filename =
1064 GeneratedMetadataFileName(file_node, false);
1065 printer->Print("\\^name^::initOnce();\n", "name",
1066 FilenameToClassname(dependency_filename));
1067 }
1068 }
1069
1070 std::string files_data;
1071 sorted_file_set.SerializeToString(&files_data);
1072
1073 printer->Print("$pool->internalAddGeneratedFile(\n");
1074 Indent(printer);
1075 printer->Print("\"^data^\"\n", "data", BinaryToPhpString(files_data));
1076 Outdent(printer);
1077 printer->Print(", true);\n");
1078
1079 printer->Print("static::$is_initialized = true;\n");
1080 }
1081
GenerateUseDeclaration(const Options & options,io::Printer * printer)1082 void GenerateUseDeclaration(const Options& options, io::Printer* printer) {
1083 if (!options.is_descriptor) {
1084 printer->Print(
1085 "use Google\\Protobuf\\Internal\\GPBType;\n"
1086 "use Google\\Protobuf\\Internal\\RepeatedField;\n"
1087 "use Google\\Protobuf\\Internal\\GPBUtil;\n\n");
1088 } else {
1089 printer->Print(
1090 "use Google\\Protobuf\\Internal\\GPBType;\n"
1091 "use Google\\Protobuf\\Internal\\GPBWire;\n"
1092 "use Google\\Protobuf\\Internal\\RepeatedField;\n"
1093 "use Google\\Protobuf\\Internal\\InputStream;\n"
1094 "use Google\\Protobuf\\Internal\\GPBUtil;\n\n");
1095 }
1096 }
1097
GenerateHead(const FileDescriptor * file,io::Printer * printer)1098 void GenerateHead(const FileDescriptor* file, io::Printer* printer) {
1099 printer->Print(
1100 "<?php\n"
1101 "# Generated by the protocol buffer compiler. DO NOT EDIT!\n"
1102 "# NO CHECKED-IN"
1103 // Intentional line breaker.
1104 " PROTOBUF GENCODE\n"
1105 "# source: ^filename^\n"
1106 "\n",
1107 "filename", file->name());
1108 }
1109
FilenameToClassname(absl::string_view filename)1110 std::string FilenameToClassname(absl::string_view filename) {
1111 size_t lastindex = filename.find_last_of('.');
1112 std::string result(filename.substr(0, lastindex));
1113 for (size_t i = 0; i < result.size(); i++) {
1114 if (result[i] == '/') {
1115 result[i] = '\\';
1116 }
1117 }
1118 return result;
1119 }
1120
GenerateMetadataFile(const FileDescriptor * file,const Options & options,GeneratorContext * generator_context)1121 void GenerateMetadataFile(const FileDescriptor* file, const Options& options,
1122 GeneratorContext* generator_context) {
1123 std::string filename = GeneratedMetadataFileName(file, options);
1124 std::unique_ptr<io::ZeroCopyOutputStream> output(
1125 generator_context->Open(filename));
1126 io::Printer printer(output.get(), '^');
1127
1128 GenerateHead(file, &printer);
1129
1130 std::string fullname = FilenameToClassname(filename);
1131 int lastindex = fullname.find_last_of('\\');
1132
1133 if (lastindex != std::string::npos) {
1134 printer.Print("namespace ^name^;\n\n", "name",
1135 fullname.substr(0, lastindex));
1136
1137 printer.Print(
1138 "class ^name^\n"
1139 "{\n",
1140 "name", fullname.substr(lastindex + 1));
1141 } else {
1142 printer.Print(
1143 "class ^name^\n"
1144 "{\n",
1145 "name", fullname);
1146 }
1147 Indent(&printer);
1148
1149 GenerateAddFileToPool(file, options, &printer);
1150
1151 Outdent(&printer);
1152 printer.Print("}\n\n");
1153 }
1154
GenerateEnumFile(const FileDescriptor * file,const EnumDescriptor * en,const Options & options,GeneratorContext * generator_context,std::string * error)1155 bool GenerateEnumFile(const FileDescriptor* file, const EnumDescriptor* en,
1156 const Options& options,
1157 GeneratorContext* generator_context, std::string* error) {
1158 if (en->is_closed()) {
1159 *error = absl::StrCat("Can't generate PHP code for closed enum ",
1160 en->full_name(),
1161 ". Please use either proto3 or editions without "
1162 "`enum_type = CLOSED`.\n");
1163 return false;
1164 }
1165
1166 std::string filename = GeneratedClassFileName(en, options);
1167 std::unique_ptr<io::ZeroCopyOutputStream> output(
1168 generator_context->Open(filename));
1169 io::Printer printer(output.get(), '^');
1170
1171 GenerateHead(file, &printer);
1172
1173 std::string fullname = FilenameToClassname(filename);
1174 int lastindex = fullname.find_last_of('\\');
1175
1176 if (lastindex != std::string::npos) {
1177 printer.Print("namespace ^name^;\n\n", "name",
1178 fullname.substr(0, lastindex));
1179
1180 // We only need this 'use' statement if the enum has a namespace.
1181 // Otherwise, we get a warning that the use statement has no effect.
1182 printer.Print("use UnexpectedValueException;\n\n");
1183 }
1184
1185 GenerateEnumDocComment(&printer, en, options);
1186
1187 if (lastindex != std::string::npos) {
1188 fullname = fullname.substr(lastindex + 1);
1189 }
1190
1191 printer.Print(
1192 "class ^name^\n"
1193 "{\n",
1194 "name", fullname);
1195 Indent(&printer);
1196
1197 bool hasReserved = false;
1198 for (int i = 0; i < en->value_count(); i++) {
1199 const EnumValueDescriptor* value = en->value(i);
1200 GenerateEnumValueDocComment(&printer, value);
1201
1202 std::string prefix = ConstantNamePrefix(value->name());
1203 if (!prefix.empty()) {
1204 hasReserved = true;
1205 }
1206
1207 printer.Print("const ^name^ = ^number^;\n", "name",
1208 absl::StrCat(prefix, value->name()), "number",
1209 IntToString(value->number()));
1210 }
1211
1212 printer.Print("\nprivate static $valueToName = [\n");
1213 Indent(&printer);
1214 for (int i = 0; i < en->value_count(); i++) {
1215 const EnumValueDescriptor* value = en->value(i);
1216 printer.Print(
1217 "self::^constant^ => '^name^',\n", "constant",
1218 absl::StrCat(ConstantNamePrefix(value->name()), value->name()), "name",
1219 value->name());
1220 }
1221 Outdent(&printer);
1222 printer.Print("];\n");
1223
1224 printer.Print(
1225 "\npublic static function name($value)\n"
1226 "{\n");
1227 Indent(&printer);
1228 printer.Print("if (!isset(self::$valueToName[$value])) {\n");
1229 Indent(&printer);
1230 printer.Print("throw new UnexpectedValueException(sprintf(\n");
1231 Indent(&printer);
1232 Indent(&printer);
1233 printer.Print(
1234 "'Enum %s has no name defined for value %s', __CLASS__, $value));\n");
1235 Outdent(&printer);
1236 Outdent(&printer);
1237 Outdent(&printer);
1238 printer.Print(
1239 "}\n"
1240 "return self::$valueToName[$value];\n");
1241 Outdent(&printer);
1242 printer.Print("}\n\n");
1243
1244 printer.Print(
1245 "\npublic static function value($name)\n"
1246 "{\n");
1247 Indent(&printer);
1248 printer.Print(
1249 "$const = __CLASS__ . '::' . strtoupper($name);\n"
1250 "if (!defined($const)) {\n");
1251 Indent(&printer);
1252 if (hasReserved) {
1253 printer.Print(
1254 "$pbconst = __CLASS__. '::PB' . strtoupper($name);\n"
1255 "if (!defined($pbconst)) {\n");
1256 Indent(&printer);
1257 }
1258 printer.Print("throw new UnexpectedValueException(sprintf(\n");
1259 Indent(&printer);
1260 Indent(&printer);
1261 printer.Print(
1262 "'Enum %s has no value defined for name %s', __CLASS__, $name));\n");
1263 Outdent(&printer);
1264 Outdent(&printer);
1265 if (hasReserved) {
1266 Outdent(&printer);
1267 printer.Print(
1268 "}\n"
1269 "return constant($pbconst);\n");
1270 }
1271 Outdent(&printer);
1272 printer.Print(
1273 "}\n"
1274 "return constant($const);\n");
1275 Outdent(&printer);
1276 printer.Print("}\n");
1277
1278 Outdent(&printer);
1279 printer.Print("}\n\n");
1280
1281 return true;
1282 }
1283
GenerateMessageFile(const FileDescriptor * file,const Descriptor * message,const Options & options,GeneratorContext * generator_context,std::string * error)1284 bool GenerateMessageFile(const FileDescriptor* file, const Descriptor* message,
1285 const Options& options,
1286 GeneratorContext* generator_context,
1287 std::string* error) {
1288 // Don't generate MapEntry messages -- we use the PHP extension's native
1289 // support for map fields instead.
1290 if (message->options().map_entry()) {
1291 return true;
1292 }
1293
1294 std::string filename = GeneratedClassFileName(message, options);
1295 std::unique_ptr<io::ZeroCopyOutputStream> output(
1296 generator_context->Open(filename));
1297 io::Printer printer(output.get(), '^');
1298
1299 GenerateHead(file, &printer);
1300
1301 std::string fullname = FilenameToClassname(filename);
1302 int lastindex = fullname.find_last_of('\\');
1303
1304 if (lastindex != std::string::npos) {
1305 printer.Print("namespace ^name^;\n\n", "name",
1306 fullname.substr(0, lastindex));
1307 }
1308
1309 GenerateUseDeclaration(options, &printer);
1310
1311 GenerateMessageDocComment(&printer, message, options);
1312 if (lastindex != std::string::npos) {
1313 fullname = fullname.substr(lastindex + 1);
1314 }
1315
1316 std::string base;
1317
1318 switch (message->well_known_type()) {
1319 case Descriptor::WELLKNOWNTYPE_ANY:
1320 base = "\\Google\\Protobuf\\Internal\\AnyBase";
1321 break;
1322 case Descriptor::WELLKNOWNTYPE_TIMESTAMP:
1323 base = "\\Google\\Protobuf\\Internal\\TimestampBase";
1324 break;
1325 default:
1326 base = "\\Google\\Protobuf\\Internal\\Message";
1327 break;
1328 }
1329
1330 printer.Print(
1331 "class ^name^ extends ^base^\n"
1332 "{\n",
1333 "base", base, "name", fullname);
1334 Indent(&printer);
1335
1336 // Field and oneof definitions.
1337 for (int i = 0; i < message->field_count(); i++) {
1338 const FieldDescriptor* field = message->field(i);
1339 if (!GenerateField(field, &printer, options, error)) {
1340 return false;
1341 }
1342 }
1343 for (int i = 0; i < message->real_oneof_decl_count(); i++) {
1344 const OneofDescriptor* oneof = message->oneof_decl(i);
1345 GenerateOneofField(oneof, &printer);
1346 }
1347 printer.Print("\n");
1348
1349 GenerateMessageConstructorDocComment(&printer, message, options);
1350 printer.Print("public function __construct($data = NULL) {\n");
1351 Indent(&printer);
1352
1353 std::string metadata_filename = GeneratedMetadataFileName(file, options);
1354 std::string metadata_fullname = FilenameToClassname(metadata_filename);
1355 printer.Print("\\^fullname^::initOnce();\n", "fullname", metadata_fullname);
1356
1357 printer.Print("parent::__construct($data);\n");
1358
1359 Outdent(&printer);
1360 printer.Print("}\n\n");
1361
1362 // Field and oneof accessors.
1363 for (int i = 0; i < message->field_count(); i++) {
1364 const FieldDescriptor* field = message->field(i);
1365 GenerateFieldAccessor(field, options, &printer);
1366 }
1367 for (int i = 0; i < message->real_oneof_decl_count(); i++) {
1368 const OneofDescriptor* oneof = message->oneof_decl(i);
1369 printer.Print(
1370 "/**\n"
1371 " * @return string\n"
1372 " */\n"
1373 "public function get^camel_name^()\n"
1374 "{\n"
1375 " return $this->whichOneof(\"^name^\");\n"
1376 "}\n\n",
1377 "camel_name", UnderscoresToCamelCase(oneof->name(), true), "name",
1378 oneof->name());
1379 }
1380
1381 Outdent(&printer);
1382 printer.Print("}\n\n");
1383
1384 // Nested messages and enums.
1385 for (int i = 0; i < message->nested_type_count(); i++) {
1386 if (!GenerateMessageFile(file, message->nested_type(i), options,
1387 generator_context, error)) {
1388 return false;
1389 }
1390 }
1391 for (int i = 0; i < message->enum_type_count(); i++) {
1392 if (!GenerateEnumFile(file, message->enum_type(i), options,
1393 generator_context, error)) {
1394 return false;
1395 }
1396 }
1397 return true;
1398 }
1399
GenerateFile(const FileDescriptor * file,const Options & options,GeneratorContext * generator_context,std::string * error)1400 bool GenerateFile(const FileDescriptor* file, const Options& options,
1401 GeneratorContext* generator_context, std::string* error) {
1402 GenerateMetadataFile(file, options, generator_context);
1403
1404 for (int i = 0; i < file->message_type_count(); i++) {
1405 if (!GenerateMessageFile(file, file->message_type(i), options,
1406 generator_context, error)) {
1407 return false;
1408 }
1409 }
1410 for (int i = 0; i < file->enum_type_count(); i++) {
1411 if (!GenerateEnumFile(file, file->enum_type(i), options, generator_context,
1412 error)) {
1413 return false;
1414 }
1415 }
1416
1417 return true;
1418 }
1419
EscapePhpdoc(absl::string_view input)1420 static std::string EscapePhpdoc(absl::string_view input) {
1421 std::string result;
1422 result.reserve(input.size() * 2);
1423
1424 char prev = '\0';
1425
1426 for (std::string::size_type i = 0; i < input.size(); i++) {
1427 char c = input[i];
1428 switch (c) {
1429 // NOTE: "/*" is allowed, do not escape it
1430 case '/':
1431 // Escape "*/" with "{@*}".
1432 if (prev == '*') {
1433 result.pop_back();
1434 result.append("{@*}");
1435 } else {
1436 result.push_back(c);
1437 }
1438 break;
1439 case '@':
1440 // '@' starts phpdoc tags. Play it safe and escape it.
1441 result.append("\\");
1442 result.push_back(c);
1443 break;
1444 default:
1445 result.push_back(c);
1446 break;
1447 }
1448 prev = c;
1449 }
1450
1451 return result;
1452 }
1453
GenerateDocCommentBodyForLocation(io::Printer * printer,const SourceLocation & location,bool trailingNewline,int indentCount)1454 static void GenerateDocCommentBodyForLocation(io::Printer* printer,
1455 const SourceLocation& location,
1456 bool trailingNewline,
1457 int indentCount) {
1458 std::string comments = location.leading_comments.empty()
1459 ? location.trailing_comments
1460 : location.leading_comments;
1461 if (!comments.empty()) {
1462 // TODO: Ideally we should parse the comment text as Markdown and
1463 // write it back as HTML, but this requires a Markdown parser. For now
1464 // we just use the proto comments unchanged.
1465
1466 // If the comment itself contains block comment start or end markers,
1467 // HTML-escape them so that they don't accidentally close the doc comment.
1468 comments = EscapePhpdoc(comments);
1469
1470 std::vector<absl::string_view> lines =
1471 absl::StrSplit(comments, '\n', absl::SkipEmpty());
1472 while (!lines.empty() && lines.back().empty()) {
1473 lines.pop_back();
1474 }
1475
1476 for (int i = 0; i < lines.size(); i++) {
1477 // Most lines should start with a space. Watch out for lines that start
1478 // with a /, since putting that right after the leading asterisk will
1479 // close the comment.
1480 if (indentCount == 0 && !lines[i].empty() && lines[i][0] == '/') {
1481 printer->Print(" * ^line^\n", "line", lines[i]);
1482 } else {
1483 std::string indent = std::string(indentCount, ' ');
1484 printer->Print(" *^ind^^line^\n", "ind", indent, "line", lines[i]);
1485 }
1486 }
1487 if (trailingNewline) {
1488 printer->Print(" *\n");
1489 }
1490 }
1491 }
1492
1493 template <typename DescriptorType>
GenerateDocCommentBody(io::Printer * printer,const DescriptorType * descriptor)1494 static void GenerateDocCommentBody(io::Printer* printer,
1495 const DescriptorType* descriptor) {
1496 SourceLocation location;
1497 if (descriptor->GetSourceLocation(&location)) {
1498 GenerateDocCommentBodyForLocation(printer, location, true, 0);
1499 }
1500 }
1501
FirstLineOf(absl::string_view value)1502 static std::string FirstLineOf(absl::string_view value) {
1503 std::string result(value);
1504
1505 std::string::size_type pos = result.find_first_of('\n');
1506 if (pos != std::string::npos) {
1507 result.erase(pos);
1508 }
1509
1510 return result;
1511 }
1512
GenerateMessageDocComment(io::Printer * printer,const Descriptor * message,const Options & options)1513 void GenerateMessageDocComment(io::Printer* printer, const Descriptor* message,
1514 const Options& options) {
1515 printer->Print("/**\n");
1516 GenerateDocCommentBody(printer, message);
1517 if (message->options().deprecated()) {
1518 printer->Print(" * @deprecated\n");
1519 }
1520
1521 printer->Print(
1522 " * Generated from protobuf message <code>^messagename^</code>\n"
1523 " */\n",
1524 "fullname", EscapePhpdoc(FullClassName(message, options)), "messagename",
1525 EscapePhpdoc(message->full_name()));
1526 }
1527
GenerateMessageConstructorDocComment(io::Printer * printer,const Descriptor * message,const Options & options)1528 void GenerateMessageConstructorDocComment(io::Printer* printer,
1529 const Descriptor* message,
1530 const Options& options) {
1531 // In theory we should have slightly different comments for setters, getters,
1532 // etc., but in practice everyone already knows the difference between these
1533 // so it's redundant information.
1534
1535 // We start the comment with the main body based on the comments from the
1536 // .proto file (if present). We then end with the field declaration, e.g.:
1537 // optional string foo = 5;
1538 // If the field is a group, the debug string might end with {.
1539 printer->Print("/**\n");
1540 printer->Print(" * Constructor.\n");
1541 printer->Print(" *\n");
1542 printer->Print(" * @param array $data {\n");
1543 printer->Print(" * Optional. Data for populating the Message object.\n");
1544 printer->Print(" *\n");
1545 for (int i = 0; i < message->field_count(); i++) {
1546 const FieldDescriptor* field = message->field(i);
1547 printer->Print(" * @type ^php_type^ $^var^\n", "php_type",
1548 PhpSetterTypeName(field, options), "var", field->name());
1549 SourceLocation location;
1550 if (field->GetSourceLocation(&location)) {
1551 GenerateDocCommentBodyForLocation(printer, location, false, 10);
1552 }
1553 }
1554 printer->Print(" * }\n");
1555 printer->Print(" */\n");
1556 }
1557
GenerateFieldDocComment(io::Printer * printer,const FieldDescriptor * field,const Options & options,int function_type)1558 void GenerateFieldDocComment(io::Printer* printer, const FieldDescriptor* field,
1559 const Options& options, int function_type) {
1560 // In theory we should have slightly different comments for setters, getters,
1561 // etc., but in practice everyone already knows the difference between these
1562 // so it's redundant information.
1563
1564 // We start the comment with the main body based on the comments from the
1565 // .proto file (if present). We then end with the field declaration, e.g.:
1566 // optional string foo = 5;
1567 // If the field is a group, the debug string might end with {.
1568 printer->Print("/**\n");
1569 GenerateDocCommentBody(printer, field);
1570 printer->Print(" * Generated from protobuf field <code>^def^</code>\n", "def",
1571 EscapePhpdoc(FirstLineOf(field->DebugString())));
1572 if (function_type == kFieldSetter) {
1573 printer->Print(" * @param ^php_type^ $var\n", "php_type",
1574 PhpSetterTypeName(field, options));
1575 printer->Print(" * @return $this\n");
1576 } else if (function_type == kFieldGetter) {
1577 bool can_return_null =
1578 field->has_presence() &&
1579 field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE;
1580 printer->Print(" * @return ^php_type^^maybe_null^\n", "php_type",
1581 PhpGetterTypeName(field, options), "maybe_null",
1582 can_return_null ? "|null" : "");
1583 }
1584 if (field->options().deprecated()) {
1585 printer->Print(" * @deprecated\n");
1586 }
1587 printer->Print(" */\n");
1588 }
1589
GenerateWrapperFieldGetterDocComment(io::Printer * printer,const FieldDescriptor * field)1590 void GenerateWrapperFieldGetterDocComment(io::Printer* printer,
1591 const FieldDescriptor* field) {
1592 // Generate a doc comment for the special getXXXValue methods that are
1593 // generated for wrapper types.
1594 const FieldDescriptor* primitiveField =
1595 field->message_type()->FindFieldByName("value");
1596 printer->Print("/**\n");
1597 printer->Print(
1598 " * Returns the unboxed value from <code>get^camel_name^()</code>\n\n",
1599 "camel_name", UnderscoresToCamelCase(field->name(), true));
1600 GenerateDocCommentBody(printer, field);
1601 printer->Print(" * Generated from protobuf field <code>^def^</code>\n", "def",
1602 EscapePhpdoc(FirstLineOf(field->DebugString())));
1603 printer->Print(" * @return ^php_type^|null\n", "php_type",
1604 PhpGetterTypeName(primitiveField, false));
1605 printer->Print(" */\n");
1606 }
1607
GenerateWrapperFieldSetterDocComment(io::Printer * printer,const FieldDescriptor * field)1608 void GenerateWrapperFieldSetterDocComment(io::Printer* printer,
1609 const FieldDescriptor* field) {
1610 // Generate a doc comment for the special setXXXValue methods that are
1611 // generated for wrapper types.
1612 const FieldDescriptor* primitiveField =
1613 field->message_type()->FindFieldByName("value");
1614 printer->Print("/**\n");
1615 printer->Print(
1616 " * Sets the field by wrapping a primitive type in a ^message_name^ "
1617 "object.\n\n",
1618 "message_name", FullClassName(field->message_type(), false));
1619 GenerateDocCommentBody(printer, field);
1620 printer->Print(" * Generated from protobuf field <code>^def^</code>\n", "def",
1621 EscapePhpdoc(FirstLineOf(field->DebugString())));
1622 printer->Print(" * @param ^php_type^|null $var\n", "php_type",
1623 PhpSetterTypeName(primitiveField, false));
1624 printer->Print(" * @return $this\n");
1625 printer->Print(" */\n");
1626 }
1627
GenerateEnumDocComment(io::Printer * printer,const EnumDescriptor * enum_,const Options & options)1628 void GenerateEnumDocComment(io::Printer* printer, const EnumDescriptor* enum_,
1629 const Options& options) {
1630 printer->Print("/**\n");
1631 if (enum_->options().deprecated()) {
1632 printer->Print(" * @deprecated\n");
1633 }
1634 GenerateDocCommentBody(printer, enum_);
1635 printer->Print(
1636 " * Protobuf type <code>^fullname^</code>\n"
1637 " */\n",
1638 "fullname", EscapePhpdoc(enum_->full_name()));
1639 }
1640
GenerateEnumValueDocComment(io::Printer * printer,const EnumValueDescriptor * value)1641 void GenerateEnumValueDocComment(io::Printer* printer,
1642 const EnumValueDescriptor* value) {
1643 printer->Print("/**\n");
1644 GenerateDocCommentBody(printer, value);
1645 printer->Print(
1646 " * Generated from protobuf enum <code>^def^</code>\n"
1647 " */\n",
1648 "def", EscapePhpdoc(FirstLineOf(value->DebugString())));
1649 }
1650
FilenameCName(const FileDescriptor * file)1651 std::string FilenameCName(const FileDescriptor* file) {
1652 return absl::StrReplaceAll(file->name(), {{".", "_"}, {"/", "_"}});
1653 }
1654
GenerateCEnum(const EnumDescriptor * desc,io::Printer * printer)1655 void GenerateCEnum(const EnumDescriptor* desc, io::Printer* printer) {
1656 std::string c_name = absl::StrReplaceAll(desc->full_name(), {{".", "_"}});
1657 std::string php_name =
1658 absl::StrReplaceAll(FullClassName(desc, Options()), {{"\\", "\\\\"}});
1659 printer->Print(
1660 "/* $c_name$ */\n"
1661 "\n"
1662 "zend_class_entry* $c_name$_ce;\n"
1663 "\n"
1664 "PHP_METHOD($c_name$, name) {\n"
1665 " $file_c_name$_AddDescriptor();\n"
1666 " const upb_DefPool *symtab = DescriptorPool_GetSymbolTable();\n"
1667 " const upb_EnumDef *e = upb_DefPool_FindEnumByName(symtab, "
1668 "\"$name$\");\n"
1669 " zend_long value;\n"
1670 " if (zend_parse_parameters(ZEND_NUM_ARGS(), \"l\", &value) ==\n"
1671 " FAILURE) {\n"
1672 " return;\n"
1673 " }\n"
1674 " const upb_EnumValueDef* ev =\n"
1675 " upb_EnumDef_FindValueByNumber(e, value);\n"
1676 " if (!ev) {\n"
1677 " zend_throw_exception_ex(NULL, 0,\n"
1678 " \"$php_name$ has no name \"\n"
1679 " \"defined for value \" ZEND_LONG_FMT "
1680 "\".\",\n"
1681 " value);\n"
1682 " return;\n"
1683 " }\n"
1684 " RETURN_STRING(upb_EnumValueDef_Name(ev));\n"
1685 "}\n"
1686 "\n"
1687 "PHP_METHOD($c_name$, value) {\n"
1688 " $file_c_name$_AddDescriptor();\n"
1689 " const upb_DefPool *symtab = DescriptorPool_GetSymbolTable();\n"
1690 " const upb_EnumDef *e = upb_DefPool_FindEnumByName(symtab, "
1691 "\"$name$\");\n"
1692 " char *name = NULL;\n"
1693 " size_t name_len;\n"
1694 " if (zend_parse_parameters(ZEND_NUM_ARGS(), \"s\", &name,\n"
1695 " &name_len) == FAILURE) {\n"
1696 " return;\n"
1697 " }\n"
1698 " const upb_EnumValueDef* ev = upb_EnumDef_FindValueByNameWithSize(\n"
1699 " e, name, name_len);\n"
1700 " if (!ev) {\n"
1701 " zend_throw_exception_ex(NULL, 0,\n"
1702 " \"$php_name$ has no value \"\n"
1703 " \"defined for name %s.\",\n"
1704 " name);\n"
1705 " return;\n"
1706 " }\n"
1707 " RETURN_LONG(upb_EnumValueDef_Number(ev));\n"
1708 "}\n"
1709 "\n"
1710 "static zend_function_entry $c_name$_phpmethods[] = {\n"
1711 " PHP_ME($c_name$, name, arginfo_lookup, ZEND_ACC_PUBLIC | "
1712 "ZEND_ACC_STATIC)\n"
1713 " PHP_ME($c_name$, value, arginfo_lookup, ZEND_ACC_PUBLIC | "
1714 "ZEND_ACC_STATIC)\n"
1715 " ZEND_FE_END\n"
1716 "};\n"
1717 "\n"
1718 "static void $c_name$_ModuleInit() {\n"
1719 " zend_class_entry tmp_ce;\n"
1720 "\n"
1721 " INIT_CLASS_ENTRY(tmp_ce, \"$php_name$\",\n"
1722 " $c_name$_phpmethods);\n"
1723 "\n"
1724 " $c_name$_ce = zend_register_internal_class(&tmp_ce);\n",
1725 "name", desc->full_name(), "file_c_name", FilenameCName(desc->file()),
1726 "c_name", c_name, "php_name", php_name);
1727
1728 for (int i = 0; i < desc->value_count(); i++) {
1729 const EnumValueDescriptor* value = desc->value(i);
1730 printer->Print(
1731 " zend_declare_class_constant_long($c_name$_ce, \"$name$\",\n"
1732 " strlen(\"$name$\"), $num$);\n",
1733 "c_name", c_name, "name", value->name(), "num",
1734 std::to_string(value->number()));
1735 }
1736
1737 printer->Print(
1738 "}\n"
1739 "\n");
1740 }
1741
GenerateCMessage(const Descriptor * message,io::Printer * printer)1742 void GenerateCMessage(const Descriptor* message, io::Printer* printer) {
1743 std::string c_name = absl::StrReplaceAll(message->full_name(), {{".", "_"}});
1744 std::string php_name =
1745 absl::StrReplaceAll(FullClassName(message, Options()), {{"\\", "\\\\"}});
1746 printer->Print(
1747 "/* $c_name$ */\n"
1748 "\n"
1749 "zend_class_entry* $c_name$_ce;\n"
1750 "\n"
1751 "static PHP_METHOD($c_name$, __construct) {\n"
1752 " $file_c_name$_AddDescriptor();\n"
1753 " zim_Message___construct(INTERNAL_FUNCTION_PARAM_PASSTHRU);\n"
1754 "}\n"
1755 "\n",
1756 "file_c_name", FilenameCName(message->file()), "c_name", c_name);
1757
1758 for (int i = 0; i < message->field_count(); i++) {
1759 auto field = message->field(i);
1760 printer->Print(
1761 "static PHP_METHOD($c_name$, get$camel_name$) {\n"
1762 " Message* intern = (Message*)Z_OBJ_P(getThis());\n"
1763 " const upb_FieldDef *f = upb_MessageDef_FindFieldByName(\n"
1764 " intern->desc->msgdef, \"$name$\");\n"
1765 " zval ret;\n"
1766 " Message_get(intern, f, &ret);\n"
1767 " RETURN_COPY_VALUE(&ret);\n"
1768 "}\n"
1769 "\n"
1770 "static PHP_METHOD($c_name$, set$camel_name$) {\n"
1771 " Message* intern = (Message*)Z_OBJ_P(getThis());\n"
1772 " const upb_FieldDef *f = upb_MessageDef_FindFieldByName(\n"
1773 " intern->desc->msgdef, \"$name$\");\n"
1774 " zval *val;\n"
1775 " if (zend_parse_parameters(ZEND_NUM_ARGS(), \"z\", &val)\n"
1776 " == FAILURE) {\n"
1777 " return;\n"
1778 " }\n"
1779 " Message_set(intern, f, val);\n"
1780 " RETURN_COPY(getThis());\n"
1781 "}\n"
1782 "\n",
1783 "c_name", c_name, "name", field->name(), "camel_name",
1784 UnderscoresToCamelCase(field->name(), true));
1785 }
1786
1787 for (int i = 0; i < message->real_oneof_decl_count(); i++) {
1788 auto oneof = message->oneof_decl(i);
1789 printer->Print(
1790 "static PHP_METHOD($c_name$, get$camel_name$) {\n"
1791 " Message* intern = (Message*)Z_OBJ_P(getThis());\n"
1792 " const upb_OneofDef *oneof = upb_MessageDef_FindOneofByName(\n"
1793 " intern->desc->msgdef, \"$name$\");\n"
1794 " const upb_FieldDef *field = \n"
1795 " upb_Message_WhichOneofByDef(intern->msg, oneof);\n"
1796 " RETURN_STRING(field ? upb_FieldDef_Name(field) : \"\");\n"
1797 "}\n",
1798 "c_name", c_name, "name", oneof->name(), "camel_name",
1799 UnderscoresToCamelCase(oneof->name(), true));
1800 }
1801
1802 switch (message->well_known_type()) {
1803 case Descriptor::WELLKNOWNTYPE_ANY:
1804 printer->Print(
1805 "ZEND_BEGIN_ARG_INFO_EX(arginfo_is, 0, 0, 1)\n"
1806 " ZEND_ARG_INFO(0, proto)\n"
1807 "ZEND_END_ARG_INFO()\n"
1808 "\n");
1809 break;
1810 case Descriptor::WELLKNOWNTYPE_TIMESTAMP:
1811 printer->Print(
1812 "ZEND_BEGIN_ARG_INFO_EX(arginfo_timestamp_fromdatetime, 0, 0, 1)\n"
1813 " ZEND_ARG_INFO(0, datetime)\n"
1814 "ZEND_END_ARG_INFO()\n"
1815 "\n");
1816 break;
1817 default:
1818 break;
1819 }
1820
1821 printer->Print(
1822 "static zend_function_entry $c_name$_phpmethods[] = {\n"
1823 " PHP_ME($c_name$, __construct, arginfo_construct, ZEND_ACC_PUBLIC)\n",
1824 "c_name", c_name);
1825
1826 for (int i = 0; i < message->field_count(); i++) {
1827 auto field = message->field(i);
1828 printer->Print(
1829 " PHP_ME($c_name$, get$camel_name$, arginfo_void, ZEND_ACC_PUBLIC)\n"
1830 " PHP_ME($c_name$, set$camel_name$, arginfo_setter, "
1831 "ZEND_ACC_PUBLIC)\n",
1832 "c_name", c_name, "camel_name",
1833 UnderscoresToCamelCase(field->name(), true));
1834 }
1835
1836 for (int i = 0; i < message->real_oneof_decl_count(); i++) {
1837 auto oneof = message->oneof_decl(i);
1838 printer->Print(
1839 " PHP_ME($c_name$, get$camel_name$, arginfo_void, ZEND_ACC_PUBLIC)\n",
1840 "c_name", c_name, "camel_name",
1841 UnderscoresToCamelCase(oneof->name(), true));
1842 }
1843
1844 // Extra hand-written functions added to the well-known types.
1845 switch (message->well_known_type()) {
1846 case Descriptor::WELLKNOWNTYPE_ANY:
1847 printer->Print(
1848 " PHP_ME($c_name$, is, arginfo_is, ZEND_ACC_PUBLIC)\n"
1849 " PHP_ME($c_name$, pack, arginfo_setter, ZEND_ACC_PUBLIC)\n"
1850 " PHP_ME($c_name$, unpack, arginfo_void, ZEND_ACC_PUBLIC)\n",
1851 "c_name", c_name);
1852 break;
1853 case Descriptor::WELLKNOWNTYPE_TIMESTAMP:
1854 printer->Print(
1855 " PHP_ME($c_name$, fromDateTime, arginfo_timestamp_fromdatetime, "
1856 "ZEND_ACC_PUBLIC)\n"
1857 " PHP_ME($c_name$, toDateTime, arginfo_void, ZEND_ACC_PUBLIC)\n",
1858 "c_name", c_name);
1859 break;
1860 default:
1861 break;
1862 }
1863
1864 printer->Print(
1865 " ZEND_FE_END\n"
1866 "};\n"
1867 "\n"
1868 "static void $c_name$_ModuleInit() {\n"
1869 " zend_class_entry tmp_ce;\n"
1870 "\n"
1871 " INIT_CLASS_ENTRY(tmp_ce, \"$php_name$\",\n"
1872 " $c_name$_phpmethods);\n"
1873 "\n"
1874 " $c_name$_ce = zend_register_internal_class(&tmp_ce);\n"
1875 " $c_name$_ce->ce_flags |= ZEND_ACC_FINAL;\n"
1876 " $c_name$_ce->create_object = Message_create;\n"
1877 " zend_do_inheritance($c_name$_ce, message_ce);\n"
1878 "}\n"
1879 "\n",
1880 "c_name", c_name, "php_name", php_name);
1881
1882 for (int i = 0; i < message->nested_type_count(); i++) {
1883 GenerateCMessage(message->nested_type(i), printer);
1884 }
1885 for (int i = 0; i < message->enum_type_count(); i++) {
1886 GenerateCEnum(message->enum_type(i), printer);
1887 }
1888 }
1889
GenerateEnumCInit(const EnumDescriptor * desc,io::Printer * printer)1890 void GenerateEnumCInit(const EnumDescriptor* desc, io::Printer* printer) {
1891 std::string c_name = absl::StrReplaceAll(desc->full_name(), {{".", "_"}});
1892
1893 printer->Print(" $c_name$_ModuleInit();\n", "c_name", c_name);
1894 }
1895
GenerateCInit(const Descriptor * message,io::Printer * printer)1896 void GenerateCInit(const Descriptor* message, io::Printer* printer) {
1897 std::string c_name = absl::StrReplaceAll(message->full_name(), {{".", "_"}});
1898
1899 printer->Print(" $c_name$_ModuleInit();\n", "c_name", c_name);
1900
1901 for (int i = 0; i < message->nested_type_count(); i++) {
1902 GenerateCInit(message->nested_type(i), printer);
1903 }
1904 for (int i = 0; i < message->enum_type_count(); i++) {
1905 GenerateEnumCInit(message->enum_type(i), printer);
1906 }
1907 }
1908
GenerateCWellKnownTypes(const std::vector<const FileDescriptor * > & files,GeneratorContext * context)1909 void GenerateCWellKnownTypes(const std::vector<const FileDescriptor*>& files,
1910 GeneratorContext* context) {
1911 std::unique_ptr<io::ZeroCopyOutputStream> output(
1912 context->Open("../ext/google/protobuf/wkt.inc"));
1913 io::Printer printer(output.get(), '$');
1914
1915 printer.Print(
1916 "// This file is generated from the .proto files for the well-known\n"
1917 "// types. Do not edit!\n\n");
1918
1919 printer.Print(
1920 "ZEND_BEGIN_ARG_INFO_EX(arginfo_lookup, 0, 0, 1)\n"
1921 " ZEND_ARG_INFO(0, key)\n"
1922 "ZEND_END_ARG_INFO()\n"
1923 "\n");
1924
1925 for (auto file : files) {
1926 printer.Print("static void $c_name$_AddDescriptor();\n", "c_name",
1927 FilenameCName(file));
1928 }
1929
1930 for (auto file : files) {
1931 std::string c_name = FilenameCName(file);
1932 std::string metadata_filename = GeneratedMetadataFileName(file, Options());
1933 std::string metadata_classname = FilenameToClassname(metadata_filename);
1934 std::string metadata_c_name =
1935 absl::StrReplaceAll(metadata_classname, {{"\\", "_"}});
1936 metadata_classname =
1937 absl::StrReplaceAll(metadata_classname, {{"\\", "\\\\"}});
1938 FileDescriptorProto file_proto = StripSourceRetentionOptions(*file);
1939 std::string serialized;
1940 file_proto.SerializeToString(&serialized);
1941 printer.Print(
1942 "/* $filename$ */\n"
1943 "\n"
1944 "zend_class_entry* $metadata_c_name$_ce;\n"
1945 "\n"
1946 "const char $c_name$_descriptor [$size$] = {\n",
1947 "filename", file->name(), "c_name", c_name, "metadata_c_name",
1948 metadata_c_name, "size", std::to_string(serialized.size()));
1949
1950 for (size_t i = 0; i < serialized.size();) {
1951 for (size_t j = 0; j < 25 && i < serialized.size(); ++i, ++j) {
1952 printer.Print("'$ch$', ", "ch", absl::CEscape(serialized.substr(i, 1)));
1953 }
1954 printer.Print("\n");
1955 }
1956
1957 printer.Print(
1958 "};\n"
1959 "\n"
1960 "static void $c_name$_AddDescriptor() {\n"
1961 " if (DescriptorPool_HasFile(\"$filename$\")) return;\n",
1962 "filename", file->name(), "c_name", c_name, "metadata_c_name",
1963 metadata_c_name);
1964
1965 for (int i = 0; i < file->dependency_count(); i++) {
1966 std::string dep_c_name = FilenameCName(file->dependency(i));
1967 printer.Print(" $dep_c_name$_AddDescriptor();\n", "dep_c_name",
1968 dep_c_name);
1969 }
1970
1971 printer.Print(
1972 " DescriptorPool_AddDescriptor(\"$filename$\", $c_name$_descriptor,\n"
1973 " sizeof($c_name$_descriptor));\n"
1974 "}\n"
1975 "\n"
1976 "static PHP_METHOD($metadata_c_name$, initOnce) {\n"
1977 " $c_name$_AddDescriptor();\n"
1978 "}\n"
1979 "\n"
1980 "static zend_function_entry $metadata_c_name$_methods[] = {\n"
1981 " PHP_ME($metadata_c_name$, initOnce, arginfo_void, ZEND_ACC_PUBLIC | "
1982 "ZEND_ACC_STATIC)\n"
1983 " ZEND_FE_END\n"
1984 "};\n"
1985 "\n"
1986 "static void $metadata_c_name$_ModuleInit() {\n"
1987 " zend_class_entry tmp_ce;\n"
1988 "\n"
1989 " INIT_CLASS_ENTRY(tmp_ce, \"$metadata_classname$\",\n"
1990 " $metadata_c_name$_methods);\n"
1991 "\n"
1992 " $metadata_c_name$_ce = zend_register_internal_class(&tmp_ce);\n"
1993 "}\n"
1994 "\n",
1995 "filename", file->name(), "c_name", c_name, "metadata_c_name",
1996 metadata_c_name, "metadata_classname", metadata_classname);
1997 for (int i = 0; i < file->message_type_count(); i++) {
1998 GenerateCMessage(file->message_type(i), &printer);
1999 }
2000 for (int i = 0; i < file->enum_type_count(); i++) {
2001 GenerateCEnum(file->enum_type(i), &printer);
2002 }
2003 }
2004
2005 printer.Print("static void WellKnownTypes_ModuleInit() {\n");
2006
2007 for (auto file : files) {
2008 std::string metadata_filename = GeneratedMetadataFileName(file, Options());
2009 std::string metadata_classname = FilenameToClassname(metadata_filename);
2010 std::string metadata_c_name =
2011 absl::StrReplaceAll(metadata_classname, {{"\\", "_"}});
2012 printer.Print(" $metadata_c_name$_ModuleInit();\n", "metadata_c_name",
2013 metadata_c_name);
2014 for (int i = 0; i < file->message_type_count(); i++) {
2015 GenerateCInit(file->message_type(i), &printer);
2016 }
2017 for (int i = 0; i < file->enum_type_count(); i++) {
2018 GenerateEnumCInit(file->enum_type(i), &printer);
2019 }
2020 }
2021
2022 printer.Print("}\n");
2023 }
2024
2025 } // namespace
2026
Generate(const FileDescriptor * file,const std::string & parameter,GeneratorContext * generator_context,std::string * error) const2027 bool Generator::Generate(const FileDescriptor* file,
2028 const std::string& parameter,
2029 GeneratorContext* generator_context,
2030 std::string* error) const {
2031 return Generate(file, Options(), generator_context, error);
2032 }
2033
Generate(const FileDescriptor * file,const Options & options,GeneratorContext * generator_context,std::string * error) const2034 bool Generator::Generate(const FileDescriptor* file, const Options& options,
2035 GeneratorContext* generator_context,
2036 std::string* error) const {
2037 if (options.is_descriptor && file->name() != kDescriptorFile) {
2038 *error =
2039 "Can only generate PHP code for google/protobuf/descriptor.proto.\n";
2040 return false;
2041 }
2042
2043 return GenerateFile(file, options, generator_context, error);
2044 }
2045
GenerateAll(const std::vector<const FileDescriptor * > & files,const std::string & parameter,GeneratorContext * generator_context,std::string * error) const2046 bool Generator::GenerateAll(const std::vector<const FileDescriptor*>& files,
2047 const std::string& parameter,
2048 GeneratorContext* generator_context,
2049 std::string* error) const {
2050 Options options;
2051
2052 for (const auto& option : absl::StrSplit(parameter, ",", absl::SkipEmpty())) {
2053 const std::vector<std::string> option_pair =
2054 absl::StrSplit(option, "=", absl::SkipEmpty());
2055 if (absl::StartsWith(option_pair[0], "aggregate_metadata")) {
2056 options.aggregate_metadata = true;
2057 for (const auto& prefix :
2058 absl::StrSplit(option_pair[1], "#", absl::AllowEmpty())) {
2059 options.aggregate_metadata_prefixes.emplace(prefix);
2060 ABSL_LOG(INFO) << prefix;
2061 }
2062 } else if (option_pair[0] == "internal") {
2063 options.is_descriptor = true;
2064 } else if (option_pair[0] == "internal_generate_c_wkt") {
2065 GenerateCWellKnownTypes(files, generator_context);
2066 } else {
2067 ABSL_LOG(FATAL) << "Unknown codegen option: " << option_pair[0];
2068 }
2069 }
2070
2071 for (auto file : files) {
2072 if (!Generate(file, options, generator_context, error)) {
2073 return false;
2074 }
2075 }
2076
2077 return true;
2078 }
2079
2080 } // namespace php
2081 } // namespace compiler
2082 } // namespace protobuf
2083 } // namespace google
2084