44
55namespace Timatic \JsonApiSdk \Generators ;
66
7+ use cebe \openapi \spec \Reference ;
8+ use cebe \openapi \spec \Schema ;
79use Crescat \SaloonSdkGenerator \Contracts \PostProcessor ;
810use Crescat \SaloonSdkGenerator \Data \Generator \ApiSpecification ;
911use Crescat \SaloonSdkGenerator \Data \Generator \Config ;
@@ -20,6 +22,9 @@ class JsonApiFactoryGenerator implements PostProcessor
2022
2123 protected Config $ config ;
2224
25+ protected ApiSpecification $ specification ;
26+ protected ?Schema $ dtoSchema ;
27+
2328 /**
2429 * Get Foundation class with target namespace
2530 */
@@ -31,6 +36,7 @@ protected function foundationClass(string $relativePath): string
3136 public function process (Config $ config , ApiSpecification $ specification , GeneratedCode $ generatedCode ,): PhpFile |array |null
3237 {
3338 $ this ->config = $ config ;
39+ $ this ->specification = $ specification ;
3440
3541 foreach ($ generatedCode ->dtoClasses as $ dtoClassName => $ dtoClass ) {
3642 $ this ->generateFactoryClass ($ dtoClassName , $ dtoClass );
@@ -44,6 +50,8 @@ protected function generateFactoryClass(string $dtoClassName, PhpFile $dtoClass)
4450 {
4551 $ factoryName = $ dtoClassName .'Factory ' ;
4652
53+ $ this ->dtoSchema = $ this ->getSchemaForDto ($ dtoClassName );
54+
4755 $ classType = new ClassType ($ factoryName );
4856 $ classFile = new PhpFile ;
4957 $ namespace = $ classFile ->addNamespace ("{$ this ->config ->namespace }\\Factories " );
@@ -67,7 +75,7 @@ protected function generateFactoryClass(string $dtoClassName, PhpFile $dtoClass)
6775 ->setReturnType ('array ' )
6876 ->setProtected ();
6977
70- $ definitionBody = $ this ->generateDefinitionBody ($ properties , $ namespace );
78+ $ definitionBody = $ this ->generateDefinitionBody ($ dtoClassName , $ properties , $ namespace );
7179 $ definitionMethod ->setBody ($ definitionBody );
7280
7381 // Add modelClass() method
@@ -100,6 +108,35 @@ protected function getPropertiesToSkip(): array
100108 return ['id ' , 'type ' ];
101109 }
102110
111+ /**
112+ * Get the Schema object for a given DTO class name
113+ */
114+ protected function getSchemaForDto (string $ dtoClassName ): ?Schema
115+ {
116+ return $ this ->specification ->components ->schemas [$ dtoClassName ] ?? null ;
117+ }
118+
119+ /**
120+ * Extract properties from JSON:API schema structure (same as DtoGenerator)
121+ *
122+ * @return array<string, Schema|Reference>
123+ */
124+ protected function extractDtoProperties (): array
125+ {
126+ if (!$ this ->dtoSchema ) {
127+ return [];
128+ }
129+
130+ $ attributesSchema = $ this ->dtoSchema ->properties ['attributes ' ];
131+
132+ if ($ attributesSchema instanceof Schema && isset ($ attributesSchema ->properties )) {
133+ // Return properties from the attributes object
134+ return $ attributesSchema ->properties ;
135+ }
136+
137+ return [];
138+ }
139+
103140 /**
104141 * Get DTO properties using the PhpFile representation (no reflection)
105142 *
@@ -181,13 +218,22 @@ protected function getDtoPropertiesFromPhpFile(PhpFile $dtoClass): array
181218 /**
182219 * Generate the body of the definition() method
183220 */
184- protected function generateDefinitionBody (array $ properties , $ namespace ): string
221+ protected function generateDefinitionBody (string $ dtoClassName , array $ properties , $ namespace ): string
185222 {
186223 $ lines = ['return [ ' ];
187224
188225 foreach ($ properties as $ property ) {
189226 $ propertyName = $ property ['name ' ];
190- $ fakerCall = $ this ->generateFakerCall ($ propertyName , $ property ['type ' ], $ property ['isDateTime ' ]);
227+
228+ // Check if this property is a Reference in the schema
229+ $ referencedDtoClass = $ this ->getReferencedDtoClass ($ dtoClassName , $ propertyName );
230+
231+ $ fakerCall = $ this ->generateFakerCall (
232+ $ propertyName ,
233+ $ property ['type ' ],
234+ $ property ['isDateTime ' ],
235+ $ referencedDtoClass
236+ );
191237
192238 $ lines [] = " ' {$ propertyName }' => {$ fakerCall }, " ;
193239 }
@@ -206,7 +252,7 @@ protected function generateDefinitionBody(array $properties, $namespace): string
206252 /**
207253 * Generate appropriate Faker call for a property
208254 */
209- protected function generateFakerCall (string $ propertyName , ?string $ propertyType , bool $ isDateTime ): string
255+ protected function generateFakerCall (string $ propertyName , ?string $ propertyType , bool $ isDateTime, ? string $ referencedDtoClass = null ): string
210256 {
211257 $ lowerName = strtolower ($ propertyName );
212258
@@ -215,6 +261,11 @@ protected function generateFakerCall(string $propertyName, ?string $propertyType
215261 return 'Carbon::now()->subDays($this->faker->numberBetween(0, 365)) ' ;
216262 }
217263
264+ // Handle nested DTO properties (from OpenAPI $ref)
265+ if ($ referencedDtoClass !== null ) {
266+ return "{$ referencedDtoClass }Factory::new()->make() " ;
267+ }
268+
218269 // Handle by property type FIRST (type takes precedence over naming)
219270 if ($ propertyType ) {
220271 $ baseType = ltrim ($ propertyType , '? \\' );
@@ -288,4 +339,25 @@ protected function generateFakerCall(string $propertyName, ?string $propertyType
288339 // Default to word for strings
289340 return '$this->faker->word() ' ;
290341 }
342+
343+ /**
344+ * @param Schema|Reference|null $schemaProperty
345+ * @return string|null
346+ */
347+ public function getReferencedDtoClass (string $ propertyName ): ?string
348+ {
349+ // Get the schema for this DTO to check for References
350+ $ schemaProperties = $ this ->extractDtoProperties ();
351+
352+ $ schemaProperty = $ schemaProperties [$ propertyName ] ?? null ;
353+ $ referencedDtoClass = null ;
354+
355+ if ($ schemaProperty instanceof Reference) {
356+ // Extract the DTO class name from the reference
357+ $ referencedDtoClass = \Illuminate \Support \Str::afterLast ($ schemaProperty ->getReference (), '/ ' );
358+ $ referencedDtoClass = ucfirst ($ referencedDtoClass );
359+ }
360+
361+ return $ referencedDtoClass ;
362+ }
291363}
0 commit comments