Skip to content

Commit 0def334

Browse files
committed
Class re-usage
Namespace documentation
1 parent ebe2a21 commit 0def334

File tree

22 files changed

+790
-313
lines changed

22 files changed

+790
-313
lines changed

README.md

Lines changed: 188 additions & 179 deletions
Large diffs are not rendered by default.

docs/source/generic/namespaces.rst

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
Namespaces
2+
==========
3+
4+
If a directory with subdirectories is converted into models the directory structure of the JSON-Schema files will be mirrored to the directory structure of the generated models.
5+
Models located in a subdirectory will contain a PSR-4 compatible namespace based on the directory structure.
6+
Set a namespace prefix with the `setNamespacePrefix` method of the `GeneratorConfiguration` object for locating the models inside your application or adding them to your composer autoloading configuration.
7+
8+
An example structure of your JSON-Schema files for a user module may look like:
9+
10+
.. code-block:: none
11+
12+
- Models
13+
- Request
14+
- Login.json
15+
- Register.json
16+
- Update.json
17+
- Response
18+
- Error
19+
- UserExists.json
20+
- Login.json
21+
- Register.json
22+
- Update.json
23+
- Modules
24+
- LoginData.json
25+
- User.json
26+
- Message.json
27+
- generateModels.php
28+
29+
Your model generation code inside `generateModels.php` now could look like:
30+
31+
.. code-block:: php
32+
33+
$generator = new ModelGenerator((new GeneratorConfiguration())->setNamespacePrefix('MyApp\User'));
34+
35+
$generator
36+
->generateModelDirectory(__DIR__ . '/build')
37+
->generateModels(__DIR__ . '/Models', __DIR__ . '/build');
38+
39+
The generated main classes will be:
40+
41+
.. code-block:: none
42+
43+
- build
44+
- Request
45+
- Login.php (FQCN: `MyApp\User\Request\Login`)
46+
- Register.php (FQCN: `MyApp\User\Request\Register`)
47+
- Update.php (FQCN: `MyApp\User\Request\Update`)
48+
- Response
49+
- Error
50+
- UserExists.php (FQCN: `MyApp\User\Response\Error\UserExists`)
51+
- Login.php (FQCN: `MyApp\User\Response\Login`)
52+
- Register.php (FQCN: `MyApp\User\Response\Register`)
53+
- Update.php (FQCN: `MyApp\User\Response\Update`)
54+
55+
Class re-usage
56+
--------------
57+
58+
If referenced classes (eg. in the example given above the modules which may be used in multiple schemas) or nested classes occur multiple times the generator will detect the re-used class and link to the already generated object.
59+
The generator gives a `duplicated signature` hint in the output.
60+
61+
The detection is not bound to namespace limits so a nested object which occurs in `Request\Register.json` as well as in `Response\Register.json` will be generated only once.
62+
Consequently the class generated to `Response\Register.php` may use classes from the `Request` namespace.
63+
A duplicated class is not linked to an already generated class if it's a primary object. The detection only links nested classes.

docs/source/gettingStarted.rst

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ Namespace prefix
119119
120120
setNamespacePrefix(string $prefix);
121121
122-
Configures a namespace prefix for all generated classes. The namespaces will be extended with the directory structure of the source directory. By default no namespace prefix will be set.
122+
Configures a namespace prefix for all generated classes. By default no namespace prefix will be set. Generated namespaces are PSR-4 compatible.
123+
Further information about the generated namespaces can be found at `Namespaces <generic/namespaces.html>`__.
123124

124125
.. code-block:: php
125126
@@ -222,3 +223,14 @@ Enable or disable output of the generation process to STDOUT. By default the out
222223
223224
(new GeneratorConfiguration())
224225
->setOutputEnabled(false);
226+
227+
The output contains information about generated classes, rendered classes, hints and warnings concerning the internal handling or the given schema files.
228+
The output of a generation process may look like:
229+
230+
.. code-block:: none
231+
232+
Generated class MyApp\User\Response\Login
233+
Generated class MyApp\User\Response\Register
234+
Duplicated signature 444fd086d8d1f186145a6f81a3ac3f7a for class Register_Message. Redirecting to Login_Message
235+
Rendered class MyApp\User\Response\Login
236+
Rendered class MyApp\User\Response\Register

docs/source/toc-generic.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44

55
generic/required
66
generic/default
7+
generic/namespaces
78
generic/meta
89
generic/combinedException

src/PropertyProcessor/ComposedValue/AbstractComposedValueProcessor.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,15 @@ protected function transferPropertiesToMergedSchema(Schema $mergedPropertySchema
188188
return false;
189189
})
190190
);
191+
192+
// the parent schema needs to know about all imports of the nested classes as all properties of the
193+
// nested classes are available in the parent schema (combined schema merging)
194+
$this->schema->addNamespaceTransferDecorator(
195+
new SchemaNamespaceTransferDecorator($property->getNestedSchema())
196+
);
191197
}
192198

