1
|
|
|
<?php |
2
|
|
|
/** @noinspection PhpArrayShapeAttributeCanBeAddedInspection */ |
3
|
|
|
|
4
|
|
|
/** @noinspection PhpInternalEntityUsedInspection */ |
5
|
|
|
|
6
|
|
|
|
7
|
|
|
namespace TgScraper\Common; |
8
|
|
|
|
9
|
|
|
use InvalidArgumentException; |
10
|
|
|
use Nette\PhpGenerator\Helpers; |
11
|
|
|
use Nette\PhpGenerator\PhpFile; |
12
|
|
|
use Nette\PhpGenerator\PhpNamespace; |
13
|
|
|
use Nette\PhpGenerator\Type; |
14
|
|
|
use TgScraper\TgScraper; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* Class StubCreator |
18
|
|
|
* @package TgScraper\Common |
19
|
|
|
*/ |
20
|
|
|
class StubCreator |
21
|
|
|
{ |
22
|
|
|
|
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* @var string |
26
|
|
|
*/ |
27
|
|
|
private string $namespace; |
28
|
|
|
/** |
29
|
|
|
* @var array |
30
|
|
|
*/ |
31
|
|
|
private array $abstractClasses = []; |
32
|
|
|
/** |
33
|
|
|
* @var array |
34
|
|
|
*/ |
35
|
|
|
private array $extendedClasses = []; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* StubCreator constructor. |
39
|
|
|
* @param array $schema |
40
|
|
|
* @param string $namespace |
41
|
|
|
* @throws InvalidArgumentException |
42
|
|
|
*/ |
43
|
|
|
public function __construct(private array $schema, string $namespace = '') |
44
|
|
|
{ |
45
|
|
|
if (str_ends_with($namespace, '\\')) { |
46
|
|
|
$namespace = substr($namespace, 0, -1); |
47
|
|
|
} |
48
|
|
|
if (!empty($namespace)) { |
49
|
|
|
if (!Helpers::isNamespaceIdentifier($namespace)) { |
50
|
|
|
throw new InvalidArgumentException('Namespace invalid'); |
51
|
|
|
} |
52
|
|
|
} |
53
|
|
|
if (!TgScraper::validateSchema($this->schema)) { |
54
|
|
|
throw new InvalidArgumentException('Schema invalid'); |
55
|
|
|
} |
56
|
|
|
$this->getExtendedTypes(); |
57
|
|
|
$this->namespace = $namespace; |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Builds the abstract and the extended class lists. |
62
|
|
|
*/ |
63
|
|
|
private function getExtendedTypes(): void |
64
|
|
|
{ |
65
|
|
|
foreach ($this->schema['types'] as $type) { |
66
|
|
|
if (!empty($type['extended_by'])) { |
67
|
|
|
$this->abstractClasses[] = $type['name']; |
68
|
|
|
foreach ($type['extended_by'] as $extendedType) { |
69
|
|
|
$this->extendedClasses[$extendedType] = $type['name']; |
70
|
|
|
} |
71
|
|
|
} |
72
|
|
|
} |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* @param string $str |
77
|
|
|
* @return string |
78
|
|
|
*/ |
79
|
|
|
private static function toCamelCase(string $str): string |
80
|
|
|
{ |
81
|
|
|
return lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $str)))); |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* @param array $fieldTypes |
86
|
|
|
* @param PhpNamespace $phpNamespace |
87
|
|
|
* @return array |
88
|
|
|
*/ |
89
|
|
|
private function parseFieldTypes(array $fieldTypes, PhpNamespace $phpNamespace): array |
90
|
|
|
{ |
91
|
|
|
$types = []; |
92
|
|
|
$comments = []; |
93
|
|
|
foreach ($fieldTypes as $fieldType) { |
94
|
|
|
$comments[] = $fieldType; |
95
|
|
|
if (str_starts_with($fieldType, 'Array')) { |
96
|
|
|
$types[] = 'array'; |
97
|
|
|
continue; |
98
|
|
|
} |
99
|
|
|
if (ucfirst($fieldType) == $fieldType) { |
100
|
|
|
$fieldType = $phpNamespace->getName() . '\\' . $fieldType; |
101
|
|
|
} |
102
|
|
|
$types[] = $fieldType; |
103
|
|
|
} |
104
|
|
|
$comments = empty($comments) ? '' : sprintf('@var %s', implode('|', $comments)); |
105
|
|
|
return [ |
106
|
|
|
'types' => implode('|', $types), |
107
|
|
|
'comments' => $comments |
108
|
|
|
]; |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* @param array $apiTypes |
113
|
|
|
* @param PhpNamespace $phpNamespace |
114
|
|
|
* @return array |
115
|
|
|
*/ |
116
|
|
|
private function parseApiFieldTypes(array $apiTypes, PhpNamespace $phpNamespace): array |
117
|
|
|
{ |
118
|
|
|
$types = []; |
119
|
|
|
$comments = []; |
120
|
|
|
foreach ($apiTypes as $apiType) { |
121
|
|
|
$comments[] = $apiType; |
122
|
|
|
if (str_starts_with($apiType, 'Array')) { |
123
|
|
|
$types[] = 'array'; |
124
|
|
|
$text = $apiType; |
125
|
|
|
while (preg_match('/Array<(.+)>/', $text, $matches) === 1) { |
126
|
|
|
$text = $matches[1]; |
127
|
|
|
} |
128
|
|
|
$subTypes = explode('|', $text); |
129
|
|
|
foreach ($subTypes as $subType) { |
130
|
|
|
if (ucfirst($subType) == $subType) { |
131
|
|
|
$subType = $this->namespace . '\\Types\\' . $subType; |
132
|
|
|
$phpNamespace->addUse($subType); |
133
|
|
|
} |
134
|
|
|
} |
135
|
|
|
continue; |
136
|
|
|
} |
137
|
|
|
if (ucfirst($apiType) == $apiType) { |
138
|
|
|
$apiType = $this->namespace . '\\Types\\' . $apiType; |
139
|
|
|
$phpNamespace->addUse($apiType); |
140
|
|
|
} |
141
|
|
|
$types[] = $apiType; |
142
|
|
|
} |
143
|
|
|
$comments = empty($comments) ? '' : sprintf('@param %s', implode('|', $comments)); |
144
|
|
|
return [ |
145
|
|
|
'types' => implode('|', $types), |
146
|
|
|
'comments' => $comments |
147
|
|
|
]; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* @param string $namespace |
152
|
|
|
* @return PhpFile[] |
153
|
|
|
*/ |
154
|
|
|
private function generateDefaultTypes(string $namespace): array |
155
|
|
|
{ |
156
|
|
|
$interfaceFile = new PhpFile; |
157
|
|
|
$interfaceNamespace = $interfaceFile->addNamespace($namespace); |
158
|
|
|
$interfaceNamespace->addInterface('TypeInterface'); |
159
|
|
|
$responseFile = new PhpFile; |
160
|
|
|
$responseNamespace = $responseFile->addNamespace($namespace); |
161
|
|
|
$responseNamespace->addUse('stdClass'); |
162
|
|
|
$response = $responseNamespace->addClass('Response'); |
163
|
|
|
$response->addProperty('ok') |
164
|
|
|
->setPublic() |
165
|
|
|
->setType(Type::BOOL); |
|
|
|
|
166
|
|
|
$response->addProperty('result') |
167
|
|
|
->setPublic() |
168
|
|
|
->setType(sprintf('stdClass|%s\\TypeInterface|array|int|string|bool', $namespace)) |
169
|
|
|
->setNullable() |
170
|
|
|
->setValue(null); |
171
|
|
|
$response->addProperty('errorCode') |
172
|
|
|
->setPublic() |
173
|
|
|
->setType(Type::INT) |
|
|
|
|
174
|
|
|
->setNullable() |
175
|
|
|
->setValue(null); |
176
|
|
|
$response->addProperty('description') |
177
|
|
|
->setPublic() |
178
|
|
|
->setType(Type::STRING) |
|
|
|
|
179
|
|
|
->setNullable() |
180
|
|
|
->setValue(null); |
181
|
|
|
$response->addProperty('parameters') |
182
|
|
|
->setPublic() |
183
|
|
|
->setType(sprintf('stdClass|%s\\ResponseParameters', $namespace)) |
184
|
|
|
->setNullable() |
185
|
|
|
->setValue(null); |
186
|
|
|
$response->addImplement($namespace . '\\TypeInterface'); |
187
|
|
|
return [ |
188
|
|
|
'Response' => $responseFile, |
189
|
|
|
'TypeInterface' => $interfaceFile |
190
|
|
|
]; |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
/** |
194
|
|
|
* @return PhpFile[] |
195
|
|
|
*/ |
196
|
|
|
private function generateTypes(): array |
197
|
|
|
{ |
198
|
|
|
$namespace = $this->namespace . '\\Types'; |
199
|
|
|
$types = $this->generateDefaultTypes($namespace); |
200
|
|
|
foreach ($this->schema['types'] as $type) { |
201
|
|
|
$file = new PhpFile; |
202
|
|
|
$phpNamespace = $file->addNamespace($namespace); |
203
|
|
|
$typeClass = $phpNamespace->addClass($type['name']); |
204
|
|
|
if (in_array($type['name'], $this->abstractClasses)) { |
205
|
|
|
$typeClass->setAbstract(); |
206
|
|
|
} |
207
|
|
|
if (array_key_exists($type['name'], $this->extendedClasses)) { |
208
|
|
|
$typeClass->setExtends($namespace . '\\' . $this->extendedClasses[$type['name']]); |
209
|
|
|
} else { |
210
|
|
|
$typeClass->addImplement($namespace . '\\TypeInterface'); |
211
|
|
|
} |
212
|
|
|
foreach ($type['fields'] as $field) { |
213
|
|
|
['types' => $fieldTypes, 'comments' => $fieldComments] = $this->parseFieldTypes( |
214
|
|
|
$field['types'], |
215
|
|
|
$phpNamespace |
216
|
|
|
); |
217
|
|
|
$fieldName = self::toCamelCase($field['name']); |
218
|
|
|
$typeProperty = $typeClass->addProperty($fieldName) |
219
|
|
|
->setPublic() |
220
|
|
|
->setType($fieldTypes); |
221
|
|
|
$default = $field['default'] ?? null; |
222
|
|
|
if (!empty($default)) { |
223
|
|
|
$typeProperty->setValue($default); |
224
|
|
|
} |
225
|
|
|
if ($field['optional']) { |
226
|
|
|
$typeProperty->setNullable(); |
227
|
|
|
if (!$typeProperty->isInitialized()) { |
228
|
|
|
$typeProperty->setValue(null); |
229
|
|
|
} |
230
|
|
|
$fieldComments .= '|null'; |
231
|
|
|
} |
232
|
|
|
if (!empty($fieldComments)) { |
233
|
|
|
$fieldComments .= ' ' . $field['description']; |
234
|
|
|
$typeProperty->addComment($fieldComments); |
235
|
|
|
} |
236
|
|
|
} |
237
|
|
|
$types[$type['name']] = $file; |
238
|
|
|
} |
239
|
|
|
return $types; |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
/** |
243
|
|
|
* @return PhpFile |
244
|
|
|
*/ |
245
|
|
|
private function generateApi(): PhpFile |
246
|
|
|
{ |
247
|
|
|
$file = new PhpFile; |
248
|
|
|
$file->addComment('@noinspection PhpUnused'); |
249
|
|
|
$file->addComment('@noinspection PhpUnusedParameterInspection'); |
250
|
|
|
$phpNamespace = $file->addNamespace($this->namespace); |
251
|
|
|
$apiClass = $phpNamespace->addTrait('API'); |
252
|
|
|
$sendRequest = $apiClass->addMethod('sendRequest') |
253
|
|
|
->setPublic() |
254
|
|
|
->setAbstract() |
255
|
|
|
->setReturnType(Type::MIXED); |
|
|
|
|
256
|
|
|
$sendRequest->addParameter('method') |
257
|
|
|
->setType(Type::STRING); |
|
|
|
|
258
|
|
|
$sendRequest->addParameter('args') |
259
|
|
|
->setType(Type::ARRAY); |
|
|
|
|
260
|
|
|
foreach ($this->schema['methods'] as $method) { |
261
|
|
|
$function = $apiClass->addMethod($method['name']) |
262
|
|
|
->setPublic() |
263
|
|
|
->addBody('$args = get_defined_vars();') |
264
|
|
|
->addBody('return $this->sendRequest(__FUNCTION__, $args);'); |
265
|
|
|
$function->addComment($method['description']); |
266
|
|
|
$fields = $method['fields']; |
267
|
|
|
usort( |
268
|
|
|
$fields, |
269
|
|
|
function ($a, $b) { |
270
|
|
|
return $a['optional'] - $b['optional']; |
271
|
|
|
} |
272
|
|
|
); |
273
|
|
|
foreach ($fields as $field) { |
274
|
|
|
['types' => $types, 'comments' => $comment] = $this->parseApiFieldTypes($field['types'], $phpNamespace); |
275
|
|
|
$fieldName = self::toCamelCase($field['name']); |
276
|
|
|
$parameter = $function->addParameter($fieldName) |
277
|
|
|
->setType($types); |
278
|
|
|
$default = $field['default'] ?? null; |
279
|
|
|
if (!empty($default) and (!is_string($default) or lcfirst($default) == $default)) { |
280
|
|
|
$parameter->setDefaultValue($default); |
281
|
|
|
} |
282
|
|
|
if ($field['optional']) { |
283
|
|
|
$parameter->setNullable(); |
284
|
|
|
if (!$parameter->hasDefaultValue()) { |
285
|
|
|
$parameter->setDefaultValue(null); |
286
|
|
|
} |
287
|
|
|
$comment .= '|null'; |
288
|
|
|
} |
289
|
|
|
$comment .= sprintf(' $%s %s', $fieldName, $field['description']); |
290
|
|
|
$function->addComment($comment); |
291
|
|
|
} |
292
|
|
|
['types' => $returnTypes, 'comments' => $returnComment] = $this->parseApiFieldTypes( |
293
|
|
|
$method['return_types'], |
294
|
|
|
$phpNamespace |
295
|
|
|
); |
296
|
|
|
$function->setReturnType($returnTypes); |
297
|
|
|
$function->addComment(str_replace('param', 'return', $returnComment)); |
298
|
|
|
} |
299
|
|
|
return $file; |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* @return array{types: PhpFile[], api: PhpFile} |
304
|
|
|
*/ |
305
|
|
|
public function generateCode(): array |
306
|
|
|
{ |
307
|
|
|
return [ |
308
|
|
|
'types' => $this->generateTypes(), |
309
|
|
|
'api' => $this->generateApi() |
310
|
|
|
]; |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
} |
This class constant has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.