1
+ #!/usr/bin/env php
2
+ <?php
3
+
4
+ require_once __DIR__ . '/../vendor/autoload.php ' ;
5
+
6
+ const DOCUSIGN_SRC_DIR = __DIR__ . '/../vendor/docusign/esign-client/src ' ;
7
+ const DOCUSIGN_NAMESPACE = '\\DocuSign \\eSign ' ;
8
+ const DOCUSIGN_API_NAMESPACE = DOCUSIGN_NAMESPACE . '\\Api ' ;
9
+ const DOCUSIGN_API_NAMESPACE_ALIAS = 'Api ' ;
10
+
11
+ const DOCUSIGN_MODEL_NAMESPACE = DOCUSIGN_NAMESPACE . '\\Model ' ;
12
+ const DOCUSIGN_MODEL_NAMESPACE_ALIAS = 'Models ' ;
13
+
14
+ const SRC_DIR = __DIR__ . '/../src ' ;
15
+ const API_DIR = SRC_DIR . '/Api ' ;
16
+
17
+ $ apisUsingAccountId = [];
18
+
19
+ function createApiClassDocblock ($ classname )
20
+ {
21
+ global $ apisUsingAccountId ;
22
+
23
+ $ vendorClassname = DOCUSIGN_API_NAMESPACE_ALIAS . '\\' . $ classname . 'Api ' ;
24
+
25
+ $ reflectionClass = new ReflectionClass (DOCUSIGN_API_NAMESPACE . '\\' . $ classname . 'Api ' );
26
+ $ publicMethods = $ reflectionClass ->getMethods (ReflectionMethod::IS_PUBLIC );
27
+ $ methods = [' * @method ' . $ vendorClassname . ' getClient() ' ];
28
+ foreach ($ publicMethods as $ method ) {
29
+ $ params = $ method ->getParameters ();
30
+ if (count ($ params ) > 0 && $ params [0 ]->name === 'account_id ' ) {
31
+ // Some methods/classes don't require account_id
32
+ // Because we verify we're authenticated before making a request
33
+ $ apisUsingAccountId [$ classname ][$ method ->name ] = true ;
34
+ array_shift ($ params );
35
+ }
36
+
37
+ // We'll support the `WithHttpInfo` to inject the account_id for debugging but the base methods should be used outside of that
38
+ if (strpos ($ method ->name , 'WithHttpInfo ' ) !== false || strpos ($ method ->name , '_ ' ) !== false ) {
39
+ continue ;
40
+ }
41
+
42
+ $ description = '' ;
43
+ if (preg_match_all ('/\*\s+([\w\s\(\)]+)/ ' , $ method ->getDocComment (), $ textMatches )) {
44
+ $ description = implode (' ' , array_filter (array_map ('trim ' , $ textMatches [1 ]), static function ($ param ) {
45
+ return !empty ($ param ) && stripos ($ param , 'operation ' ) !== 0 ;
46
+ }));
47
+ }
48
+
49
+ $ paramDoc = [];
50
+ if (preg_match_all ('/@param\s+([^\$]\S+)?\s*(\$\w+)?(.*)/ ' , $ method ->getDocComment (), $ matches )) {
51
+ [$ allMatches , $ types , $ varnames , $ descriptions ] = $ matches ;
52
+
53
+ foreach ($ allMatches as $ k => $ m ) {
54
+ if ($ varnames [$ k ] !== '$account_id ' ) {
55
+ // if there is no type this will be empty and we try to fetch from reflection
56
+ $ type = $ types [$ k ] ?: getParamType ($ params , $ varnames [$ k ]);
57
+ // If this returns an option type then we need to add that method
58
+ if (stripos ($ type , $ vendorClassname . '\\' . ucfirst ($ method ->name ) . 'Options ' ) !== false ) {
59
+ $ setters = array_filter ((new ReflectionClass ($ type ))->getMethods (), static function (ReflectionMethod $ method ) {
60
+ // Only want setters because that's what the class supports
61
+ return strpos ($ method ->name , 'set ' ) === 0 ;
62
+ });
63
+ // Create the array values with null defaults
64
+ $ arrayKeys = array_map (static function (ReflectionMethod $ setter ) {
65
+ return "' " . strtolower (preg_replace ('/[A-Z]([A-Z](?![a-z]))*/ ' , '_$0 ' , lcfirst ($ setter ->name ))) . "' => null " ;
66
+ }, $ setters );
67
+
68
+ $ methods [] = ' * @method ' . $ type . ' ' . $ method ->name . 'Options(array $options = [ ' . implode (', ' , $ arrayKeys ) . ']) ' ;
69
+ }
70
+ // If there is still no type we skip it
71
+ $ paramDoc [] = ($ type ? $ type . ' ' : '' )
72
+ . $ varnames [$ k ]
73
+ // Default to null when optional
74
+ . (strpos ($ descriptions [$ k ], '(optional) ' ) !== false ? ' = null ' : '' );
75
+ }
76
+ }
77
+ }
78
+
79
+ $ returnType = '' ;
80
+ if ($ method ->hasReturnType ()) {
81
+ $ returnType = (string )$ method ->getReturnType ();
82
+ } elseif (preg_match ('/@return\s+(\S+)/ ' , $ method ->getDocComment (), $ returnMatch )) {
83
+
84
+ if ($ returnMatch [1 ] === $ classname . 'Api ' ) {
85
+ // If this returns itself we need to give the FQCN
86
+ $ returnMatch [1 ] = DOCUSIGN_API_NAMESPACE_ALIAS . '\\' . $ returnMatch [1 ];
87
+ }
88
+
89
+ $ returnType = $ returnMatch [1 ];
90
+ }
91
+ $ methods [] = ' * @method ' . $ returnType . ' ' . $ method ->name . '( ' . implode (', ' , $ paramDoc ) . ') ' . $ description ;
92
+ }
93
+
94
+ // Replace the namespace with the aliases
95
+ $ methodBlock = str_replace (
96
+ [DOCUSIGN_API_NAMESPACE , DOCUSIGN_MODEL_NAMESPACE ],
97
+ [DOCUSIGN_API_NAMESPACE_ALIAS , DOCUSIGN_MODEL_NAMESPACE_ALIAS ],
98
+ implode ("\n" , $ methods )
99
+ );
100
+
101
+ return <<<DOCBLOC
102
+ /**
103
+ * Class $ classname
104
+ $ methodBlock
105
+ */
106
+ DOCBLOC ;
107
+ }
108
+
109
+ /**
110
+ * @param ReflectionParameter[] $parameters
111
+ * @param $param
112
+ * @return string|null
113
+ */
114
+ function getParamType (array $ parameters , string $ param )
115
+ {
116
+ foreach ($ parameters as $ p ) {
117
+ if ($ p ->name === preg_replace ('/^\$/ ' , '' , $ param ) && $ p ->hasType ()) {
118
+ return "\\" . ((string )$ p ->getType ());
119
+ }
120
+ }
121
+
122
+ return '' ;
123
+ }
124
+
125
+ function generateApiClasses ()
126
+ {
127
+ // Remove old Api classes EXCEPT for the BaseApi class
128
+ foreach (glob (API_DIR . '/*.php ' ) as $ apiFile ) {
129
+ if ($ apiFile !== API_DIR . '/BaseApi.php ' ) {
130
+ unlink ($ apiFile );
131
+ }
132
+ }
133
+ global $ apisUsingAccountId ;
134
+ // Only generate a 1:1 with docusign
135
+ foreach (glob (DOCUSIGN_SRC_DIR . '/Api/*.php ' ) as $ apiFile ) {
136
+ $ classname = str_replace ('Api.php ' , '' , basename ($ apiFile ));
137
+ $ docBlock = createApiClassDocblock ($ classname );
138
+ $ methodsUsingAccountId = array_map (static function ($ var ) {
139
+ return "' $ var' " ;
140
+ }, array_keys ($ apisUsingAccountId [$ classname ] ?? []));
141
+ $ usesAccountId = empty ($ methodsUsingAccountId )
142
+ ? ''
143
+ : " protected \$methodsWithAccountId = [ \n " . implode (", \n " , $ methodsUsingAccountId ) . "\n]; " ;
144
+ $ apiNamespace = preg_replace ('/^ \\\/ ' , '' , DOCUSIGN_API_NAMESPACE );
145
+ $ apiNamespaceAlias = DOCUSIGN_API_NAMESPACE_ALIAS ;
146
+ $ modelNamespace = DOCUSIGN_MODEL_NAMESPACE ;
147
+ $ modelNamespaceAlias = DOCUSIGN_MODEL_NAMESPACE_ALIAS ;
148
+ $ template = <<<TEMPLATE
149
+ <?php
150
+
151
+ namespace DocuSign\Rest\Api;
152
+
153
+ use $ apiNamespace as $ apiNamespaceAlias;
154
+ use $ modelNamespace as $ modelNamespaceAlias;
155
+
156
+ $ docBlock
157
+ class $ classname extends BaseApi
158
+ {
159
+ $ usesAccountId
160
+ }
161
+ TEMPLATE ;
162
+
163
+ file_put_contents (API_DIR . '/ ' . $ classname . '.php ' , $ template );
164
+ }
165
+ }
166
+
167
+ function generateClientDocBlocks ()
168
+ {
169
+ $ docblock = ['/** ' , ' * Class Client ' ];
170
+ foreach (glob (API_DIR . '/*.php ' ) as $ file ) {
171
+ if (strpos ($ file , 'BaseApi ' ) === false ) {
172
+ $ filename = basename ($ file );
173
+ $ classname = str_replace ('.php ' , '' , $ filename );
174
+ $ varName = '$ ' . lcfirst ($ classname );
175
+ $ docblock [] = ' * @property-read Api \\' . $ classname . ' ' . $ varName ;
176
+ }
177
+ }
178
+
179
+ foreach (glob (DOCUSIGN_SRC_DIR . '/Model/*.php ' ) as $ modelFile ) {
180
+ $ modelBaseClass = str_replace ('.php ' , '' , basename ($ modelFile ));
181
+ $ modelClass = '\\DocuSign \\eSign \\Model \\' . $ modelBaseClass ;
182
+ $ constructorArray = implode (', ' , array_map (static function ($ prop ) {
183
+ return "' $ prop' => null " ;
184
+ }, array_keys ($ modelClass ::setters ())));
185
+ // Client aliases the models namespace as Models in the client class
186
+ $ docblock [] = ' * @method Models \\' . $ modelBaseClass . ' ' . lcfirst ($ modelBaseClass ) . '(array $props = [ ' . $ constructorArray . ']) ' ;
187
+ }
188
+
189
+ $ docblock [] = ' */ ' ;
190
+
191
+ $ clientReflection = new ReflectionClass (\DocuSign \Rest \Client::class);
192
+
193
+ $ client = SRC_DIR . '/Client.php ' ;
194
+ $ content = str_replace ($ clientReflection ->getDocComment (), implode ("\n" , $ docblock ), file_get_contents ($ client ));
195
+ echo 'Writing Client ' ;
196
+ file_put_contents ($ client , $ content );
197
+ }
198
+
199
+ // These come first because client docblocks rely on them
200
+ generateApiClasses ();
201
+ // Generates docblocks for our API implementation
202
+ generateClientDocBlocks ();
0 commit comments