Skip to content

Commit a004669

Browse files
committed
Generate full classes from JSON schema - Close #13
1 parent 1f19c9f commit a004669

25 files changed

+1233
-91
lines changed

composer.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,13 @@
3333
},
3434
"require": {
3535
"php": "^7.4 || ^8.0",
36-
"open-code-modeling/json-schema-to-php": "^0.1.0 || dev-master",
37-
"open-code-modeling/php-code-ast": "^0.8.6 || dev-master"
36+
"open-code-modeling/json-schema-to-php": "0.2.x-dev",
37+
"open-code-modeling/php-code-ast": "0.9.x-dev"
3838
},
3939
"require-dev": {
4040
"jangregor/phpstan-prophecy": "^0.8.0",
4141
"laminas/laminas-filter": "^2.9",
42+
"open-code-modeling/php-filter": "^0.1.1",
4243
"phpspec/prophecy-phpunit": "^2.0",
4344
"phpstan/phpstan": "^0.12.33",
4445
"phpstan/phpstan-strict-rules": "^0.12.4",
@@ -47,6 +48,9 @@
4748
"roave/security-advisories": "dev-master",
4849
"squizlabs/php_codesniffer": "^3.4"
4950
},
51+
"suggest": {
52+
"open-code-modeling/php-filter": "For pre-configured filters for proper class / method / property names etc."
53+
},
5054
"minimum-stability": "dev",
5155
"prefer-stable": true,
5256
"scripts": {

src/ClassGenerator.php

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
<?php
2+
3+
/**
4+
* @see https://github.com/open-code-modeling/json-schema-to-php-ast for the canonical source repository
5+
* @copyright https://github.com/open-code-modeling/json-schema-to-php-ast/blob/master/COPYRIGHT.md
6+
* @license https://github.com/open-code-modeling/json-schema-to-php-ast/blob/master/LICENSE.md MIT License
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace OpenCodeModeling\JsonSchemaToPhpAst;
12+
13+
use OpenCodeModeling\CodeAst\Builder\ClassBuilder;
14+
use OpenCodeModeling\CodeAst\Builder\ClassBuilderCollection;
15+
use OpenCodeModeling\CodeAst\Builder\ClassPropertyBuilder;
16+
use OpenCodeModeling\CodeAst\Package\ClassInfoList;
17+
use OpenCodeModeling\JsonSchemaToPhp\Type\ArrayType;
18+
use OpenCodeModeling\JsonSchemaToPhp\Type\ObjectType;
19+
use OpenCodeModeling\JsonSchemaToPhp\Type\ReferenceType;
20+
use OpenCodeModeling\JsonSchemaToPhp\Type\ScalarType;
21+
use OpenCodeModeling\JsonSchemaToPhp\Type\TypeDefinition;
22+
use OpenCodeModeling\JsonSchemaToPhp\Type\TypeSet;
23+
use PhpParser\NodeTraverser;
24+
use PhpParser\Parser;
25+
use PhpParser\PrettyPrinterAbstract;
26+
27+
final class ClassGenerator
28+
{
29+
private ClassInfoList $classInfoList;
30+
private ValueObjectFactory $valueObjectFactory;
31+
32+
/**
33+
* @var callable
34+
*/
35+
private $classNameFilter;
36+
37+
/**
38+
* @var callable
39+
*/
40+
private $propertyNameFilter;
41+
42+
/**
43+
* @var callable
44+
*/
45+
private $methodNameFilter;
46+
47+
public function __construct(
48+
ClassInfoList $classInfoList,
49+
ValueObjectFactory $valueObjectFactory,
50+
callable $classNameFilter,
51+
callable $propertyNameFilter,
52+
callable $methodNameFilter
53+
) {
54+
$this->classInfoList = $classInfoList;
55+
$this->valueObjectFactory = $valueObjectFactory;
56+
$this->classNameFilter = $classNameFilter;
57+
$this->propertyNameFilter = $propertyNameFilter;
58+
$this->methodNameFilter = $methodNameFilter;
59+
}
60+
61+
/**
62+
* @param ClassBuilder $classBuilder Main class
63+
* @param ClassBuilderCollection $classBuilderCollection Collection for other classes
64+
* @param TypeSet $typeSet
65+
* @param string $srcFolder Source folder for namespace imports
66+
* @param string|null $className Class name of other classes
67+
* @return void
68+
*/
69+
public function generateClasses(
70+
ClassBuilder $classBuilder,
71+
ClassBuilderCollection $classBuilderCollection,
72+
TypeSet $typeSet,
73+
string $srcFolder,
74+
string $className = null
75+
): void {
76+
$type = $typeSet->first();
77+
78+
$classInfo = $this->classInfoList->classInfoForPath($srcFolder);
79+
$classNamespace = $classInfo->getClassNamespaceFromPath($srcFolder);
80+
81+
if ($type instanceof ReferenceType
82+
&& $refType = $type->resolvedType()
83+
) {
84+
$type = $refType->first();
85+
}
86+
87+
switch (true) {
88+
case $type instanceof ObjectType:
89+
/** @var TypeSet $propertyTypeSet */
90+
foreach ($type->properties() as $propertyName => $propertyTypeSet) {
91+
$propertyClassName = ($this->classNameFilter)($propertyName);
92+
$propertyPropertyName = ($this->propertyNameFilter)($propertyName);
93+
94+
$propertyType = $propertyTypeSet->first();
95+
switch (true) {
96+
case $propertyType instanceof ArrayType:
97+
foreach ($propertyType->items() as $itemTypeSet) {
98+
$itemType = $itemTypeSet->first();
99+
100+
if (null === $itemType) {
101+
continue;
102+
}
103+
$itemClassName = ($this->classNameFilter)($itemType->name());
104+
$itemPropertyName = ($this->propertyNameFilter)($itemType->name());
105+
106+
$this->generateClasses(
107+
ClassBuilder::fromScratch($itemClassName, $classNamespace)->setFinal(true),
108+
$classBuilderCollection,
109+
$itemTypeSet,
110+
$srcFolder,
111+
$itemPropertyName
112+
);
113+
}
114+
// no break
115+
case $propertyType instanceof ObjectType:
116+
$this->generateClasses(
117+
ClassBuilder::fromScratch($propertyClassName, $classNamespace)->setFinal(true),
118+
$classBuilderCollection,
119+
$propertyTypeSet,
120+
$srcFolder,
121+
$propertyClassName
122+
);
123+
$classBuilder->addNamespaceImport($classNamespace . '\\' . $propertyClassName);
124+
$classBuilder->addProperty(ClassPropertyBuilder::fromScratch($propertyPropertyName, $propertyClassName));
125+
break;
126+
case $propertyType instanceof ReferenceType:
127+
if ($propertyRefType = $propertyType->resolvedType()) {
128+
$this->generateClasses(
129+
ClassBuilder::fromScratch($propertyClassName, $classNamespace)->setFinal(true),
130+
$classBuilderCollection,
131+
$propertyRefType,
132+
$srcFolder,
133+
$propertyType->name()
134+
);
135+
$propertyClassName = ($this->classNameFilter)($propertyType->name());
136+
$classBuilder->addNamespaceImport($classNamespace . '\\' . $propertyClassName);
137+
}
138+
$classBuilder->addProperty(
139+
ClassPropertyBuilder::fromScratch($propertyPropertyName, $propertyClassName)
140+
);
141+
break;
142+
case $propertyType instanceof ScalarType:
143+
$classBuilderCollection->add(
144+
$this->generateValueObject($propertyClassName, $classNamespace, $propertyType)
145+
);
146+
$classBuilder->addNamespaceImport($classNamespace . '\\' . $propertyClassName);
147+
$classBuilder->addProperty(
148+
ClassPropertyBuilder::fromScratch(
149+
$propertyPropertyName,
150+
$propertyClassName
151+
)
152+
);
153+
break;
154+
default:
155+
break;
156+
}
157+
}
158+
$classBuilderCollection->add($classBuilder);
159+
break;
160+
case $type instanceof ScalarType:
161+
$classBuilderCollection->add(
162+
$this->generateValueObject(($this->classNameFilter)($className), $classNamespace, $type)
163+
);
164+
break;
165+
case $type instanceof ArrayType:
166+
$arrayClassBuilder = $this->generateValueObject(($this->classNameFilter)($className), $classNamespace, $type);
167+
$this->addNamespaceImport($arrayClassBuilder, $type);
168+
$classBuilderCollection->add($arrayClassBuilder);
169+
break;
170+
default:
171+
break;
172+
}
173+
}
174+
175+
public function generateValueObject(string $className, string $classNamespace, TypeDefinition $definition): ClassBuilder
176+
{
177+
$classBuilder = $this->valueObjectFactory->classBuilder($definition);
178+
$classBuilder->setName($className)
179+
->setNamespace($classNamespace)
180+
->setStrict(true)
181+
->setFinal(true);
182+
183+
return $classBuilder;
184+
}
185+
186+
/**
187+
* @param ClassBuilderCollection $classBuilderCollection
188+
* @param Parser $parser
189+
* @param PrettyPrinterAbstract $printer
190+
* @return array<string, string> List of filename => code
191+
*/
192+
public function generateFiles(
193+
ClassBuilderCollection $classBuilderCollection,
194+
Parser $parser,
195+
PrettyPrinterAbstract $printer
196+
): array {
197+
$files = [];
198+
199+
$previousNamespace = '__invalid//namespace__';
200+
201+
foreach ($classBuilderCollection as $classBuilder) {
202+
if ($previousNamespace !== $classBuilder->getNamespace()) {
203+
$previousNamespace = $classBuilder->getNamespace();
204+
$classInfo = $this->classInfoList->classInfoForNamespace($previousNamespace);
205+
$path = $classInfo->getPath($classBuilder->getNamespace() . '\\' . $classBuilder->getName());
206+
}
207+
$filename = $classInfo->getFilenameFromPathAndName($path, $classBuilder->getName());
208+
209+
$nodeTraverser = new NodeTraverser();
210+
$classBuilder->injectVisitors($nodeTraverser, $parser);
211+
212+
$files[$filename] = $printer->prettyPrintFile($nodeTraverser->traverse([]));
213+
}
214+
215+
return $files;
216+
}
217+
218+
private function addNamespaceImport(ClassBuilder $classBuilder, TypeDefinition $typeDefinition): void
219+
{
220+
switch (true) {
221+
case $typeDefinition instanceof ArrayType:
222+
foreach ($typeDefinition->items() as $itemTypeSet) {
223+
$itemType = $itemTypeSet->first();
224+
225+
if (null === $itemType) {
226+
continue;
227+
}
228+
229+
if ($itemType instanceof ReferenceType
230+
&& $refType = $itemType->resolvedType()
231+
) {
232+
$itemType = $refType->first();
233+
}
234+
$itemClassName = ($this->classNameFilter)($itemType->name());
235+
$classBuilder->addNamespaceImport($classBuilder->getNamespace() . '\\' . $itemClassName);
236+
}
237+
break;
238+
default:
239+
break;
240+
}
241+
}
242+
}

