44
55namespace 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 ;
139use Membrane \OpenAPIReader \Exception \CannotRead ;
1410use Membrane \OpenAPIReader \Exception \CannotSupport ;
1511use Membrane \OpenAPIReader \Exception \InvalidOpenAPI ;
1612use Membrane \OpenAPIReader \MembraneReader ;
1713use Membrane \OpenAPIReader \OpenAPIVersion ;
18- use Membrane \OpenAPIReader \ValueObject \Valid \{V30 , V31 };
19- use Membrane \OpenAPIReader \ValueObject \Valid \Enum \Method ;
20- use Membrane \Processor ;
2114use Psr \Log \LoggerInterface ;
2215
2316class 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