199+
// make sure the merged schema knows all imports of the parent schema
193200
$mergedPropertySchema->addNamespaceTransferDecorator(new SchemaNamespaceTransferDecorator($this->schema));
194201
}
195202
}

src/PropertyProcessor/Property/ObjectProcessor.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ public function process(string $propertyName, array $propertyData): PropertyInte
4141
$this->schema->getSchemaDictionary()
4242
);
4343

44+
// if the generated schema is located in a different namespace (the schema for the given structure in
45+
// $propertyData is duplicated) add used classes to the current schema. By importing the class which is
46+
// represented by $schema and by transferring all imports of $schema as well as imports for all properties
47+
// of $schema to $this->schema the already generated schema can be used
4448
if ($schema->getClassPath() !== $this->schema->getClassPath()) {
4549
$this->schema->addUsedClass("{$schema->getClassPath()}\\{$schema->getClassName()}");
4650
$this->schema->addNamespaceTransferDecorator(new SchemaNamespaceTransferDecorator($schema, true));

tests/AbstractPHPModelGeneratorTest.php

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@ abstract class AbstractPHPModelGeneratorTest extends TestCase
3232
/**
3333
* Set up an empty directory for the tests
3434
*/
35-
public static function setUpBeforeClass(): void
35+
public function setUp(): void
3636
{
37+
parent::setUp();
38+
3739
if (is_dir(sys_get_temp_dir() . '/PHPModelGeneratorTest')) {
3840
$di = new RecursiveDirectoryIterator(sys_get_temp_dir() . '/PHPModelGeneratorTest', FilesystemIterator::SKIP_DOTS);
3941
$ri = new RecursiveIteratorIterator($di, RecursiveIteratorIterator::CHILD_FIRST);
@@ -74,16 +76,6 @@ public function tearDown(): void
7476
}
7577
}
7678

77-
// clear the JSON schema definitions
78-
foreach ($this->names as $name) {
79-
@unlink(sys_get_temp_dir() . '/PHPModelGeneratorTest/' . $name . '.json');
80-
}
81-
82-
// clear the generated class files
83-
foreach ($this->generatedFiles as $file) {
84-
@unlink($file);
85-
}
86-
8779
$this->names = [];
8880
$this->generatedFiles = [];
8981
}
@@ -119,7 +111,7 @@ private function copyExternalJSON(): void
119111
* @throws RenderException
120112
* @throws SchemaException
121113
*/
122-
public function generateClassFromFile(
114+
protected function generateClassFromFile(
123115
string $file,
124116
GeneratorConfiguration $generatorConfiguration = null,
125117
bool $originalClassNames = false
@@ -145,7 +137,7 @@ public function generateClassFromFile(
145137
* @throws RenderException
146138
* @throws SchemaException
147139
*/
148-
public function generateClassFromFileTemplate(
140+
protected function generateClassFromFileTemplate(
149141
string $file,
150142
array $values,
151143
GeneratorConfiguration $generatorConfiguration = null,
@@ -181,7 +173,7 @@ function ($item) use ($escape) {
181173
* @throws RenderException
182174
* @throws SchemaException
183175
*/
184-
public function generateClass(
176+
protected function generateClass(
185177
string $jsonSchema,
186178
GeneratorConfiguration $generatorConfiguration = null,
187179
bool $originalClassNames = false
@@ -231,12 +223,39 @@ public function getClassName(
231223
foreach ($generatedFiles as $path) {
232224
$this->generatedFiles[] = $path;
233225

234-
require_once $path;
226+
require $path;
235227
}
236228

237229
return $className;
238230
}
239231

232+
/**
233+
* Generate objects for all JSON-Schema files in the given directory
234+
*
235+
* @param string $directory
236+
* @param GeneratorConfiguration $configuration
237+
* @return array
238+
*
239+
* @throws FileSystemException
240+
* @throws RenderException
241+
* @throws SchemaException
242+
*/
243+
protected function generateDirectory(string $directory, GeneratorConfiguration $configuration): array
244+
{
245+
$generatedClasses = (new ModelGenerator($configuration))->generateModels(
246+
__DIR__ . '/Schema/' . $this->getStaticClassName() . '/' . $directory,
247+
MODEL_TEMP_PATH
248+
);
249+
250+
foreach ($generatedClasses as $path) {
251+
$this->generatedFiles[] = $path;
252+
253+
require $path;
254+
}
255+
256+
return $generatedClasses;
257+
}
258+
240259
/**
241260
* Combine two data providers
242261
*

tests/Basic/BasicSchemaGenerationTest.php

Lines changed: 3 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -168,22 +168,11 @@ public function testNamespacePrefix(): void
168168

169169
public function testFolderIsGeneratedRecursively(): void
170170
{
171-
(new ModelGenerator(
172-
(new GeneratorConfiguration())
173-
->setNamespacePrefix('\\Application')
174-
->setPrettyPrint(false)
175-
->setOutputEnabled(false)
176-
))->generateModels(
177-
__DIR__ . '/../Schema/BasicSchemaGenerationTest/RecursiveTest',
178-
MODEL_TEMP_PATH
171+
$this->generateDirectory(
172+
'RecursiveTest',
173+
(new GeneratorConfiguration())->setNamespacePrefix('\\Application')->setOutputEnabled(false)
179174
);
180175

181-
$mainClassFile = MODEL_TEMP_PATH . DIRECTORY_SEPARATOR . 'MainClass.php';
182-
$subClassFile = MODEL_TEMP_PATH . DIRECTORY_SEPARATOR . 'SubFolder' . DIRECTORY_SEPARATOR . 'SubClass.php';
183-
184-
require_once $mainClassFile;
185-
require_once $subClassFile;
186-
187176
$mainClassFQCN = '\\Application\\MainClass';
188177
$mainObject = new $mainClassFQCN(['property' => 'Hello']);
189178

@@ -193,10 +182,6 @@ public function testFolderIsGeneratedRecursively(): void
193182
$subObject = new $subClassFQCN(['property' => 3]);
194183

195184
$this->assertSame(3, $subObject->getProperty());
196-
197-
unlink($mainClassFile);
198-
unlink($subClassFile);
199-
rmdir(MODEL_TEMP_PATH . DIRECTORY_SEPARATOR . 'SubFolder');
200185
}
201186

202187
public function testInvalidJsonSchemaFileThrowsAnException(): void
@@ -223,80 +208,6 @@ public function testJsonSchemaWithInvalidPropertyTypeDefinitionThrowsAnException
223208
$this->generateClassFromFile('JSONSchemaWithInvalidPropertyTypeDefinition.json');
224209
}
225210

226-
public function testIdenticalSchemasAreMappedToOneClass(): void
227-
{
228-
$className = $this->generateClassFromFile('IdenticalSubSchema.json');
229-
230-
$reflection = new ReflectionClass($className);
231-
232-
$this->assertSame(
233-
$reflection->getProperty('object1')->getDocComment(),
234-
$reflection->getProperty('object2')->getDocComment()
235-
);
236-
237-
$object = new $className([
238-
'object1' => ['property1' => 'Hello'],
239-
'object2' => ['property1' => 'Goodbye'],
240-
]);
241-
242-
$this->assertSame('Hello', $object->getObject1()->getProperty1());
243-
$this->assertSame('Goodbye', $object->getObject2()->getProperty1());
244-
245-
$this->assertSame(get_class($object->getObject1()), get_class($object->getObject2()));
246-
}
247-
248-
public function testIdenticalSchemasAreMappedToOneClassFromDifferentNamespaces(): void
249-
{
250-
ob_start();
251-
252-
$generatedClasses = (new ModelGenerator(
253-
(new GeneratorConfiguration())
254-
->setNamespacePrefix('Application')
255-
->setPrettyPrint(false)
256-
))->generateModels(
257-
__DIR__ . '/../Schema/BasicSchemaGenerationTest/IdenticalSubSchemaDifferentNamespace',
258-
MODEL_TEMP_PATH
259-
);
260-
261-
$output = ob_get_contents();
262-
263-
// check for output warnings/messages
264-
$this->assertRegExp('/(.*)Generated class Application\\\SubFolder1\\\SubSchema(.*)/m', $output);
265-
$this->assertRegExp('/(.*)Rendered class Application\\\SubFolder1\\\SubSchema(.*)/m', $output);
266-
$this->assertRegExp('/(.*)Duplicated signature (.*) for class (.*) Redirecting to(.*)/m', $output);
267-
$this->assertRegExp(
268-
'/(.*)Warning: empty composition for property2 may lead to unexpected results(.*)/m',
269-
$output
270-
);
271-
272-
ob_end_clean();
273-
274-
foreach ($generatedClasses as $path) {
275-
require_once $path;
276-
}
277-
278-
$subClass1FQCN = '\\Application\\SubFolder1\\SubSchema';
279-
$subObject1 = new $subClass1FQCN(['object1' => ['property1' => 'Hello'], 'property3' => 3]);
280-
281-
$this->assertSame('Hello', $subObject1->getObject1()->getProperty1());
282-
$this->assertSame(3, $subObject1->getProperty3());
283-
284-
$subClass2FQCN = '\\Application\\SubFolder2\\SubSchema';
285-
$subObject2 = new $subClass2FQCN(['object1' => ['property1' => 'Goodbye'], 'property3' => true]);
286-
287-
$this->assertSame('Goodbye', $subObject2->getObject1()->getProperty1());
288-
$this->assertSame(true, $subObject2->getProperty3());
289-
290-
$this->assertSame(get_class($subObject1->getObject1()), get_class($subObject2->getObject1()));
291-
292-
foreach ($generatedClasses as $path) {
293-
unlink($path);
294-
}
295-
296-
rmdir(MODEL_TEMP_PATH . DIRECTORY_SEPARATOR . 'SubFolder1');
297-
rmdir(MODEL_TEMP_PATH . DIRECTORY_SEPARATOR . 'SubFolder2');
298-
}
299-
300211
public function testDuplicateIdThrowsAnException(): void
301212
{
302213
$this->expectException(FileSystemException::class);

0 commit comments

Comments
 (0)