Skip to content

Commit 7cc3f8c

Browse files
committed
save
1 parent 7807301 commit 7cc3f8c

File tree

6 files changed

+142
-81
lines changed

6 files changed

+142
-81
lines changed

src/Hydra/JsonSchema/SchemaFactory.php

+46-14
Original file line numberDiff line numberDiff line change
@@ -112,37 +112,37 @@ public function buildSchema(string $className, string $format = 'jsonld', string
112112
$inputOrOutputClass = $this->findOutputClass($className, $type, $operation, $serializerContext);
113113
$serializerContext ??= $this->getSerializerContext($operation, $type);
114114
}
115+
115116
if (null === $inputOrOutputClass) {
116117
// input or output disabled
117118
return $this->schemaFactory->buildSchema($className, $format, $type, $operation, $schema, $serializerContext, $forceCollection);
118119
}
119120

120121
$schema = $this->schemaFactory->buildSchema($className, 'json', $type, $operation, $schema, $serializerContext, $forceCollection);
122+
$schema = $this->schemaFactory->buildSchema($className, 'jsonld', $type, $operation, $schema, [self::COMPUTE_REFERENCES => true] + $serializerContext, $forceCollection);
121123
$definitionName = $this->definitionNameFactory->create($className, $format, $className, $operation, $serializerContext);
122124
$definitions = $schema->getDefinitions();
123125
$prefix = $this->getSchemaUriPrefix($schema->getVersion());
124126
$collectionKey = $schema->getItemsDefinitionKey();
125127

126-
// Already computed
127-
if (!$collectionKey && isset($definitions[$definitionName])) {
128-
$schema['$ref'] = $prefix.$definitionName;
129-
130-
return $schema;
131-
}
132-
133128
$key = $schema->getRootDefinitionKey() ?? $collectionKey;
134-
135129
$name = Schema::TYPE_OUTPUT === $type ? self::ITEM_BASE_SCHEMA_NAME : self::ITEM_BASE_SCHEMA_OUTPUT_NAME;
136130
if (!isset($definitions[$name])) {
137131
$definitions[$name] = Schema::TYPE_OUTPUT === $type ? self::ITEM_BASE_SCHEMA_OUTPUT : self::ITEM_BASE_SCHEMA;
138132
}
139133

140-
$definitions[$definitionName] = [
141-
'allOf' => [
142-
['$ref' => $prefix.$name],
143-
['$ref' => $prefix.$key],
144-
],
145-
];
134+
if (!$collectionKey) {
135+
$schema['definitions'][$definitionName] = [
136+
'allOf' => [
137+
['$ref' => $prefix.$name],
138+
['$ref' => $prefix.$key],
139+
$definitions[$definitionName],
140+
],
141+
];
142+
$schema['$ref'] = $preifx . $definitionName;
143+
144+
return $schema;
145+
}
146146

147147
if (isset($definitions[$key]['description'])) {
148148
$definitions[$definitionName]['description'] = $definitions[$key]['description'];
@@ -273,4 +273,36 @@ public function setSchemaFactory(SchemaFactoryInterface $schemaFactory): void
273273
$this->schemaFactory->setSchemaFactory($schemaFactory);
274274
}
275275
}
276+
277+
private function collectRefs(array|\ArrayObject $baseFormatSchema, $prefix)
278+
{
279+
if (!$key = $this->getSubSchemaKey($baseFormatSchema)) {
280+
return null;
281+
}
282+
283+
foreach ($baseFormatSchema[$key] as $k => $s) {
284+
if (isset($s['$ref'])) {
285+
dd($s['$ref'], $prefix);
286+
}
287+
288+
if (!$s instanceof \ArrayObject) {
289+
continue;
290+
}
291+
292+
$this->collectRefs($s, $prefix);
293+
}
294+
295+
return [];
296+
}
297+
298+
private function getSubSchemaKey(array|\ArrayObject $subSchema): ?string
299+
{
300+
foreach (['properties', 'items', 'allOf', 'anyOf', 'oneOf'] as $key) {
301+
if (isset($subSchema[$key])) {
302+
return $key;
303+
}
304+
}
305+
306+
return null;
307+
}
276308
}

src/JsonSchema/Metadata/Property/Factory/SchemaPropertyMetadataFactory.php

+2-4
Original file line numberDiff line numberDiff line change
@@ -283,10 +283,8 @@ private function getClassType(?string $className, bool $nullable, ?bool $readabl
283283
];
284284
}
285285

286-
if ($className && !$isResourceClass) {
287-
return ['type' => 'object'];
288-
}
289-
286+
// When this is set, we compute the schema at SchemaFactory::buildPropertySchema as it
287+
// will end up being a $ref to another class schema, we don't have enough informations here
290288
return ['type' => Schema::UNKNOWN_TYPE];
291289
}
292290

