Skip to content

Commit 181fac2

Browse files
authored
fix: introduce ConfigurationAttributeGenerator (#381)
1 parent b8ff914 commit 181fac2

File tree

10 files changed

+216
-21
lines changed

10 files changed

+216
-21
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\SchemaGenerator\AttributeGenerator;
15+
16+
use ApiPlatform\SchemaGenerator\Model\Attribute;
17+
use ApiPlatform\SchemaGenerator\Model\Class_;
18+
use ApiPlatform\SchemaGenerator\Model\Property;
19+
use ApiPlatform\SchemaGenerator\Model\Use_;
20+
use ApiPlatform\SchemaGenerator\Schema\Model\Class_ as SchemaClass;
21+
22+
final class ConfigurationAttributeGenerator extends AbstractAttributeGenerator
23+
{
24+
/**
25+
* {@inheritdoc}
26+
*/
27+
public function generateClassAttributes(Class_ $class): array
28+
{
29+
$typeConfig = $this->config['types'][$class->name()] ?? null;
30+
$vocabConfig = null;
31+
if ($class instanceof SchemaClass) {
32+
$vocabConfig = $this->config['vocabularies'][$class->resource()->getGraph()->getUri()] ?? null;
33+
}
34+
35+
$attributes = [];
36+
$configAttributes = array_merge($vocabConfig['attributes'] ?? [], $typeConfig['attributes'] ?? []);
37+
foreach ($configAttributes as $attributeName => $attributeArgs) {
38+
$attributes[] = new Attribute($attributeName, ($attributeArgs ?? []) + ['alwaysGenerate' => !isset($vocabConfig['attributes'][$attributeName]) || isset($typeConfig['attributes'][$attributeName])]);
39+
}
40+
41+
return $attributes;
42+
}
43+
44+
/**
45+
* {@inheritdoc}
46+
*/
47+
public function generatePropertyAttributes(Property $property, string $className): array
48+
{
49+
$typeConfig = $this->config['types'][$className] ?? null;
50+
$propertyConfig = $typeConfig['properties'][$property->name()] ?? null;
51+
52+
$attributes = [];
53+
foreach ($propertyConfig['attributes'] ?? [] as $attributeName => $attributeArgs) {
54+
$attributes[] = new Attribute($attributeName, $attributeArgs ?? []);
55+
}
56+
57+
return $attributes;
58+
}
59+
60+
/**
61+
* {@inheritdoc}
62+
*/
63+
public function generateUses(Class_ $class): array
64+
{
65+
$uses = [];
66+
foreach ($this->config['uses'] as $useName => $useArgs) {
67+
$uses[] = new Use_($useName, $useArgs['alias'] ?? null);
68+
}
69+
70+
return $uses;
71+
}
72+
}

src/Model/AddAttributeTrait.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ public function addAttribute(Attribute $attribute): self
2222
$this->attributes,
2323
fn (Attribute $attr) => $attr->name() === $attribute->name()
2424
))) {
25-
$this->attributes[] = $attribute;
25+
if ($attribute->append) {
26+
$this->attributes[] = $attribute;
27+
}
2628
} else {
2729
$this->attributes = array_map(
2830
fn (Attribute $attr) => $attr->name() === $attribute->name()
29-
? new Attribute($attr->name(), array_merge($attribute->args(), $attr->args()))
31+
? new Attribute($attr->name(), array_merge($attr->args(), $attribute->args()))
3032
: $attr,
3133
$this->attributes
3234
);

src/Model/Attribute.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,18 @@ final class Attribute
2525
/** @var (int|bool|null|string|string[]|string[][]|\Nette\PhpGenerator\Literal|\Nette\PhpGenerator\Literal[])[] */
2626
private array $args;
2727

28+
public bool $append = true;
29+
2830
/**
2931
* @param (int|bool|null|string|string[]|string[][]|\Nette\PhpGenerator\Literal|\Nette\PhpGenerator\Literal[])[] $args
3032
*/
3133
public function __construct(string $name, array $args = [])
3234
{
3335
$this->name = $name;
36+
37+
$this->append = (bool) ($args['alwaysGenerate'] ?? true);
38+
unset($args['alwaysGenerate']);
39+
3440
$this->args = $args;
3541
}
3642

src/OpenApi/ClassGenerator.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -220,10 +220,6 @@ private function buildClassFromSchema(Schema $schema, string $name, array $confi
220220
$class->setRdfType($schema->externalDocs->url);
221221
}
222222

223-
foreach ($config['uses'] as $useName => $useArgs) {
224-
$class->addUse(new Use_($useName, $useArgs['alias'] ?? null));
225-
}
226-
227223
foreach ($schema->properties as $propertyName => $schemaProperty) {
228224
\assert($schemaProperty instanceof Schema);
229225
$property = ($this->propertyGenerator)($propertyName, $config, $class, ['schema' => $schema, 'property' => $schemaProperty]);

src/Schema/PropertyGenerator/PropertyGenerator.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
use ApiPlatform\SchemaGenerator\CardinalitiesExtractor;
1717
use ApiPlatform\SchemaGenerator\GoodRelationsBridge;
18-
use ApiPlatform\SchemaGenerator\Model\Attribute;
1918
use ApiPlatform\SchemaGenerator\Model\Class_;
2019
use ApiPlatform\SchemaGenerator\Model\Property;
2120
use ApiPlatform\SchemaGenerator\PhpTypeConverterInterface;
@@ -165,10 +164,6 @@ public function __invoke(string $name, array $config, Class_ $class, array $cont
165164
$schemaProperty->inversedBy = $propertyConfig['inversedBy'] ?? null;
166165
$schemaProperty->groups = $propertyConfig['groups'] ?? [];
167166

168-
foreach ($propertyConfig['attributes'] ?? [] as $attributeName => $attributeArgs) {
169-
$schemaProperty->addAttribute(new Attribute($attributeName, $attributeArgs ?? []));
170-
}
171-
172167
return $schemaProperty;
173168
}
174169

src/SchemaGeneratorConfiguration.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use ApiPlatform\SchemaGenerator\AnnotationGenerator\PhpDocAnnotationGenerator;
1717
use ApiPlatform\SchemaGenerator\AttributeGenerator\ApiPlatformCoreAttributeGenerator;
18+
use ApiPlatform\SchemaGenerator\AttributeGenerator\ConfigurationAttributeGenerator;
1819
use ApiPlatform\SchemaGenerator\AttributeGenerator\ConstraintAttributeGenerator;
1920
use ApiPlatform\SchemaGenerator\AttributeGenerator\DoctrineOrmAttributeGenerator;
2021
use ApiPlatform\SchemaGenerator\AttributeGenerator\SerializerGroupsAttributeGenerator;
@@ -263,6 +264,8 @@ public function getConfigTreeBuilder(): TreeBuilder
263264
ApiPlatformCoreAttributeGenerator::class,
264265
ConstraintAttributeGenerator::class,
265266
SerializerGroupsAttributeGenerator::class,
267+
// Configuration attribute generator needs to be last to merge its attributes with previously generated ones.
268+
ConfigurationAttributeGenerator::class,
266269
])
267270
->scalarPrototype()->end()
268271
->end()

