Skip to content

Commit f75c6e5

Browse files
authored
Merge pull request #6 from Tucker-Eric/docblocks
Docblocks
2 parents ffa5020 + d7bee1a commit f75c6e5

31 files changed

+2502
-101
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
vendor
22
.idea
3-
tests/config.php
3+
tests/config.php
4+
composer.lock

bin/create-docblocs.php

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
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();

composer.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
}
1414
],
1515
"require": {
16+
"ext-json": "*",
1617
"php": "^7.2",
17-
"docusign/esign-client": "^4.0"
18+
"docusign/esign-client": "^5.1"
1819
},
1920
"require-dev": {
2021
"phpunit/phpunit": "8.2.*"
@@ -23,5 +24,10 @@
2324
"psr-4": {
2425
"DocuSign\\Rest\\": "src/"
2526
}
27+
},
28+
"autoload-dev": {
29+
"psr-4": {
30+
"Docusign\\Rest\\Tests\\": "tests/"
31+
}
2632
}
2733
}

0 commit comments

Comments
 (0)