src/JsonSchema/SchemaFactory.php

+39-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<?php
2-
32
/*
43
* This file is part of the API Platform project.
54
*
@@ -22,6 +21,7 @@
2221
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
2322
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
2423
use ApiPlatform\Metadata\ResourceClassResolverInterface;
24+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue5793\BagOfTests;
2525
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
2626
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
2727

@@ -37,7 +37,6 @@ final class SchemaFactory implements SchemaFactoryInterface, SchemaFactoryAwareI
3737

3838
private ?SchemaFactoryInterface $schemaFactory = null;
3939
// Edge case where the related resource is not readable (for example: NotExposed) but we have groups to read the whole related object
40-
public const FORCE_SUBSCHEMA = '_api_subschema_force_readable_link';
4140
public const OPENAPI_DEFINITION_NAME = 'openapi_definition_name';
4241

4342
public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory, private readonly ?NameConverterInterface $nameConverter = null, ?ResourceClassResolverInterface $resourceClassResolver = null, ?array $distinctFormats = null, private ?DefinitionNameFactoryInterface $definitionNameFactory = null)
@@ -104,7 +103,7 @@ public function buildSchema(string $className, string $format = 'json', string $
104103
/** @var \ArrayObject<string, mixed> $definition */
105104
$definition = new \ArrayObject(['type' => 'object']);
106105
$definitions[$definitionName] = $definition;
107-
if ($description = $operation->getDescription()) {
106+
if ($description = $operation?->getDescription()) {
108107
$definition['description'] = $description;
109108
}
110109

@@ -114,7 +113,7 @@ public function buildSchema(string $className, string $format = 'json', string $
114113
$definition['additionalProperties'] = false;
115114
}
116115

117-
// see https://github.com/json-schema-org/json-schema-spec/pull/737
116+
// see https://github.com/json-schema-org/json-schema-speMc/pull/737
118117
if (Schema::VERSION_SWAGGER !== $version && $operation && $operation->getDeprecationReason()) {
119118
$definition['deprecated'] = true;
120119
}
@@ -128,6 +127,7 @@ public function buildSchema(string $className, string $format = 'json', string $
128127
$options = ['schema_type' => $type] + $this->getFactoryOptions($serializerContext, $validationGroups, $operation instanceof HttpOperation ? $operation : null);
129128
foreach ($this->propertyNameCollectionFactory->create($inputOrOutputClass, $options) as $propertyName) {
130129
$propertyMetadata = $this->propertyMetadataFactory->create($inputOrOutputClass, $propertyName, $options);
130+
131131
if (!$propertyMetadata->isReadable() && !$propertyMetadata->isWritable()) {
132132
continue;
133133
}
@@ -137,13 +137,13 @@ public function buildSchema(string $className, string $format = 'json', string $
137137
$definition['required'][] = $normalizedPropertyName;
138138
}
139139

140-
$this->buildPropertySchema($schema, $definitionName, $normalizedPropertyName, $propertyMetadata, $serializerContext, $format, $type);
140+
$this->buildPropertySchema($schema, $definitionName, $normalizedPropertyName, $propertyMetadata, $serializerContext, $format, $type, $inputOrOutputClass === BagOfTests::class);
141141
}
142142

143143
return $schema;
144144
}
145145

146-
private function buildPropertySchema(Schema $schema, string $definitionName, string $normalizedPropertyName, ApiProperty $propertyMetadata, array $serializerContext, string $format, string $parentType): void
146+
private function buildPropertySchema(Schema $schema, string $definitionName, string $normalizedPropertyName, ApiProperty $propertyMetadata, array $serializerContext, string $format, string $parentType, $t = null): void
147147
{
148148
$version = $schema->getVersion();
149149
if (Schema::VERSION_SWAGGER === $version || Schema::VERSION_OPENAPI === $version) {
@@ -165,9 +165,13 @@ private function buildPropertySchema(Schema $schema, string $definitionName, str
165165
$extraProperties = $propertyMetadata->getExtraProperties() ?? [];
166166
// see AttributePropertyMetadataFactory
167167
if (true === ($extraProperties[SchemaPropertyMetadataFactory::JSON_SCHEMA_USER_DEFINED] ?? false)) {
168+
if (true === $serializerContext[self::COMPUTE_REFERENCES] ?? null) {
169+
return;
170+
171+
}
172+
168173
// schema seems to have been declared by the user: do not override nor complete user value
169174
$schema->getDefinitions()[$definitionName]['properties'][$normalizedPropertyName] = new \ArrayObject($propertySchema);
170-
171175
return;
172176
}
173177

@@ -178,14 +182,11 @@ private function buildPropertySchema(Schema $schema, string $definitionName, str
178182
// or if property schema is already fully defined (type=string + format || enum)
179183
$propertySchemaType = $propertySchema['type'] ?? false;
180184

181-
if (Schema::UNKNOWN_TYPE === $propertySchemaType && 'propertyCollectionIriOnlyRelation' === $normalizedPropertyName) {
182-
dd($propertySchema, $propertyMetadata);
183-
}
184-
185185
$isUnknown = Schema::UNKNOWN_TYPE === $propertySchemaType
186186
|| ('array' === $propertySchemaType && Schema::UNKNOWN_TYPE === ($propertySchema['items']['type'] ?? null))
187187
|| ('object' === $propertySchemaType && Schema::UNKNOWN_TYPE === ($propertySchema['additionalProperties']['type'] ?? null));
188188

189+
// Scalar properties
189190
if (
190191
!$isUnknown && (
191192
[] === $types
@@ -194,6 +195,10 @@ private function buildPropertySchema(Schema $schema, string $definitionName, str
194195
|| ($propertySchema['format'] ?? $propertySchema['enum'] ?? false)
195196
)
196197
) {
198+
if (true === $serializerContext[self::COMPUTE_REFERENCES]) {
199+
return;
200+
}
201+
197202
if (isset($propertySchema['$ref'])) {
198203
unset($propertySchema['type']);
199204
}
@@ -208,6 +213,7 @@ private function buildPropertySchema(Schema $schema, string $definitionName, str
208213
$version = $schema->getVersion();
209214
$refs = [];
210215
$isNullable = null;
216+
$hasClassName = false;
211217

212218
foreach ($types as $type) {
213219
$subSchema = new Schema($version);
@@ -225,6 +231,7 @@ private function buildPropertySchema(Schema $schema, string $definitionName, str
225231
continue;
226232
}
227233

234+
$hasClassName = true;
228235
$subSchemaFactory = $this->schemaFactory ?: $this;
229236
$subSchema = $subSchemaFactory->buildSchema($className, $format, $parentType, null, $subSchema, $serializerContext + [self::FORCE_SUBSCHEMA => true], false);
230237
if (!isset($subSchema['$ref'])) {
@@ -254,7 +261,12 @@ private function buildPropertySchema(Schema $schema, string $definitionName, str
254261
$refs[] = ['type' => 'null'];
255262
}
256263

257-
if (($c = \count($refs)) > 1) {
264+
if (!$hasClassName && (true === $serializerContext[self::COMPUTE_REFERENCES] ?? null)) {
265+
return;
266+
}
267+
268+
$c = \count($refs);
269+
if ($c > 1) {
258270
$propertySchema['anyOf'] = $refs;
259271
unset($propertySchema['type']);
260272
} elseif (1 === $c) {
@@ -310,4 +322,19 @@ public function setSchemaFactory(SchemaFactoryInterface $schemaFactory): void
310322
{
311323
$this->schemaFactory = $schemaFactory;
312324
}
325+
326+
// private function isObject($types): bool
327+
// {
328+
// foreach ($types as $type) {
329+
// if ($type->getClassName()) {
330+
// return true;
331+
// }
332+
//
333+
// if ($type->getCollectionValueTypes() && ($r = $this->isObject($type->getCollectionValueTypes()))) {
334+
// return $r;
335+
// }
336+
// }
337+
//
338+
// return false;
339+
// }
313340
}

src/JsonSchema/SchemaFactoryInterface.php

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
*/
2323
interface SchemaFactoryInterface
2424
{
25+
public const FORCE_SUBSCHEMA = '_api_subschema_force_readable_link';
26+
public const SUBSCHEMA_FORMAT = '_api_subschema_format';
27+
public const COMPUTE_REFERENCES = '_api_subschema_compute_references';
28+
2529
/**
2630
* Builds the JSON Schema document corresponding to the given PHP class.
2731
*/

src/Metadata/Property/Factory/SerializerPropertyMetadataFactory.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,8 @@ private function transformReadWrite(ApiProperty $propertyMetadata, string $resou
8989
}
9090

9191
$serializerAttributeMetadata = $this->getSerializerAttributeMetadata($resourceClass, $propertyName);
92-
$groups = $serializerAttributeMetadata ? $serializerAttributeMetadata->getGroups() : [];
93-
$ignored = $serializerAttributeMetadata && $serializerAttributeMetadata->isIgnored();
92+
$groups = $serializerAttributeMetadata?->getGroups() ?? [];
93+
$ignored = $serializerAttributeMetadata?->isIgnored() ?? false;
9494

9595
if (false !== $propertyMetadata->isReadable()) {
9696
$propertyMetadata = $propertyMetadata->withReadable(!$ignored && (null === $normalizationGroups || array_intersect($normalizationGroups, $groups)));

0 commit comments

Comments
 (0)