src/Common/IteratorFactory.php

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,17 @@ final class IteratorFactory
3131
private PropertyFactory $propertyFactory;
3232
private bool $typed;
3333

34-
public function __construct(Parser $parser, bool $typed)
34+
/**
35+
* @var callable
36+
*/
37+
private $propertyNameFilter;
38+
39+
public function __construct(Parser $parser, bool $typed, callable $propertyNameFilter)
3540
{
3641
$this->parser = $parser;
3742
$this->typed = $typed;
38-
$this->propertyFactory = new PropertyFactory($typed);
43+
$this->propertyNameFilter = $propertyNameFilter;
44+
$this->propertyFactory = new PropertyFactory($typed, $propertyNameFilter);
3945
}
4046

4147
/**
@@ -90,6 +96,8 @@ public function classBuilderFromNative(
9096

9197
public function methodRewind(string $positionPropertyName): MethodGenerator
9298
{
99+
$positionPropertyName = ($this->propertyNameFilter)($positionPropertyName);
100+
93101
$method = new MethodGenerator(
94102
'rewind',
95103
[],
@@ -104,6 +112,8 @@ public function methodRewind(string $positionPropertyName): MethodGenerator
104112

105113
public function methodCurrent(string $name, string $itemType, string $positionPropertyName): MethodGenerator
106114
{
115+
$positionPropertyName = ($this->propertyNameFilter)($positionPropertyName);
116+
107117
$method = new MethodGenerator(
108118
'current',
109119
[],
@@ -118,6 +128,8 @@ public function methodCurrent(string $name, string $itemType, string $positionPr
118128

119129
public function methodKey(string $positionPropertyName): MethodGenerator
120130
{
131+
$positionPropertyName = ($this->propertyNameFilter)($positionPropertyName);
132+
121133
$method = new MethodGenerator(
122134
'key',
123135
[],
@@ -132,6 +144,8 @@ public function methodKey(string $positionPropertyName): MethodGenerator
132144

133145
public function methodNext(string $positionPropertyName): MethodGenerator
134146
{
147+
$positionPropertyName = ($this->propertyNameFilter)($positionPropertyName);
148+
135149
$method = new MethodGenerator(
136150
'next',
137151
[],
@@ -146,6 +160,8 @@ public function methodNext(string $positionPropertyName): MethodGenerator
146160

147161
public function methodValid(string $name, string $positionPropertyName): MethodGenerator
148162
{
163+
$positionPropertyName = ($this->propertyNameFilter)($positionPropertyName);
164+
149165
$method = new MethodGenerator(
150166
'valid',
151167
[],
@@ -161,13 +177,15 @@ public function methodValid(string $name, string $positionPropertyName): MethodG
161177
return $method;
162178
}
163179

164-
public function methodCount(string $name): MethodGenerator
180+
public function methodCount(string $propertyName): MethodGenerator
165181
{
182+
$propertyName = ($this->propertyNameFilter)($propertyName);
183+
166184
$method = new MethodGenerator(
167185
'count',
168186
[],
169187
MethodGenerator::FLAG_PUBLIC,
170-
new BodyGenerator($this->parser, 'return count($this->' . $name . ');')
188+
new BodyGenerator($this->parser, 'return count($this->' . $propertyName . ');')
171189
);
172190
$method->setTyped($this->typed);
173191
$method->setReturnType('int');

src/PropertyFactory.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,15 @@ final class PropertyFactory
2525
**/
2626
private $typed;
2727

28-
public function __construct(bool $typed)
28+
/**
29+
* @var callable
30+
*/
31+
private $propertyNameFilter;
32+
33+
public function __construct(bool $typed, callable $propertyNameFilter)
2934
{
3035
$this->typed = $typed;
36+
$this->propertyNameFilter = $propertyNameFilter;
3137
}
3238

3339
/**
@@ -100,7 +106,7 @@ public function nodeVisitorFromNative(string $name, string $type): array
100106

101107
public function propertyGenerator(string $name, string $type): PropertyGenerator
102108
{
103-
return new PropertyGenerator($name, $type, null, $this->typed);
109+
return new PropertyGenerator(($this->propertyNameFilter)($name), $type, null, $this->typed);
104110
}
105111

106112
/**

0 commit comments

Comments
 (0)