Skip to content

Commit 9ad45bc

Browse files
authored
Merge pull request #195 from charjr/codegen-using-atto
Codegen using atto
2 parents a1be123 + c289eab commit 9ad45bc

File tree

15 files changed

+408
-380
lines changed

15 files changed

+408
-380
lines changed

.gitignore

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
vendor
2-
.idea
1+
generated/
2+
vendor/
3+
.idea/
34
composer.lock
45
composer.phar
56
.phpunit.cache

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"require": {
1717
"php": "^8.1.0",
1818
"devizzent/cebe-php-openapi": "^1.1.2",
19+
"atto/codegen-tools": "dev-main",
1920
"psr/http-message": "^1.0 || ^2.0",
2021
"psr/log": "^2.0 || ^3.0",
2122
"symfony/console": "^6.2 || ^7.0",

src/Console/Service/CacheOpenAPIProcessors.php

Lines changed: 16 additions & 232 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,19 @@
44

55
namespace Membrane\Console\Service;
66

7-
use Membrane\Console\Template;
8-
use Membrane\Filter\String\AlphaNumeric;
9-
use Membrane\Filter\String\ToPascalCase;
10-
use Membrane\OpenAPI\Builder\{OpenAPIRequestBuilder, OpenAPIResponseBuilder};
11-
use Membrane\OpenAPI\ExtractPathParameters\PathParameterExtractor;
12-
use Membrane\OpenAPI\Specification\{OpenAPIRequest, OpenAPIResponse};
7+
use Atto\CodegenTools\ClassDefinition\PHPClassDefinitionProducer;
8+
use Atto\CodegenTools\CodeGeneration\PHPFilesWriter;
139
use Membrane\OpenAPIReader\Exception\CannotRead;
1410
use Membrane\OpenAPIReader\Exception\CannotSupport;
1511
use Membrane\OpenAPIReader\Exception\InvalidOpenAPI;
1612
use Membrane\OpenAPIReader\MembraneReader;
1713
use Membrane\OpenAPIReader\OpenAPIVersion;
18-
use Membrane\OpenAPIReader\ValueObject\Valid\{V30, V31};
19-
use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Method;
20-
use Membrane\Processor;
2114
use Psr\Log\LoggerInterface;
2215

2316
class CacheOpenAPIProcessors
2417
{
25-
private OpenAPIRequestBuilder $requestBuilder;
26-
private OpenAPIResponseBuilder $responseBuilder;
27-
private Template\Processor $processorTemplate;
28-
2918
public function __construct(
3019
private readonly LoggerInterface $logger,
31-
private readonly Template\RequestBuilder $requestBuilderTemplate = new Template\RequestBuilder(),
32-
private readonly Template\ResponseBuilder $responseBuilderTemplate = new Template\ResponseBuilder()
3320
) {
3421
}
3522

@@ -40,228 +27,25 @@ public function cache(
4027
bool $buildRequests = true,
4128
bool $buildResponses = true
4229
): bool {
43-
$this->logger->info("Reading OpenAPI from $openAPIFilePath");
44-
try {
45-
$openAPI = (new MembraneReader([
46-
OpenAPIVersion::Version_3_0,
47-
//OpenAPIVersion::Version_3_1, //TODO support 3.1
48-
]))->readFromAbsoluteFilePath($openAPIFilePath);
49-
} catch (CannotRead | CannotSupport | InvalidOpenAPI $e) {
50-
$this->logger->error($e->getMessage());
51-
return false;
52-
}
53-
54-
$this->logger->info("Checking for write permission to $cacheDestinationFilePath");
55-
if (!$this->isDestinationAWriteableDirectory($cacheDestinationFilePath)) {
56-
return false;
57-
}
58-
59-
$processors = $this->buildProcessors($openAPI, $buildRequests, $buildResponses);
60-
61-
$destination = rtrim($cacheDestinationFilePath, '/');
62-
63-
if ($buildRequests) {
64-
// Create Request Directory if it doesn't exist.
65-
if (!file_exists("$destination/Request")) {
66-
mkdir("$destination/Request", recursive: true);
67-
}
68-
}
69-
70-
// Initialize classMap for CachedBuilers
71-
$classMap = $classNames = [];
72-
73-
foreach ($processors as $operationId => $operation) {
74-
$classNames[$operationId] = $this->createSuitableClassName($operationId, $classNames);
75-
$className = $classNames[$operationId];
76-
77-
if (isset($operation['request'])) {
78-
$classMap[$operationId]['request'] = sprintf('%s\Request\%s', $cacheNamespace, $className);
79-
80-
$this->logger->info("Caching $operationId Request at $destination/Request/$className.php");
81-
$this->cacheProcessor(
82-
filePath: "$destination/Request/$className.php",
83-
namespace: "$cacheNamespace\\Request",
84-
className: $className,
85-
processor: $operation['request']
86-
);
87-
}
88-
89-
if (isset($operation['response'])) {
90-
$classMap[$operationId]['response'] = [];
91-
foreach ($operation['response'] as $code => $response) {
92-
$prefixedCode = 'Code' . ucfirst((string)$code);
93-
if (!file_exists("$destination/Response/$prefixedCode")) {
94-
mkdir("$destination/Response/$prefixedCode", recursive: true);
95-
}
30+
$yieldsClasses = new YieldsClassDefinitions($this->logger);
9631

97-
$classMap[$operationId]['response'][(string)$code] =
98-
sprintf('%s\Response\%s\%s', $cacheNamespace, $prefixedCode, $className);
99-
100-
$this->logger->info(
101-
"Caching $operationId $code Response at $destination/Response/$prefixedCode/$className.php"
102-
);
103-
$this->cacheProcessor(
104-
filePath: "$destination/Response/$prefixedCode/$className.php",
105-
namespace: "$cacheNamespace\\Response\\$prefixedCode",
106-
className: $className,
107-
processor: $response
108-
);
109-
}
110-
}
111-
}
112-
113-
$this->logger->info('Processors cached successfully');
114-
115-
if ($buildRequests) {
116-
$this->logger->info('Building CachedRequestBuilder');
117-
118-
$cachedRequestBuilder = $this->requestBuilderTemplate->createFromTemplate(
119-
$cacheNamespace,
32+
try {
33+
$definitionProducer = new PHPClassDefinitionProducer($yieldsClasses(
12034
$openAPIFilePath,
121-
array_map(fn($p) => $p['request'], $classMap)
122-
);
123-
124-
file_put_contents(sprintf('%s/CachedRequestBuilder.php', $cacheDestinationFilePath), $cachedRequestBuilder);
125-
}
126-
127-
if ($buildResponses) {
128-
$this->logger->info('Building CachedResponseBuilder');
129-
130-
$cachedResponseBuilder = $this->responseBuilderTemplate->createFromTemplate(
13135
$cacheNamespace,
132-
$openAPIFilePath,
133-
array_filter(array_map(fn($p) => $p['response'] ?? null, $classMap))
134-
);
135-
136-
file_put_contents(
137-
sprintf('%s/CachedResponseBuilder.php', $cacheDestinationFilePath),
138-
$cachedResponseBuilder
139-
);
36+
$buildRequests,
37+
$buildResponses,
38+
));
39+
40+
$destination = rtrim($cacheDestinationFilePath, '/');
41+
$classWriter = new PHPFilesWriter($destination, $cacheNamespace);
42+
$classWriter->writeFiles($definitionProducer);
43+
} catch (CannotRead | CannotSupport | InvalidOpenAPI | \RuntimeException $e) {
44+
// TODO do not catch RuntimeException once PHPFilesWriter throws specific exceptions
45+
$this->logger->error($e->getMessage());
46+
return false;
14047
}
14148

14249
return true;
14350
}
144-
145-
private function cacheProcessor(string $filePath, string $namespace, string $className, Processor $processor): void
146-
{
147-
$contents = $this->getProcessorTemplate()->createFromTemplate($namespace, $className, $processor);
148-
149-
file_put_contents($filePath, $contents);
150-
}
151-
152-
private function getProcessorTemplate(): Template\Processor
153-
{
154-
if (!isset($this->processorTemplate)) {
155-
$this->processorTemplate = new Template\Processor();
156-
return $this->processorTemplate;
157-
}
158-
return $this->processorTemplate;
159-
}
160-
161-
private function isDestinationAWriteableDirectory(string $destination): bool
162-
{
163-
while (!file_exists($destination)) {
164-
$destination = dirname($destination);
165-
}
166-
167-
if (is_dir($destination) && is_writable($destination)) {
168-
return true;
169-
}
170-
171-
$this->logger->error("Cannot write to $destination");
172-
return false;
173-
}
174-
175-
/** @param array<string,string> $existingClassNames */
176-
private function createSuitableClassName(string $nameToConvert, array $existingClassNames): string
177-
{
178-
$pascalCaseName = (new ToPascalCase())->filter($nameToConvert)->value;
179-
$alphanumericName = (new AlphaNumeric())->filter($pascalCaseName)->value;
180-
181-
assert(is_string($alphanumericName));
182-
if (is_numeric($alphanumericName[0])) {
183-
$alphanumericName = 'm' . $alphanumericName;
184-
}
185-
186-
if (in_array($alphanumericName, $existingClassNames)) {
187-
$i = 1;
188-
do {
189-
$postfixedName = sprintf('%s%d', $alphanumericName, $i++);
190-
} while (in_array($postfixedName, $existingClassNames));
191-
192-
return $postfixedName;
193-
}
194-
195-
return $alphanumericName;
196-
}
197-
198-
private function getRequestBuilder(): OpenAPIRequestBuilder
199-
{
200-
if (!isset($this->requestBuilder)) {
201-
$this->requestBuilder = new OpenAPIRequestBuilder();
202-
return $this->requestBuilder;
203-
}
204-
205-
return $this->requestBuilder;
206-
}
207-
208-
private function getResponseBuilder(): OpenAPIResponseBuilder
209-
{
210-
if (!isset($this->responseBuilder)) {
211-
$this->responseBuilder = new OpenAPIResponseBuilder();
212-
return $this->responseBuilder;
213-
}
214-
return $this->responseBuilder;
215-
}
216-
217-
/**
218-
* @return array<string, array{
219-
* 'request'?: Processor,
220-
* 'response'?: array<string,Processor>
221-
* }>
222-
*/
223-
private function buildProcessors(
224-
V30\OpenAPI | V31\OpenAPI $openAPI,
225-
bool $buildRequests,
226-
bool $buildResponses,
227-
): array {
228-
$processors = [];
229-
foreach ($openAPI->paths as $pathUrl => $path) {
230-
$this->logger->info("Building Processors for $pathUrl");
231-
foreach ($path->getOperations() as $method => $operation) {
232-
$methodObject = Method::tryFrom(strtolower($method));
233-
if ($methodObject === null) {
234-
$this->logger->warning("$method not supported and will be skipped.");
235-
continue;
236-
}
237-
238-
if ($buildRequests) {
239-
$this->logger->info('Building Request processor');
240-
$processors[$operation->operationId]['request'] = $this->getRequestBuilder()->build(
241-
new OpenAPIRequest(
242-
new PathParameterExtractor($pathUrl),
243-
$path,
244-
$methodObject,
245-
)
246-
);
247-
}
248-
249-
if ($buildResponses) {
250-
$processors[$operation->operationId]['response'] = [];
251-
foreach ($operation->responses as $code => $response) {
252-
$this->logger->info("Building $code Response Processor");
253-
254-
$processors[$operation->operationId]['response'][$code] = $this->getResponseBuilder()->build(
255-
new OpenAPIResponse(
256-
$operation->operationId,
257-
(string)$code,
258-
$response,
259-
)
260-
);
261-
}
262-
}
263-
}
264-
}
265-
return $processors;
266-
}
26751
}

0 commit comments

Comments
 (0)