Skip to content

Commit f7d8710

Browse files
parse nested DTOs for factories
1 parent 22db218 commit f7d8710

1 file changed

Lines changed: 76 additions & 4 deletions

File tree

src/Generators/JsonApiFactoryGenerator.php

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace Timatic\JsonApiSdk\Generators;
66

7+
use cebe\openapi\spec\Reference;
8+
use cebe\openapi\spec\Schema;
79
use Crescat\SaloonSdkGenerator\Contracts\PostProcessor;
810
use Crescat\SaloonSdkGenerator\Data\Generator\ApiSpecification;
911
use 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

Comments
 (0)