1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Utils for generating schemas. |
4
|
|
|
*/ |
5
|
|
|
|
6
|
|
|
namespace Graviton\SchemaBundle; |
7
|
|
|
|
8
|
|
|
use Doctrine\Common\Cache\CacheProvider; |
9
|
|
|
use Graviton\I18nBundle\Document\TranslatableDocumentInterface; |
10
|
|
|
use Graviton\I18nBundle\Document\Language; |
11
|
|
|
use Graviton\I18nBundle\Repository\LanguageRepository; |
12
|
|
|
use Graviton\RestBundle\Model\DocumentModel; |
13
|
|
|
use Graviton\SchemaBundle\Constraint\ConstraintBuilder; |
14
|
|
|
use Graviton\SchemaBundle\Document\Schema; |
15
|
|
|
use Graviton\SchemaBundle\Document\SchemaAdditionalProperties; |
16
|
|
|
use Graviton\SchemaBundle\Document\SchemaType; |
17
|
|
|
use Graviton\SchemaBundle\Service\RepositoryFactory; |
18
|
|
|
use JMS\Serializer\Serializer; |
19
|
|
|
use Metadata\MetadataFactoryInterface as SerializerMetadataFactoryInterface; |
20
|
|
|
use Symfony\Component\Routing\RouterInterface; |
21
|
|
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Utils for generating schemas. |
25
|
|
|
* |
26
|
|
|
* @author List of contributors <https://github.com/libgraviton/graviton/graphs/contributors> |
27
|
|
|
* @license http://opensource.org/licenses/gpl-license.php GNU Public License |
28
|
|
|
* @link http://swisscom.ch |
29
|
|
|
*/ |
30
|
|
|
class SchemaUtils |
31
|
|
|
{ |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* language repository |
35
|
|
|
* |
36
|
|
|
* @var LanguageRepository repository |
37
|
|
|
*/ |
38
|
|
|
private $languageRepository; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* router |
42
|
|
|
* |
43
|
|
|
* @var RouterInterface router |
44
|
|
|
*/ |
45
|
|
|
private $router; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* serializer |
49
|
|
|
* |
50
|
|
|
* @var Serializer |
51
|
|
|
*/ |
52
|
|
|
private $serializer; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* mapping service names => route names |
56
|
|
|
* |
57
|
|
|
* @var array service mapping |
58
|
|
|
*/ |
59
|
|
|
private $extrefServiceMapping; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* event map |
63
|
|
|
* |
64
|
|
|
* @var array event map |
65
|
|
|
*/ |
66
|
|
|
private $eventMap; |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* @var array [document class => [field name -> exposed name]] |
70
|
|
|
*/ |
71
|
|
|
private $documentFieldNames; |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* @var string |
75
|
|
|
*/ |
76
|
|
|
private $defaultLocale; |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* @var RepositoryFactory |
80
|
|
|
*/ |
81
|
|
|
private $repositoryFactory; |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* @var SerializerMetadataFactoryInterface |
85
|
|
|
*/ |
86
|
|
|
private $serializerMetadataFactory; |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* @var CacheProvider |
90
|
|
|
*/ |
91
|
|
|
private $cache; |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* @var string |
95
|
|
|
*/ |
96
|
|
|
private $cacheInvalidationMapKey; |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* @var ConstraintBuilder |
100
|
|
|
*/ |
101
|
|
|
private $constraintBuilder; |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Constructor |
105
|
|
|
* |
106
|
|
|
* @param RepositoryFactory $repositoryFactory Create repos from model class names |
107
|
|
|
* @param SerializerMetadataFactoryInterface $serializerMetadataFactory Serializer metadata factory |
108
|
|
|
* @param LanguageRepository $languageRepository repository |
109
|
|
|
* @param RouterInterface $router router |
110
|
|
|
* @param Serializer $serializer serializer |
111
|
|
|
* @param array $extrefServiceMapping Extref service mapping |
112
|
|
|
* @param array $eventMap eventmap |
113
|
|
|
* @param array $documentFieldNames Document field names |
114
|
|
|
* @param string $defaultLocale Default Language |
115
|
|
|
* @param ConstraintBuilder $constraintBuilder Constraint builder |
116
|
|
|
* @param CacheProvider $cache Doctrine cache provider |
117
|
|
|
* @param string $cacheInvalidationMapKey Cache invalidation map cache key |
118
|
|
|
*/ |
119
|
6 |
|
public function __construct( |
120
|
|
|
RepositoryFactory $repositoryFactory, |
121
|
|
|
SerializerMetadataFactoryInterface $serializerMetadataFactory, |
122
|
|
|
LanguageRepository $languageRepository, |
123
|
|
|
RouterInterface $router, |
124
|
|
|
Serializer $serializer, |
125
|
|
|
array $extrefServiceMapping, |
126
|
|
|
array $eventMap, |
127
|
|
|
array $documentFieldNames, |
128
|
|
|
$defaultLocale, |
129
|
|
|
ConstraintBuilder $constraintBuilder, |
130
|
|
|
CacheProvider $cache, |
131
|
|
|
$cacheInvalidationMapKey |
132
|
|
|
) { |
133
|
6 |
|
$this->repositoryFactory = $repositoryFactory; |
134
|
6 |
|
$this->serializerMetadataFactory = $serializerMetadataFactory; |
135
|
6 |
|
$this->languageRepository = $languageRepository; |
136
|
6 |
|
$this->router = $router; |
137
|
6 |
|
$this->serializer = $serializer; |
138
|
6 |
|
$this->extrefServiceMapping = $extrefServiceMapping; |
139
|
6 |
|
$this->eventMap = $eventMap; |
140
|
6 |
|
$this->documentFieldNames = $documentFieldNames; |
141
|
6 |
|
$this->defaultLocale = $defaultLocale; |
142
|
6 |
|
$this->constraintBuilder = $constraintBuilder; |
143
|
6 |
|
$this->cache = $cache; |
144
|
6 |
|
$this->cacheInvalidationMapKey = $cacheInvalidationMapKey; |
145
|
6 |
|
} |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* get schema for an array of models |
149
|
|
|
* |
150
|
|
|
* @param string $modelName name of model |
151
|
|
|
* @param DocumentModel $model model |
152
|
|
|
* |
153
|
|
|
* @return Schema |
154
|
|
|
*/ |
155
|
|
|
public function getCollectionSchema($modelName, DocumentModel $model) |
156
|
|
|
{ |
157
|
|
|
$collectionSchema = new Schema; |
158
|
|
|
$collectionSchema->setTitle(sprintf('Array of %s objects', $modelName)); |
159
|
|
|
$collectionSchema->setType('array'); |
160
|
|
|
|
161
|
|
|
$collectionSchema->setItems($this->getModelSchema($modelName, $model)); |
|
|
|
|
162
|
|
|
|
163
|
|
|
return $collectionSchema; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* return the schema for a given route |
168
|
|
|
* |
169
|
|
|
* @param string $modelName name of mode to generate schema for |
170
|
|
|
* @param DocumentModel $model model to generate schema for |
171
|
|
|
* @param boolean $online if we are online and have access to mongodb during this build |
172
|
|
|
* @param boolean $internal if true, we generate the schema for internal validation use |
173
|
|
|
* @param boolean $serialized if true, it will serialize the Schema object and return a \stdClass instead |
174
|
|
|
* |
175
|
|
|
* @return Schema|\stdClass Either a Schema instance or serialized as \stdClass if $serialized is true |
176
|
|
|
*/ |
177
|
4 |
|
public function getModelSchema( |
178
|
|
|
$modelName, |
179
|
|
|
DocumentModel $model, |
180
|
|
|
$online = true, |
181
|
|
|
$internal = false, |
182
|
|
|
$serialized = false |
183
|
|
|
) { |
184
|
|
|
|
185
|
4 |
|
$cacheKey = sprintf( |
186
|
4 |
|
'schema.%s.%s.%s.%s', |
187
|
4 |
|
$model->getEntityClass(), |
188
|
4 |
|
(string) $online, |
189
|
4 |
|
(string) $internal, |
190
|
3 |
|
(string) $serialized |
191
|
2 |
|
); |
192
|
|
|
|
193
|
4 |
|
if ($this->cache->contains($cacheKey)) { |
194
|
2 |
|
return $this->cache->fetch($cacheKey); |
195
|
|
|
} |
196
|
|
|
|
197
|
2 |
|
$invalidateCacheMap = []; |
198
|
2 |
|
if ($this->cache->contains($this->cacheInvalidationMapKey)) { |
199
|
|
|
$invalidateCacheMap = $this->cache->fetch($this->cacheInvalidationMapKey); |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
// build up schema data |
203
|
2 |
|
$schema = new Schema; |
204
|
|
|
|
205
|
2 |
|
if (!empty($model->getTitle())) { |
206
|
|
|
$schema->setTitle($model->getTitle()); |
207
|
|
|
} else { |
208
|
2 |
|
if (!is_null($modelName)) { |
209
|
2 |
|
$schema->setTitle(ucfirst($modelName)); |
210
|
1 |
|
} else { |
211
|
2 |
|
$reflection = new \ReflectionClass($model); |
212
|
2 |
|
$schema->setTitle(ucfirst($reflection->getShortName())); |
213
|
|
|
} |
214
|
|
|
} |
215
|
|
|
|
216
|
2 |
|
$schema->setDescription($model->getDescription()); |
217
|
2 |
|
$schema->setDocumentClass($model->getDocumentClass()); |
|
|
|
|
218
|
2 |
|
$schema->setType('object'); |
219
|
|
|
|
220
|
|
|
// grab schema info from model |
221
|
2 |
|
$repo = $model->getRepository(); |
222
|
2 |
|
$meta = $repo->getClassMetadata(); |
223
|
|
|
|
224
|
|
|
// Init sub searchable fields |
225
|
2 |
|
$subSearchableFields = array(); |
226
|
|
|
|
227
|
|
|
// look for translatables in document class |
228
|
2 |
|
$documentReflection = new \ReflectionClass($repo->getClassName()); |
229
|
2 |
|
if ($documentReflection->implementsInterface('Graviton\I18nBundle\Document\TranslatableDocumentInterface')) { |
230
|
|
|
/** @var TranslatableDocumentInterface $documentInstance */ |
231
|
2 |
|
$documentInstance = $documentReflection->newInstanceWithoutConstructor(); |
232
|
2 |
|
$translatableFields = array_merge( |
233
|
2 |
|
$documentInstance->getTranslatableFields(), |
234
|
2 |
|
$documentInstance->getPreTranslatedFields() |
235
|
1 |
|
); |
236
|
1 |
|
} else { |
237
|
|
|
$translatableFields = []; |
238
|
|
|
} |
239
|
|
|
|
240
|
2 |
|
if (!empty($translatableFields)) { |
241
|
|
|
$invalidateCacheMap[$this->languageRepository->getClassName()][] = $cacheKey; |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
// exposed fields |
245
|
2 |
|
$documentFieldNames = isset($this->documentFieldNames[$repo->getClassName()]) ? |
246
|
2 |
|
$this->documentFieldNames[$repo->getClassName()] : |
247
|
2 |
|
[]; |
248
|
|
|
|
249
|
2 |
|
$languages = []; |
250
|
2 |
|
if ($online) { |
251
|
2 |
|
$languages = array_map( |
252
|
|
|
function (Language $language) { |
253
|
|
|
return $language->getId(); |
254
|
2 |
|
}, |
255
|
2 |
|
$this->languageRepository->findAll() |
256
|
1 |
|
); |
257
|
1 |
|
} |
258
|
2 |
|
if (empty($languages)) { |
259
|
|
|
$languages = [ |
260
|
2 |
|
$this->defaultLocale |
261
|
1 |
|
]; |
262
|
1 |
|
} |
263
|
|
|
|
264
|
|
|
// exposed events.. |
265
|
2 |
|
$classShortName = $documentReflection->getShortName(); |
266
|
2 |
|
if (isset($this->eventMap[$classShortName])) { |
267
|
2 |
|
$schema->setEventNames(array_unique($this->eventMap[$classShortName]['events'])); |
268
|
1 |
|
} |
269
|
|
|
|
270
|
2 |
|
$requiredFields = []; |
271
|
2 |
|
$modelRequiredFields = $model->getRequiredFields(); |
272
|
2 |
|
if (is_array($modelRequiredFields)) { |
273
|
2 |
|
foreach ($modelRequiredFields as $field) { |
274
|
|
|
// don't describe hidden fields |
275
|
|
|
if (!isset($documentFieldNames[$field])) { |
276
|
|
|
continue; |
277
|
1 |
|
} |
278
|
|
|
|
279
|
|
|
$requiredFields[] = $documentFieldNames[$field]; |
280
|
1 |
|
} |
281
|
1 |
|
} |
282
|
|
|
|
283
|
2 |
|
foreach ($meta->getFieldNames() as $field) { |
284
|
|
|
// don't describe hidden fields |
285
|
2 |
|
if (!isset($documentFieldNames[$field])) { |
286
|
|
|
continue; |
287
|
|
|
} |
288
|
|
|
// hide realId field (I was aiming at a cleaner solution than the macig realId string initially) |
289
|
2 |
|
if ($meta->getTypeOfField($field) == 'id' && $field == 'realId') { |
290
|
|
|
continue; |
291
|
|
|
} |
292
|
|
|
|
293
|
2 |
|
$property = new Schema(); |
294
|
2 |
|
$property->setTitle($model->getTitleOfField($field)); |
295
|
2 |
|
$property->setDescription($model->getDescriptionOfField($field)); |
296
|
|
|
|
297
|
2 |
|
$property->setType($meta->getTypeOfField($field)); |
298
|
2 |
|
$property->setReadOnly($model->getReadOnlyOfField($field)); |
299
|
|
|
|
300
|
2 |
|
if ($meta->getTypeOfField($field) === 'many') { |
301
|
|
|
$propertyModel = $model->manyPropertyModelForTarget($meta->getAssociationTargetClass($field)); |
302
|
|
|
|
303
|
|
|
if ($model->hasDynamicKey($field)) { |
304
|
|
|
$property->setType('object'); |
305
|
|
|
|
306
|
|
|
if ($online) { |
307
|
|
|
// we generate a complete list of possible keys when we have access to mongodb |
308
|
|
|
// this makes everything work with most json-schema v3 implementations (ie. schemaform.io) |
309
|
|
|
$dynamicKeySpec = $model->getDynamicKeySpec($field); |
310
|
|
|
|
311
|
|
|
$documentId = $dynamicKeySpec->{'document-id'}; |
312
|
|
|
$dynamicRepository = $this->repositoryFactory->get($documentId); |
313
|
|
|
|
314
|
|
|
// put this in invalidate map so when know we have to invalidate when this document is used |
315
|
|
|
$invalidateCacheMap[$dynamicRepository->getDocumentName()][] = $cacheKey; |
|
|
|
|
316
|
|
|
|
317
|
|
|
$repositoryMethod = $dynamicKeySpec->{'repository-method'}; |
318
|
|
|
$records = $dynamicRepository->$repositoryMethod(); |
319
|
|
|
|
320
|
|
|
$dynamicProperties = array_map( |
321
|
|
|
function ($record) { |
322
|
|
|
return $record->getId(); |
323
|
|
|
}, |
324
|
|
|
$records |
325
|
|
|
); |
326
|
|
|
foreach ($dynamicProperties as $propertyName) { |
327
|
|
|
$property->addProperty( |
328
|
|
|
$propertyName, |
329
|
|
|
$this->getModelSchema($field, $propertyModel, $online) |
|
|
|
|
330
|
|
|
); |
331
|
|
|
} |
332
|
|
|
} else { |
333
|
|
|
// swagger case |
334
|
|
|
$property->setAdditionalProperties( |
335
|
|
|
new SchemaAdditionalProperties($this->getModelSchema($field, $propertyModel, $online)) |
|
|
|
|
336
|
|
|
); |
337
|
|
|
} |
338
|
|
|
} else { |
339
|
|
|
$property->setItems($this->getModelSchema($field, $propertyModel, $online)); |
|
|
|
|
340
|
|
|
$property->setType('array'); |
341
|
|
|
} |
342
|
2 |
|
} elseif ($meta->getTypeOfField($field) === 'one') { |
343
|
2 |
|
$propertyModel = $model->manyPropertyModelForTarget($meta->getAssociationTargetClass($field)); |
344
|
2 |
|
$property = $this->getModelSchema($field, $propertyModel, $online); |
345
|
|
|
|
346
|
2 |
|
if ($property->getSearchable()) { |
347
|
|
|
foreach ($property->getSearchable() as $searchableSubField) { |
|
|
|
|
348
|
1 |
|
$subSearchableFields[] = $field . '.' . $searchableSubField; |
349
|
|
|
} |
350
|
|
|
} |
351
|
2 |
|
} elseif (in_array($field, $translatableFields, true)) { |
352
|
|
|
$property = $this->makeTranslatable($property, $languages); |
353
|
2 |
|
} elseif (in_array($field.'[]', $translatableFields, true)) { |
354
|
|
|
$property = $this->makeArrayTranslatable($property, $languages); |
355
|
2 |
|
} elseif ($meta->getTypeOfField($field) === 'extref') { |
356
|
|
|
$urls = array(); |
357
|
|
|
$refCollections = $model->getRefCollectionOfField($field); |
358
|
|
|
foreach ($refCollections as $collection) { |
359
|
|
|
if (isset($this->extrefServiceMapping[$collection])) { |
360
|
|
|
$urls[] = $this->router->generate( |
361
|
|
|
$this->extrefServiceMapping[$collection].'.all', |
362
|
|
|
[], |
363
|
|
|
UrlGeneratorInterface::ABSOLUTE_URL |
364
|
|
|
); |
365
|
|
|
} elseif ($collection === '*') { |
366
|
|
|
$urls[] = '*'; |
367
|
|
|
} |
368
|
|
|
} |
369
|
|
|
$property->setRefCollection($urls); |
370
|
2 |
View Code Duplication |
} elseif ($meta->getTypeOfField($field) === 'collection') { |
|
|
|
|
371
|
2 |
|
$itemSchema = new Schema(); |
372
|
2 |
|
$property->setType('array'); |
373
|
2 |
|
$itemSchema->setType($this->getCollectionItemType($meta->name, $field)); |
374
|
|
|
|
375
|
2 |
|
$property->setItems($itemSchema); |
376
|
2 |
|
$property->setFormat(null); |
377
|
2 |
|
} elseif ($meta->getTypeOfField($field) === 'datearray') { |
378
|
|
|
$itemSchema = new Schema(); |
379
|
|
|
$property->setType('array'); |
380
|
|
|
$itemSchema->setType('string'); |
381
|
|
|
$itemSchema->setFormat('date-time'); |
382
|
|
|
|
383
|
|
|
$property->setItems($itemSchema); |
384
|
|
|
$property->setFormat(null); |
385
|
2 |
View Code Duplication |
} elseif ($meta->getTypeOfField($field) === 'hasharray') { |
|
|
|
|
386
|
|
|
$itemSchema = new Schema(); |
387
|
|
|
$itemSchema->setType('object'); |
388
|
|
|
|
389
|
|
|
$property->setType('array'); |
390
|
|
|
$property->setItems($itemSchema); |
391
|
|
|
$property->setFormat(null); |
392
|
|
|
} |
393
|
|
|
|
394
|
2 |
|
if (in_array($meta->getTypeOfField($field), $property->getMinLengthTypes())) { |
|
|
|
|
395
|
|
|
// make sure a required field cannot be blank |
396
|
2 |
|
if (in_array($documentFieldNames[$field], $requiredFields)) { |
397
|
|
|
$property->setMinLength(1); |
|
|
|
|
398
|
|
|
} else { |
399
|
|
|
// in the other case, make sure also null can be sent.. |
400
|
2 |
|
$currentType = $property->getType(); |
|
|
|
|
401
|
2 |
|
if ($currentType instanceof SchemaType) { |
402
|
2 |
|
$property->setType(array_merge($currentType->getTypes(), ['null'])); |
|
|
|
|
403
|
1 |
|
} else { |
404
|
|
|
$property->setType('null'); |
405
|
|
|
} |
406
|
|
|
} |
407
|
1 |
|
} |
408
|
|
|
|
409
|
2 |
|
$property = $this->constraintBuilder->addConstraints($field, $property, $model); |
|
|
|
|
410
|
|
|
|
411
|
2 |
|
$schema->addProperty($documentFieldNames[$field], $property); |
412
|
1 |
|
} |
413
|
|
|
|
414
|
2 |
|
if ($meta->isEmbeddedDocument && !in_array('id', $model->getRequiredFields())) { |
415
|
2 |
|
$schema->removeProperty('id'); |
416
|
1 |
|
} |
417
|
|
|
|
418
|
|
|
/** |
419
|
|
|
* if we generate schema for internal use; don't have id in required array as |
420
|
|
|
* it's 'requiredness' depends on the method used (POST/PUT/PATCH) and is checked in checks |
421
|
|
|
* before validation. |
422
|
|
|
*/ |
423
|
2 |
|
$idPosition = array_search('id', $requiredFields); |
424
|
2 |
|
if ($internal === true && $idPosition !== false) { |
425
|
|
|
unset($requiredFields[$idPosition]); |
426
|
|
|
} |
427
|
|
|
|
428
|
2 |
|
$schema->setRequired($requiredFields); |
429
|
|
|
|
430
|
|
|
// set additionalProperties to false (as this is our default policy) if not already set |
431
|
2 |
|
if (is_null($schema->getAdditionalProperties()) && $online) { |
432
|
2 |
|
$schema->setAdditionalProperties(new SchemaAdditionalProperties(false)); |
433
|
1 |
|
} |
434
|
|
|
|
435
|
2 |
|
$searchableFields = array_merge($subSearchableFields, $model->getSearchableFields()); |
436
|
2 |
|
$schema->setSearchable($searchableFields); |
437
|
|
|
|
438
|
2 |
|
if ($serialized === true) { |
439
|
2 |
|
$schema = json_decode($this->serializer->serialize($schema, 'json')); |
440
|
1 |
|
} |
441
|
|
|
|
442
|
2 |
|
$this->cache->save($cacheKey, $schema); |
443
|
2 |
|
$this->cache->save($this->cacheInvalidationMapKey, $invalidateCacheMap); |
444
|
|
|
|
445
|
2 |
|
return $schema; |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
/** |
449
|
|
|
* turn a property into a translatable property |
450
|
|
|
* |
451
|
|
|
* @param Schema $property simple string property |
452
|
|
|
* @param string[] $languages available languages |
453
|
|
|
* |
454
|
|
|
* @return Schema |
455
|
|
|
*/ |
456
|
|
|
public function makeTranslatable(Schema $property, $languages) |
457
|
|
|
{ |
458
|
|
|
$property->setType('object'); |
459
|
|
|
$property->setTranslatable(true); |
460
|
|
|
|
461
|
|
|
array_walk( |
462
|
|
|
$languages, |
463
|
|
|
function ($language) use ($property) { |
464
|
|
|
$schema = new Schema; |
465
|
|
|
$schema->setType('string'); |
466
|
|
|
$schema->setTitle('Translated String'); |
467
|
|
|
$schema->setDescription('String in ' . $language . ' locale.'); |
468
|
|
|
$property->addProperty($language, $schema); |
469
|
|
|
} |
470
|
|
|
); |
471
|
|
|
$property->setRequired(['en']); |
472
|
|
|
return $property; |
473
|
|
|
} |
474
|
|
|
|
475
|
|
|
/** |
476
|
|
|
* turn a array property into a translatable property |
477
|
|
|
* |
478
|
|
|
* @param Schema $property simple string property |
479
|
|
|
* @param string[] $languages available languages |
480
|
|
|
* |
481
|
|
|
* @return Schema |
482
|
|
|
*/ |
483
|
|
|
public function makeArrayTranslatable(Schema $property, $languages) |
484
|
|
|
{ |
485
|
|
|
$property->setType('array'); |
486
|
|
|
$property->setItems($this->makeTranslatable(new Schema(), $languages)); |
487
|
|
|
return $property; |
488
|
|
|
} |
489
|
|
|
|
490
|
|
|
/** |
491
|
|
|
* get canonical route to a schema based on a route |
492
|
|
|
* |
493
|
|
|
* @param string $routeName route name |
494
|
|
|
* |
495
|
|
|
* @return string schema route name |
496
|
|
|
*/ |
497
|
4 |
|
public static function getSchemaRouteName($routeName) |
498
|
|
|
{ |
499
|
4 |
|
$routeParts = explode('.', $routeName); |
500
|
|
|
|
501
|
4 |
|
$routeType = array_pop($routeParts); |
502
|
|
|
// check if we need to create an item or collection schema |
503
|
4 |
|
$realRouteType = 'canonicalSchema'; |
504
|
4 |
|
if ($routeType != 'options' && $routeType != 'all') { |
505
|
4 |
|
$realRouteType = 'canonicalIdSchema'; |
506
|
2 |
|
} |
507
|
|
|
|
508
|
4 |
|
return implode('.', array_merge($routeParts, array($realRouteType))); |
509
|
|
|
} |
510
|
|
|
|
511
|
|
|
/** |
512
|
|
|
* Get item type of collection field |
513
|
|
|
* |
514
|
|
|
* @param string $className Class name |
515
|
|
|
* @param string $fieldName Field name |
516
|
|
|
* @return string|null |
517
|
|
|
*/ |
518
|
2 |
|
private function getCollectionItemType($className, $fieldName) |
519
|
|
|
{ |
520
|
2 |
|
$serializerMetadata = $this->serializerMetadataFactory->getMetadataForClass($className); |
521
|
2 |
|
if ($serializerMetadata === null) { |
522
|
|
|
return null; |
523
|
|
|
} |
524
|
2 |
|
if (!isset($serializerMetadata->propertyMetadata[$fieldName])) { |
525
|
|
|
return null; |
526
|
|
|
} |
527
|
|
|
|
528
|
2 |
|
$type = $serializerMetadata->propertyMetadata[$fieldName]->type; |
529
|
2 |
|
return isset($type['name'], $type['params'][0]['name']) && $type['name'] === 'array' ? |
530
|
2 |
|
$type['params'][0]['name'] : |
531
|
2 |
|
null; |
532
|
|
|
} |
533
|
|
|
} |
534
|
|
|
|
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.