Skip to content

Commit 5df7d1b

Browse files
feat: custom attributes (#366)
1 parent cc34619 commit 5df7d1b

21 files changed

+189
-126
lines changed

phpstan.neon

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ parameters:
1010
relationTableName: ?string,
1111
cardinality: string,
1212
ormColumn: array<string, string|string[]>,
13-
apiPropertyArguments: array{},
1413
groups: string[],
1514
mappedBy: ?string,
1615
inversedBy: ?string,
@@ -20,7 +19,8 @@ parameters:
2019
required: ?boolean,
2120
unique: boolean,
2221
embedded: boolean,
23-
columnPrefix: false|string
22+
columnPrefix: false|string,
23+
attributes: array<string, (int|bool|null|string|string[]|string[][]|\Nette\PhpGenerator\Literal)[]|null>
2424
}
2525
'''
2626
TypeConfiguration: '''
@@ -30,18 +30,17 @@ parameters:
3030
abstract: ?boolean,
3131
embeddable: boolean,
3232
namespaces: array{class: ?string, interface: ?string},
33-
doctrine: array{attributes: array<string, (int|bool|null|string|string[]|string[][]|\Nette\PhpGenerator\Literal)[]>},
33+
attributes: array<string, (int|bool|null|string|string[]|string[][]|\Nette\PhpGenerator\Literal)[]|null>,
3434
parent: false|string,
3535
guessFrom: string,
3636
operations: array<string, array<string, string[]>>,
37-
apiResourceArguments: array{},
3837
allProperties: boolean,
3938
properties: array<string, PropertyConfiguration>
4039
}
4140
'''
4241
Configuration: '''
4342
array{
44-
vocabularies: array{uri: string, format: string, allTypes: ?boolean, apiResourceArguments: array{}}[],
43+
vocabularies: array{uri: string, format: string, allTypes: ?boolean, attributes: array<string, (int|bool|null|string|string[]|string[][]|\Nette\PhpGenerator\Literal)[]|null>}[],
4544
vocabularyNamespace: string,
4645
relations: string[],
4746
debug: boolean,
@@ -50,6 +49,7 @@ parameters:
5049
checkIsGoodRelations: boolean,
5150
header: ?string,
5251
namespaces: array{prefix: ?string, entity: string, enum: string, interface: string},
52+
uses: array<string, array{alias: ?string}>,
5353
doctrine: array{useCollection: boolean, resolveTargetEntityConfigPath: ?string, resolveTargetEntityConfigType: 'XML'|'yaml', inheritanceAttributes: array<string, (int|bool|null|string|string[]|string[][]|\Nette\PhpGenerator\Literal)[]>},
5454
validator: array{assertType: boolean},
5555
author: false|string,

src/AttributeGenerator/ApiPlatformCoreAttributeGenerator.php

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,6 @@ public function generateClassAttributes(Class_ $class): array
4646
if ($class->rdfType()) {
4747
$arguments['iri'] = $class->rdfType();
4848
}
49-
if ($class->apiResourceArguments) {
50-
$arguments = array_merge($arguments, $class->apiResourceArguments);
51-
}
5249

5350
if ($class->operations) {
5451
$operations = $this->validateClassOperations($class->operations);
@@ -98,10 +95,6 @@ public function generatePropertyAttributes(Property $property, string $className
9895
$arguments['iri'] = $property->rdfType();
9996
}
10097

101-
if ($property->apiPropertyArguments) {
102-
$arguments = array_merge($arguments, $property->apiPropertyArguments);
103-
}
104-
10598
return $property->isCustom ? [] : [new Attribute('ApiProperty', $arguments)];
10699
}
107100

src/AttributeGenerator/DoctrineMongoDBAttributeGenerator.php

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,6 @@ final class DoctrineMongoDBAttributeGenerator extends AbstractAttributeGenerator
3131
*/
3232
public function generateClassAttributes(Class_ $class): array
3333
{
34-
if ($doctrineAttributes = isset($this->config['types'][$class->name()]) ? $this->config['types'][$class->name()]['doctrine']['attributes'] : false) {
35-
$attributes = [];
36-
foreach ($doctrineAttributes as $attributeName => $attributeArgs) {
37-
$attributes[] = new Attribute($attributeName, $attributeArgs);
38-
}
39-
40-
return $attributes;
41-
}
42-
4334
if ($class->isEnum()) {
4435
return [];
4536
}

src/AttributeGenerator/DoctrineOrmAttributeGenerator.php

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,6 @@ final class DoctrineOrmAttributeGenerator extends AbstractAttributeGenerator
4242
*/
4343
public function generateClassAttributes(Class_ $class): array
4444
{
45-
if ($doctrineAttributes = (isset($this->config['types'][$class->name()]) ? $this->config['types'][$class->name()]['doctrine']['attributes'] : false)) {
46-
$attributes = [];
47-
foreach ($doctrineAttributes as $attributeName => $attributeArgs) {
48-
$attributes[] = new Attribute($attributeName, $attributeArgs);
49-
}
50-
51-
return $attributes;
52-
}
53-
5445
if ($class->isEnum()) {
5546
return [];
5647
}

src/Model/AddAttributeTrait.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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\Model;
15+
16+
trait AddAttributeTrait
17+
{
18+
public function addAttribute(Attribute $attribute): self
19+
{
20+
if (!\in_array($attribute, $this->attributes, true)) {
21+
if (empty(array_filter(
22+
$this->attributes,
23+
fn (Attribute $attr) => $attr->name() === $attribute->name()
24+
))) {
25+
$this->attributes[] = $attribute;
26+
} else {
27+
$this->attributes = array_map(
28+
fn (Attribute $attr) => $attr->name() === $attribute->name()
29+
? new Attribute($attr->name(), array_merge($attribute->args(), $attr->args()))
30+
: $attr,
31+
$this->attributes
32+
);
33+
}
34+
}
35+
36+
return $this;
37+
}
38+
}

src/Model/Class_.php

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
abstract class Class_
2424
{
25+
use AddAttributeTrait;
2526
use ResolveNameTrait;
2627

2728
protected string $name;
@@ -44,8 +45,6 @@ abstract class Class_
4445
public bool $isAbstract = false;
4546
public bool $hasChild = false;
4647
public bool $isEmbeddable = false;
47-
// @phpstan-ignore-next-line dynamic array
48-
public array $apiResourceArguments = [];
4948
/** @var array<string, array<string, string[]|null>> */
5049
public array $operations = [];
5150

@@ -168,15 +167,6 @@ public function addUse(Use_ $use): self
168167
return $this;
169168
}
170169

171-
public function addAttribute(Attribute $attribute): self
172-
{
173-
if (!\in_array($attribute, $this->attributes, true)) {
174-
$this->attributes[] = $attribute;
175-
}
176-
177-
return $this;
178-
}
179-
180170
public function addAnnotation(string $annotation): self
181171
{
182172
if ('' === $annotation || !\in_array($annotation, $this->annotations, true)) {

src/Model/Property.php

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
abstract class Property
2222
{
23+
use AddAttributeTrait;
2324
use ResolveNameTrait;
2425

2526
private string $name;
@@ -51,8 +52,6 @@ abstract class Property
5152
public ?string $adderRemoverTypeHint = null;
5253
/** @var string[] */
5354
public array $groups = [];
54-
// @phpstan-ignore-next-line dynamic array
55-
public array $apiPropertyArguments = [];
5655
/** @var Attribute[] */
5756
private array $attributes = [];
5857
/** @var string[] */
@@ -80,15 +79,6 @@ abstract public function description(): ?string;
8079

8180
abstract public function rdfType(): ?string;
8281

83-
public function addAttribute(Attribute $attribute): self
84-
{
85-
if (!\in_array($attribute, $this->attributes, true)) {
86-
$this->attributes[] = $attribute;
87-
}
88-
89-
return $this;
90-
}
91-
9282
public function addAnnotation(string $annotation): self
9383
{
9484
if ('' === $annotation || !\in_array($annotation, $this->annotations, true)) {

src/OpenApi/ClassGenerator.php

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

227+
foreach ($config['uses'] as $useName => $useArgs) {
228+
$class->addUse(new Use_($useName, $useArgs['alias'] ?? null));
229+
}
230+
227231
foreach ($schema->properties as $propertyName => $schemaProperty) {
228232
\assert($schemaProperty instanceof Schema);
229233
$property = ($this->propertyGenerator)($propertyName, $config, $class, ['schema' => $schema, 'property' => $schemaProperty]);

src/Schema/PropertyGenerator/PropertyGenerator.php

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

1616
use ApiPlatform\SchemaGenerator\CardinalitiesExtractor;
1717
use ApiPlatform\SchemaGenerator\GoodRelationsBridge;
18+
use ApiPlatform\SchemaGenerator\Model\Attribute;
1819
use ApiPlatform\SchemaGenerator\Model\Class_;
1920
use ApiPlatform\SchemaGenerator\Model\Property;
2021
use ApiPlatform\SchemaGenerator\PhpTypeConverterInterface;
@@ -163,7 +164,10 @@ public function __invoke(string $name, array $config, Class_ $class, array $cont
163164
$schemaProperty->mappedBy = $propertyConfig['mappedBy'] ?? null;
164165
$schemaProperty->inversedBy = $propertyConfig['inversedBy'] ?? null;
165166
$schemaProperty->groups = $propertyConfig['groups'] ?? [];
166-
$schemaProperty->apiPropertyArguments = $propertyConfig['apiPropertyArguments'] ?? [];
167+
168+
foreach ($propertyConfig['attributes'] ?? [] as $attributeName => $attributeArgs) {
169+
$schemaProperty->addAttribute(new Attribute($attributeName, $attributeArgs ?? []));
170+
}
167171

168172
return $schemaProperty;
169173
}

src/SchemaGeneratorConfiguration.php

Lines changed: 28 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use ApiPlatform\SchemaGenerator\AttributeGenerator\ConstraintAttributeGenerator;
1919
use ApiPlatform\SchemaGenerator\AttributeGenerator\DoctrineOrmAttributeGenerator;
2020
use ApiPlatform\SchemaGenerator\AttributeGenerator\SerializerGroupsAttributeGenerator;
21+
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
2122
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
2223
use Symfony\Component\Config\Definition\ConfigurationInterface;
2324

@@ -55,6 +56,16 @@ public function getConfigTreeBuilder(): TreeBuilder
5556
? array_reduce(array_values($nodeConfig), fn (array $map, array $v) => $map + $v, [])
5657
: $nodeConfig;
5758

59+
// @phpstan-ignore-next-line node is not null
60+
$attributesNode = fn () => (new NodeBuilder())
61+
->arrayNode('attributes')
62+
->info('Attributes (merged with generated attributes)')
63+
->variablePrototype()->end()
64+
->beforeNormalization()
65+
->ifArray()
66+
->then($transformOmap)
67+
->end();
68+
5869
$treeBuilder = new TreeBuilder('config');
5970

6071
$treeBuilder
@@ -79,14 +90,7 @@ public function getConfigTreeBuilder(): TreeBuilder
7990
->scalarNode('uri')->info('RDF vocabulary to use')->example('https://schema.org/version/latest/schemaorg-current-https.rdf')->end()
8091
->scalarNode('format')->defaultNull()->info('RDF vocabulary format')->example('rdfxml')->end()
8192
->booleanNode('allTypes')->defaultNull()->info('Generate all types for this vocabulary, even if an explicit configuration exists. If allTypes is enabled globally, it can be disabled for this particular vocabulary')->end()
82-
->arrayNode('apiResourceArguments')
83-
->info('Arguments to add to ApiResource for all the classes generated for this vocabulary')
84-
->variablePrototype()->end()
85-
->beforeNormalization()
86-
->ifArray()
87-
->then($transformOmap)
88-
->end()
89-
->end()
93+
->append($attributesNode())
9094
->end()
9195
->end()
9296
->end()
@@ -121,6 +125,16 @@ public function getConfigTreeBuilder(): TreeBuilder
121125
->scalarNode('interface')->defaultValue("{$namespacePrefix}Model")->info('The namespace of the generated interfaces')->example('App\Model')->end()
122126
->end()
123127
->end()
128+
->arrayNode('uses')
129+
->info('Custom uses (for instance if you use a custom attribute)')
130+
->useAttributeAsKey('name')
131+
->arrayPrototype()
132+
->children()
133+
->scalarNode('name')->cannotBeEmpty()->info('Name of this use')->example('App\Attributes\MyAttribute')->end()
134+
->scalarNode('alias')->defaultNull()->info('The alias to use for this use')->end()
135+
->end()
136+
->end()
137+
->end()
124138
->arrayNode('doctrine')
125139
->addDefaultsIfNotSet()
126140
->info('Doctrine')
@@ -131,6 +145,10 @@ public function getConfigTreeBuilder(): TreeBuilder
131145
->arrayNode('inheritanceAttributes')
132146
->info('Doctrine inheritance attributes (if set, no other attributes are generated)')
133147
->variablePrototype()->end()
148+
->beforeNormalization()
149+
->ifArray()
150+
->then($transformOmap)
151+
->end()
134152
->end()
135153
->end()
136154
->end()
@@ -178,29 +196,13 @@ public function getConfigTreeBuilder(): TreeBuilder
178196
->scalarNode('interface')->defaultNull()->info('The namespace for the generated interface (override any other defined namespace)')->end()
179197
->end()
180198
->end()
181-
->arrayNode('doctrine')
182-
->addDefaultsIfNotSet()
183-
->children()
184-
->arrayNode('attributes')
185-
->info('Doctrine attributes (if set, no other attributes are generated)')
186-
->variablePrototype()->end()
187-
->end()
188-
->end()
189-
->end()
199+
->append($attributesNode())
190200
->scalarNode('parent')->defaultFalse()->info('The parent class, set to false for a top level class')->end()
191201
->scalarNode('guessFrom')->defaultValue('Thing')->info('If declaring a custom class, this will be the class from which properties type will be guessed')->end()
192202
->arrayNode('operations')
193203
->info('Operations for the class')
194204
->variablePrototype()->end()
195205
->end()
196-
->arrayNode('apiResourceArguments')
197-
->info('Arguments to add to ApiResource (for instance security)')
198-
->variablePrototype()->end()
199-
->beforeNormalization()
200-
->ifArray()
201-
->then($transformOmap)
202-
->end()
203-
->end()
204206
->booleanNode('allProperties')->defaultFalse()->info('Import all existing properties')->end()
205207
->arrayNode('properties')
206208
->info('Properties of this type to use')
@@ -226,14 +228,6 @@ public function getConfigTreeBuilder(): TreeBuilder
226228
->example('{type: "decimal", precision: 5, scale: 1, options: {comment: "my comment"}}')
227229
->variablePrototype()->end()
228230
->end()
229-
->arrayNode('apiPropertyArguments')
230-
->info('Arguments to add to ApiProperty (for instance security)')
231-
->variablePrototype()->end()
232-
->beforeNormalization()
233-
->ifArray()
234-
->then($transformOmap)
235-
->end()
236-
->end()
237231
->arrayNode('groups')
238232
->info('Symfony Serialization Groups')
239233
->scalarPrototype()->end()
@@ -247,6 +241,7 @@ public function getConfigTreeBuilder(): TreeBuilder
247241
->booleanNode('unique')->defaultFalse()->info('The property unique')->end()
248242
->booleanNode('embedded')->defaultFalse()->info('Is the property embedded?')->end()
249243
->scalarNode('columnPrefix')->defaultFalse()->info('The property columnPrefix')->end()
244+
->append($attributesNode())
250245
->end()
251246
->end()
252247
->end()

0 commit comments

Comments
 (0)