@@ -32,9 +32,11 @@ abstract class BaseDescriptor
3232 protected function __construct (\ReflectionClass $ clazz )
3333 {
3434 // Use reflection to map PHP class fields to elements in the segment/Deg.
35- $ implicitIndex = true ;
3635 $ nextIndex = 0 ;
3736 foreach (static ::enumerateProperties ($ clazz ) as $ property ) {
37+ if ($ nextIndex === null ) {
38+ throw new \InvalidArgumentException ("Disallowed property $ property after an @Unlimited field " );
39+ }
3840 $ docComment = $ property ->getDocComment () ?: '' ;
3941 if (static ::getBoolAnnotation ('Ignore ' , $ docComment )) {
4042 continue ; // Skip @Ignore-d propeties.
@@ -44,22 +46,35 @@ protected function __construct(\ReflectionClass $clazz)
4446 $ descriptor = new ElementDescriptor ();
4547 $ descriptor ->field = $ property ->getName ();
4648 $ maxCount = static ::getIntAnnotation ('Max ' , $ docComment );
49+ $ unlimitedCount = static ::getBoolAnnotation ('Unlimited ' , $ docComment );
4750 if ($ type = static ::getVarAnnotation ($ docComment )) {
4851 if (str_ends_with ($ type , '|null ' )) { // Nullable field
4952 $ descriptor ->optional = true ;
5053 $ type = substr ($ type , 0 , -5 );
5154 }
5255 if (str_ends_with ($ type , '[] ' )) { // Array/repeated field
53- if ($ maxCount === null ) {
54- throw new \InvalidArgumentException ("Repeated property $ property needs @Max() annotation " );
55- }
56- $ descriptor ->repeated = $ maxCount ;
5756 $ type = substr ($ type , 0 , -2 );
58- // If a repeated field is followed by anything at all, there will be an empty entry for each possible
59- // repeated value (in extreme cases, there can be hundreds of consecutive `+`, for instance).
60- $ nextIndex += $ maxCount ;
57+ if ($ unlimitedCount ) {
58+ $ descriptor ->repeated = PHP_INT_MAX ;
59+ // A repeated field of unlimited size cannot be followed by anything, because it would not be
60+ // clear which of the following values still belong to the repeated field vs to the next field.
61+ $ nextIndex = null ;
62+ } elseif ($ maxCount !== null ) {
63+ $ descriptor ->repeated = $ maxCount ;
64+ // If there's another field value after this repeated field, then a serialized message will
65+ // contain placeholders (i.e. empty field values separated by possibly hundreds of `+`) to fill
66+ // up to the repeated field's maximum length, after which the next message continues at the next
67+ // index.
68+ $ nextIndex += $ maxCount ;
69+ } else {
70+ throw new \InvalidArgumentException (
71+ "Repeated property $ property needs @Max(.) or (rarely) @Unlimited annotation "
72+ );
73+ }
6174 } elseif ($ maxCount !== null ) {
6275 throw new \InvalidArgumentException ("@Max() annotation not recognized on single $ property " );
76+ } elseif ($ unlimitedCount ) {
77+ throw new \InvalidArgumentException ("@Unlimited annotation not recognized on single $ property " );
6378 } else {
6479 ++$ nextIndex ; // Singular field, so the index advances by 1.
6580 }
@@ -90,7 +105,7 @@ protected function __construct(\ReflectionClass $clazz)
90105 throw new \InvalidArgumentException ("No fields found in $ clazz ->name " );
91106 }
92107 ksort ($ this ->elements ); // Make sure elements are parsed in wire-format order.
93- $ this ->maxIndex = $ nextIndex - 1 ;
108+ $ this ->maxIndex = $ nextIndex === null ? PHP_INT_MAX : $ nextIndex - 1 ;
94109 }
95110
96111 /**
0 commit comments