Add php protobuffer support for transition to GTFS-realtime
[busui.git] / lib / Protobuf-PHP / library / DrSlump / Protobuf / AnnotatedMessage.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
<?php
 
namespace DrSlump\Protobuf;
 
use DrSlump\Protobuf;
 
abstract class AnnotatedMessage extends Message
{
    static public function descriptor()
    {
        $class = get_called_class();
 
        // Instantiate a new descriptor
        $descriptor = new Descriptor($class);
 
        $rflClass = new \ReflectionClass($class);
        $props = $rflClass->getProperties();
        foreach ($props as $prop) {
            $doc = $prop->getDocComment();
            if (empty($doc)) {
                continue;
            }
 
            // Format: @protobuf(tag=X, type=bool, required=true)
 
            // Extract annotation from the comment
            if (!preg_match('/@protobuf\s?\(([^\)]+)\)/', $doc, $m)) {
                continue;
            }
 
            // Parse params
            $params = explode(',', $m[1]);
            $params = array_filter(array_map('trim', $params));
 
            $options = array();
            foreach ($params as $param) {
                $parts = explode('=', $param);
                $parts = array_filter(array_map('trim', $parts));
                $options[$parts[0]] = count($parts) < 2
                                    ? true
                                    : $parts[1];
            }
 
            // Check if we have the minimum required options
            if (empty($options['tag'])) {
                throw new \InvalidArgumentException('The tag option is required for property ' . $prop->getName());
            }
            if (empty($options['type'])) {
                throw new \InvalidArgumentException('The type option is required for property ' . $prop->getName());
            }
 
            // Normalize boolean values
            foreach(array('required', 'optional', 'repeated', 'packed') as $opt) {
                if (isset($options[$opt])) {
                    $options[$opt] = filter_var($options[$opt], FILTER_VALIDATE_BOOLEAN);
                }
            }
 
            // Build a field descriptor
            $f = new Protobuf\Field();
            $f->number    = (int)$options['tag'];
            $f->name      = $prop->getName();
 
            // Convert type name to its numeric constant
            switch (strtolower($options['type'])) {
            case 'double'  : $f->type = Protobuf::TYPE_DOUBLE; break;
            case 'float'   : $f->type = Protobuf::TYPE_FLOAT; break;
            case 'int64'   : $f->type = Protobuf::TYPE_INT64; break;
            case 'uint64'  : $f->type = Protobuf::TYPE_UINT64; break;
            case 'int32'   : $f->type = Protobuf::TYPE_INT32; break;
            case 'fixed64' : $f->type = Protobuf::TYPE_FIXED64; break;
            case 'fixed32' : $f->type = Protobuf::TYPE_FIXED32; break;
            case 'bool'    : $f->type = Protobuf::TYPE_BOOL; break;
            case 'string'  : $f->type = Protobuf::TYPE_STRING; break;
            case 'message' : $f->type = Protobuf::TYPE_MESSAGE; break;
            case 'bytes'   : $f->type = Protobuf::TYPE_BYTES; break;
            case 'uint32'  : $f->type = Protobuf::TYPE_UINT32; break;
            case 'enum'    : $f->type = Protobuf::TYPE_ENUM; break;
            case 'sfixed32': $f->type = Protobuf::TYPE_SFIXED32; break;
            case 'sfixed64': $f->type = Protobuf::TYPE_SFIXED64; break;
            case 'sint32'  : $f->type = Protobuf::TYPE_SINT32; break;
            case 'sint64'  : $f->type = Protobuf::TYPE_SINT64; break;
            default:
                throw new \InvalidArgumentException('Type ' . $options['type'] . ' is not recognized as valid for property ' . $prop->getName());
            }
 
            // Define the rule type
            $f->rule = Protobuf::RULE_OPTIONAL;
            if (!empty($options['required'])) $f->rule = Protobuf::RULE_REQUIRED;
            if (!empty($options['repeated'])) $f->rule = Protobuf::RULE_REPEATED;
 
            // Check if it's flagged as packed
            if (isset($options['packed'])) {
                $f->packed = $options['packed'];
            }
 
            // Get the reference
            if (isset($options['reference'])) {
                $f->reference = $options['reference'];
            } else if ($f->type === Protobuf::TYPE_MESSAGE || $f->type === Protobuf::TYPE_ENUM) {
                throw new \InvalidArgumentException('Property ' . $prop->getName() . ' requires the "reference" option');
            }
 
            if (isset($options['default'])) {
                $f->default = $options['default'];
            }
 
            // Add the field to the message descriptor
            $descriptor->addField($f);
        }
 
        return $descriptor;
    }
 
}