src/TypesGenerator.php

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
use ApiPlatform\SchemaGenerator\ClassMutator\ClassParentMutator;
2121
use ApiPlatform\SchemaGenerator\ClassMutator\ClassPropertiesAppender;
2222
use ApiPlatform\SchemaGenerator\ClassMutator\ClassPropertiesTypehintMutator;
23-
use ApiPlatform\SchemaGenerator\Model\Attribute;
2423
use ApiPlatform\SchemaGenerator\Model\Class_;
2524
use ApiPlatform\SchemaGenerator\Model\Use_;
2625
use ApiPlatform\SchemaGenerator\PropertyGenerator\PropertyGeneratorInterface;
@@ -246,17 +245,9 @@ private function buildClass(array $graphs, array $cardinalities, string $typeNam
246245
}
247246

248247
$typeConfig = $config['types'][$typeName] ?? null;
249-
$vocabConfig = $config['vocabularies'][$type->getGraph()->getUri()] ?? null;
250248
$parent = $typeConfig['parent'] ?? null;
251249
$class = new SchemaClass($typeName, $type, $parent);
252250
$class->operations = $typeConfig['operations'] ?? [];
253-
$attributes = array_merge($vocabConfig['attributes'] ?? [], $typeConfig['attributes'] ?? []);
254-
foreach ($attributes as $attributeName => $attributeArgs) {
255-
$class->addAttribute(new Attribute($attributeName, $attributeArgs ?? []));
256-
}
257-
foreach ($config['uses'] as $useName => $useArgs) {
258-
$class->addUse(new Use_($useName, $useArgs['alias'] ?? null));
259-
}
260251

261252
if ($class->isEnum()) {
262253
(new SchemaEnumClassMutator(
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\SchemaGenerator\Tests\AttributeGenerator;
15+
16+
use ApiPlatform\SchemaGenerator\AttributeGenerator\ConfigurationAttributeGenerator;
17+
use ApiPlatform\SchemaGenerator\Model\Attribute;
18+
use ApiPlatform\SchemaGenerator\Model\Use_;
19+
use ApiPlatform\SchemaGenerator\PhpTypeConverter;
20+
use ApiPlatform\SchemaGenerator\Schema\Model\Class_ as SchemaClass;
21+
use ApiPlatform\SchemaGenerator\Schema\Model\Property;
22+
use ApiPlatform\SchemaGenerator\SchemaGeneratorConfiguration;
23+
use EasyRdf\Graph as RdfGraph;
24+
use EasyRdf\Resource as RdfResource;
25+
use PHPUnit\Framework\TestCase;
26+
use Symfony\Component\String\Inflector\EnglishInflector;
27+
28+
class ConfigurationAttributeGeneratorTest extends TestCase
29+
{
30+
/**
31+
* @dataProvider provideGenerateClassAttributesCases
32+
*/
33+
public function testGenerateClassAttributes(SchemaClass $class, array $config, array $attributes): void
34+
{
35+
$this->assertEquals($attributes, $this->generator($config)->generateClassAttributes($class));
36+
}
37+
38+
public function provideGenerateClassAttributesCases(): \Generator
39+
{
40+
$class = new SchemaClass('Foo', new RdfResource('https://schema.org/Foo', new RdfGraph()));
41+
42+
yield 'no configuration' => [$class, [], []];
43+
44+
$class = new SchemaClass('Foo', new RdfResource('https://schema.org/Foo', new RdfGraph()));
45+
46+
yield 'type configuration' => [
47+
$class,
48+
['types' => ['Foo' => ['attributes' => ['ApiResource' => ['routePrefix' => '/prefix']]]]],
49+
[new Attribute('ApiResource', ['routePrefix' => '/prefix'])],
50+
];
51+
52+
$class = new SchemaClass('Foo', new RdfResource('https://schema.org/Foo', new RdfGraph(SchemaGeneratorConfiguration::SCHEMA_ORG_URI)));
53+
$expectedAttribute = new Attribute('ApiResource', ['routePrefix' => '/prefix']);
54+
$expectedAttribute->append = false;
55+
56+
yield 'vocab configuration' => [
57+
$class,
58+
['vocabularies' => [SchemaGeneratorConfiguration::SCHEMA_ORG_URI => ['attributes' => ['ApiResource' => ['routePrefix' => '/prefix']]]]],
59+
[$expectedAttribute],
60+
];
61+
62+
$class = new SchemaClass('Foo', new RdfResource('https://schema.org/Foo', new RdfGraph(SchemaGeneratorConfiguration::SCHEMA_ORG_URI)));
63+
64+
yield 'vocab and type configuration' => [
65+
$class,
66+
[
67+
'vocabularies' => [SchemaGeneratorConfiguration::SCHEMA_ORG_URI => ['attributes' => ['ApiResource' => ['routePrefix' => '/prefix']]]],
68+
'types' => ['Foo' => ['attributes' => ['ApiResource' => ['security' => "is_granted('ROLE_USER')"]]]],
69+
],
70+
[new Attribute('ApiResource', ['security' => "is_granted('ROLE_USER')"])],
71+
];
72+
}
73+
74+
/**
75+
* @dataProvider provideGeneratePropertyAttributesCases
76+
*/
77+
public function testGeneratePropertyAttributes(Property $property, array $config, array $attributes): void
78+
{
79+
$this->assertEquals($attributes, $this->generator($config)->generatePropertyAttributes($property, 'Res'));
80+
}
81+
82+
public function provideGeneratePropertyAttributesCases(): \Generator
83+
{
84+
$property = new Property('prop');
85+
86+
yield 'no configuration' => [$property, [], []];
87+
88+
$property = new Property('prop');
89+
90+
yield 'type configuration' => [
91+
$property,
92+
['types' => ['Res' => ['properties' => ['prop' => ['attributes' => ['ApiResource' => ['security' => "is_granted('ROLE_USER')"]]]]]]],
93+
[new Attribute('ApiResource', ['security' => "is_granted('ROLE_USER')"])],
94+
];
95+
}
96+
97+
/**
98+
* @dataProvider provideGenerateUsesCases
99+
*/
100+
public function testGenerateUses(SchemaClass $class, array $config, array $uses): void
101+
{
102+
$this->assertEquals($uses, $this->generator($config)->generateUses($class));
103+
}
104+
105+
public function provideGenerateUsesCases(): \Generator
106+
{
107+
$class = new SchemaClass('Foo', new RdfResource('https://schema.org/Foo', new RdfGraph()));
108+
109+
yield 'no configuration' => [$class, ['uses' => []], []];
110+
111+
$class = new SchemaClass('Foo', new RdfResource('https://schema.org/Foo', new RdfGraph()));
112+
113+
yield 'type configuration' => [
114+
$class,
115+
['uses' => ['Symfony\Component\Validator\Constraints' => ['alias' => 'Assert']]],
116+
[new Use_('Symfony\Component\Validator\Constraints', 'Assert')],
117+
];
118+
}
119+
120+
private function generator(array $config = []): ConfigurationAttributeGenerator
121+
{
122+
return new ConfigurationAttributeGenerator(
123+
new PhpTypeConverter(),
124+
new EnglishInflector(),
125+
$config,
126+
[],
127+
);
128+
}
129+
}

tests/Command/DumpConfigurationTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ interface: null
266266
- ApiPlatform\SchemaGenerator\AttributeGenerator\ApiPlatformCoreAttributeGenerator
267267
- ApiPlatform\SchemaGenerator\AttributeGenerator\ConstraintAttributeGenerator
268268
- ApiPlatform\SchemaGenerator\AttributeGenerator\SerializerGroupsAttributeGenerator
269+
- ApiPlatform\SchemaGenerator\AttributeGenerator\ConfigurationAttributeGenerator
269270
270271
# Directories for custom generator twig templates
271272
generatorTemplates: []

tests/Command/GenerateCommandTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,8 +499,8 @@ public function testActivityStreams(): void
499499
*
500500
* @see http://www.w3.org/ns/activitystreams#Page
501501
*/
502-
#[ApiResource(types: ['http://www.w3.org/ns/activitystreams#Page'], routePrefix: 'as')]
503502
#[ORM\Entity]
503+
#[ApiResource(types: ['http://www.w3.org/ns/activitystreams#Page'], routePrefix: 'as')]
504504
class Page extends Object_
505505
PHP
506506
, $page);

0 commit comments

Comments
 (0)