SchemaUtils::getModelSchema()   F
last analyzed

Complexity

Conditions 50
Paths > 20000

Size

Total Lines 288

Duplication

Lines 16
Ratio 5.56 %

Code Coverage

Tests 0
CRAP Score 2550

Importance

Changes 0
Metric Value
dl 16
loc 288
ccs 0
cts 221
cp 0
rs 0
c 0
b 0
f 0
cc 50
nc 602120
nop 6
crap 2550

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Utils for generating schemas.
4
 */
5
6
namespace Graviton\SchemaBundle;
7
8
use Doctrine\Common\Cache\CacheProvider;
9
use Doctrine\ODM\MongoDB\DocumentRepository;
10
use Graviton\I18nBundle\Document\Language;
11
use Graviton\RestBundle\Model\DocumentModel;
12
use Graviton\SchemaBundle\Constraint\ConstraintBuilder;
13
use Graviton\SchemaBundle\Document\Schema;
14
use Graviton\SchemaBundle\Document\SchemaAdditionalProperties;
15
use Graviton\SchemaBundle\Document\SchemaType;
16
use Graviton\SchemaBundle\Service\RepositoryFactory;
17
use JmesPath\CompilerRuntime;
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  https://opensource.org/licenses/MIT MIT License
28
 * @link     http://swisscom.ch
29
 */
30
class SchemaUtils
31
{
32
33
    /**
34
     * language repository
35
     *
36
     * @var DocumentRepository 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 boolean
75
     */
76
    private $schemaVariationEnabled;
77
78
    /**
79
     * @var string
80
     */
81
    private $defaultLocale;
82
83
    /**
84
     * @var RepositoryFactory
85
     */
86
    private $repositoryFactory;
87
88
    /**
89
     * @var SerializerMetadataFactoryInterface
90
     */
91
    private $serializerMetadataFactory;
92
93
    /**
94
     * @var CacheProvider
95
     */
96
    private $cache;
97
98
    /**
99
     * @var ConstraintBuilder
100
     */
101
    private $constraintBuilder;
102
103
    /**
104
     * @var CompilerRuntime
105
     */
106
    private $jmesRuntime;
107
108
    /**
109
     * Constructor
110
     *
111
     * @param RepositoryFactory                  $repositoryFactory         Create repos from model class names
112
     * @param SerializerMetadataFactoryInterface $serializerMetadataFactory Serializer metadata factory
113
     * @param DocumentRepository                 $languageRepository        repository
114
     * @param RouterInterface                    $router                    router
115
     * @param Serializer                         $serializer                serializer
116
     * @param array                              $extrefServiceMapping      Extref service mapping
117
     * @param array                              $eventMap                  eventmap
118
     * @param array                              $documentFieldNames        Document field names
119
     * @param boolean                            $schemaVariationEnabled    if schema variations should be enabled
120
     * @param string                             $defaultLocale             Default Language
121
     * @param ConstraintBuilder                  $constraintBuilder         Constraint builder
122
     * @param CacheProvider                      $cache                     Doctrine cache provider
123
     * @param CompilerRuntime                    $jmesRuntime               jmespath.php Runtime
124
     */
125
    public function __construct(
126
        RepositoryFactory $repositoryFactory,
127
        SerializerMetadataFactoryInterface $serializerMetadataFactory,
128
        DocumentRepository $languageRepository,
129
        RouterInterface $router,
130
        Serializer $serializer,
131
        array $extrefServiceMapping,
132
        array $eventMap,
133
        array $documentFieldNames,
134
        $schemaVariationEnabled,
135
        $defaultLocale,
136
        ConstraintBuilder $constraintBuilder,
137
        CacheProvider $cache,
138
        CompilerRuntime $jmesRuntime
139
    ) {
140
        $this->repositoryFactory = $repositoryFactory;
141
        $this->serializerMetadataFactory = $serializerMetadataFactory;
142
        $this->languageRepository = $languageRepository;
143
        $this->router = $router;
144
        $this->serializer = $serializer;
145
        $this->extrefServiceMapping = $extrefServiceMapping;
146
        $this->eventMap = $eventMap;
147
        $this->documentFieldNames = $documentFieldNames;
148
        $this->schemaVariationEnabled = (bool) $schemaVariationEnabled;
149
        $this->defaultLocale = $defaultLocale;
150
        $this->constraintBuilder = $constraintBuilder;
151
        $this->cache = $cache;
152
        $this->jmesRuntime = $jmesRuntime;
153
    }
154
155
    /**
156
     * get schema for an array of models
157
     *
158
     * @param string        $modelName name of model
159
     * @param DocumentModel $model     model
160
     *
161
     * @return Schema
162
     */
163
    public function getCollectionSchema($modelName, DocumentModel $model)
164
    {
165
        $collectionSchema = new Schema;
166
        $collectionSchema->setTitle(sprintf('Array of %s objects', $modelName));
167
        $collectionSchema->setType('array');
168
        $collectionSchema->setItems($this->getModelSchema($modelName, $model));
169
        return $collectionSchema;
170
    }
171
172
    /**
173
     * return the schema for a given route
174
     *
175
     * @param string        $modelName  name of mode to generate schema for
176
     * @param DocumentModel $model      model to generate schema for
177
     * @param boolean       $online     if we are online and have access to mongodb during this build
178
     * @param boolean       $internal   if true, we generate the schema for internal validation use
179
     * @param boolean       $serialized if true, it will serialize the Schema object and return a \stdClass instead
180
     * @param \stdClass     $userData   if given, the userData will be checked for a variation match
0 ignored issues
show
Documentation introduced by
Should the type for parameter $userData not be \stdClass|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
181
     *
182
     * @return Schema|\stdClass Either a Schema instance or serialized as \stdClass if $serialized is true
183
     */
184
    public function getModelSchema(
185
        $modelName,
186
        DocumentModel $model,
187
        $online = true,
188
        $internal = false,
189
        $serialized = false,
190
        $userData = null
191
    ) {
192
        $variationName = null;
193
        if ($this->schemaVariationEnabled === true &&
194
            $userData instanceof \stdClass &&
195
            !empty($model->getVariations())
196
        ) {
197
            $variationName = $this->getSchemaVariationName($userData, $model->getVariations());
198
        }
199
200
        $languages = [];
201
        if ($online) {
202
            $languages = array_map(
203
                function (Language $language) {
204
                    return $language->getId();
205
                },
206
                $this->languageRepository->findAll()
207
            );
208
        }
209
        if (empty($languages)) {
210
            $languages = [
211
                $this->defaultLocale
212
            ];
213
        }
214
215
        $cacheKey = sprintf(
216
            'schema.%s.%s.%s.%s.%s.%s',
217
            $model->getEntityClass(),
218
            (string) $online,
219
            (string) $internal,
220
            (string) $serialized,
221
            (string) $variationName,
222
            (string) implode('-', $languages)
223
        );
224
225
        if ($this->cache->contains($cacheKey)) {
226
            return $this->cache->fetch($cacheKey);
227
        }
228
229
        // build up schema data
230
        $schema = new Schema;
231
        $schemaIsCachable = true;
232
233
        if (!empty($model->getTitle())) {
234
            $schema->setTitle($model->getTitle());
235
        } else {
236
            if (!is_null($modelName)) {
237
                $schema->setTitle(ucfirst($modelName));
238
            } else {
239
                $reflection = new \ReflectionClass($model);
240
                $schema->setTitle(ucfirst($reflection->getShortName()));
241
            }
242
        }
243
244
        $schema->setDescription($model->getDescription());
245
        $schema->setDocumentClass($model->getDocumentClass());
0 ignored issues
show
Security Bug introduced by
It seems like $model->getDocumentClass() targeting Graviton\SchemaBundle\Mo...del::getDocumentClass() can also be of type false; however, Graviton\SchemaBundle\Do...ema::setDocumentClass() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
246
        $schema->setRecordOriginModifiable($model->getRecordOriginModifiable());
247
        $schema->setIsVersioning($model->isVersioning());
248
        $schema->setType('object');
249
        $schema->setVariations($model->getVariations());
250
251
        // grab schema info from model
252
        $repo = $model->getRepository();
253
        $meta = $repo->getClassMetadata();
254
255
        // Init sub searchable fields
256
        $subSearchableFields = [];
257
258
        // reflection
259
        $documentReflection = new \ReflectionClass($repo->getClassName());
260
        $documentClass = $documentReflection->newInstance();
261
        $classShortName = $documentReflection->getShortName();
262
263
        // exposed fields
264
        $documentFieldNames = isset($this->documentFieldNames[$repo->getClassName()]) ?
265
            $this->documentFieldNames[$repo->getClassName()] :
266
            [];
267
268
        // exposed events..
269
        if (isset($this->eventMap[$classShortName])) {
270
            $schema->setEventNames(array_unique($this->eventMap[$classShortName]['events']));
271
        }
272
273
        // don't describe hidden fields
274
        $requiredFields = $model->getRequiredFields($variationName);
275
        if (empty($requiredFields) || !is_array($requiredFields)) {
276
            $requiredFields = [];
277
        }
278
        $requiredFields = array_intersect_key($documentFieldNames, array_combine($requiredFields, $requiredFields));
279
280
        foreach ($meta->getFieldNames() as $field) {
281
            // don't describe hidden fields
282
            if (!isset($documentFieldNames[$field])) {
283
                continue;
284
            }
285
286
            $isEmptyExtref = false;
287
            if (is_callable([$documentClass, 'isEmptyExtRefObject'])) {
288
                $isEmptyExtref = $documentClass->isEmptyExtRefObject();
289
            }
290
291
            // hide realId field (I was aiming at a cleaner solution than the matching realId string initially)
292
            // hide embedded ID field unless it's required or for internal validation need, back-compatibility.
293
            // TODO remove !$internal once no clients use it for embedded objects as these id are done automatically
294
            if (($meta->getTypeOfField($field) == 'id' && $field == 'realId') ||
295
                (
296
                    $field == 'id' &&
297
                    !$internal && $meta->isEmbeddedDocument && !in_array('id', $requiredFields) && $isEmptyExtref
298
                )
299
            ) {
300
                continue;
301
            }
302
303
            $property = new Schema();
304
            $property->setTitle($model->getTitleOfField($field));
305
            $property->setDescription($model->getDescriptionOfField($field));
306
            $property->setType($meta->getTypeOfField($field));
307
            $property->setGroups($model->getGroupsOfField($field));
308
            $property->setReadOnly($model->getReadOnlyOfField($field));
309
            $property->setOnVariation($model->getOnVariaton($field));
310
311
            // we only want to render if it's true
312
            if ($model->getRecordOriginExceptionOfField($field) === true) {
313
                $property->setRecordOriginException(true);
314
            }
315
316
            if ($meta->getTypeOfField($field) === 'many') {
317
                $propertyModel = $model->manyPropertyModelForTarget($meta->getAssociationTargetClass($field));
318
319
                if ($model->hasDynamicKey($field)) {
320
                    $property->setType('object');
321
322
                    if ($online) {
323
                        // we generate a complete list of possible keys when we have access to mongodb
324
                        // this makes everything work with most json-schema v3 implementations (ie. schemaform.io)
325
                        $dynamicKeySpec = $model->getDynamicKeySpec($field);
326
327
                        $documentId = $dynamicKeySpec->{'document-id'};
328
                        $dynamicRepository = $this->repositoryFactory->get($documentId);
329
330
                        // put this in invalidate map so when know we have to invalidate when this document is used
331
                        $invalidateCacheMap[$dynamicRepository->getDocumentName()][] = $cacheKey;
0 ignored issues
show
Bug introduced by
The method getDocumentName() does not seem to exist on object<Doctrine\Common\Persistence\ObjectManager>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
332
333
                        $repositoryMethod = $dynamicKeySpec->{'repository-method'};
334
                        $records = $dynamicRepository->$repositoryMethod();
335
336
                        $dynamicProperties = array_map(
337
                            function ($record) {
338
                                return $record->getId();
339
                            },
340
                            $records
341
                        );
342
                        foreach ($dynamicProperties as $propertyName) {
343
                            $property->addProperty(
344
                                $propertyName,
345
                                $this->getModelSchema($field, $propertyModel, $online, $internal)
346
                            );
347
                        }
348
349
                        // don't cache this schema
350
                        $schemaIsCachable = false;
351
                    } else {
352
                        // swagger case
353
                        $property->setAdditionalProperties(
354
                            new SchemaAdditionalProperties(
355
                                $this->getModelSchema($field, $propertyModel, $online, $internal)
356
                            )
357
                        );
358
                    }
359
                } else {
360
                    $property->setItems($this->getModelSchema($field, $propertyModel, $online, $internal));
361
                    $property->setType('array');
362
                }
363
            } elseif ($meta->getTypeOfField($field) === 'one') {
364
                $propertyModel = $model->manyPropertyModelForTarget($meta->getAssociationTargetClass($field));
365
                $property = $this->getModelSchema($field, $propertyModel, $online, $internal);
366
367
                if ($property->getSearchable()) {
368
                    foreach ($property->getSearchable() as $searchableSubField) {
0 ignored issues
show
Bug introduced by
The method getSearchable does only exist in Graviton\SchemaBundle\Document\Schema, but not in stdClass.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
369
                        $subSearchableFields[] = $field . '.' . $searchableSubField;
370
                    }
371
                }
372
            } elseif ($meta->getTypeOfField($field) == 'translatable') {
373
                $property = $this->makeTranslatable($property, $languages);
374
            } elseif ($meta->getTypeOfField($field) === 'extref') {
375
                $urls = [];
376
                $refCollections = $model->getRefCollectionOfField($field);
377
                foreach ($refCollections as $collection) {
378
                    if (isset($this->extrefServiceMapping[$collection])) {
379
                        $urls[] = $this->router->generate(
380
                            $this->extrefServiceMapping[$collection].'.all',
381
                            [],
382
                            UrlGeneratorInterface::ABSOLUTE_URL
383
                        );
384
                    } elseif ($collection === '*') {
385
                        $urls[] = '*';
386
                    }
387
                }
388
                $property->setRefCollection($urls);
389 View Code Duplication
            } elseif ($meta->getTypeOfField($field) === 'collection') {
390
                $itemSchema = new Schema();
391
                $itemSchema->setType($this->getCollectionItemType($meta->name, $field));
392
393
                $property->setType('array');
394
                $property->setItems($itemSchema);
395
                $property->setFormat(null);
396
            } elseif ($meta->getTypeOfField($field) === 'datearray') {
397
                $itemSchema = new Schema();
398
                $itemSchema->setType('string');
399
                $itemSchema->setFormat('date-time');
400
401
                $property->setType('array');
402
                $property->setItems($itemSchema);
403
                $property->setFormat(null);
404 View Code Duplication
            } elseif ($meta->getTypeOfField($field) === 'hasharray') {
405
                $itemSchema = new Schema();
406
                $itemSchema->setType('object');
407
408
                $property->setType('array');
409
                $property->setItems($itemSchema);
410
                $property->setFormat(null);
411
            } elseif ($meta->getTypeOfField($field) === 'translatablearray') {
412
                $itemSchema = new Schema();
413
                $itemSchema->setType('object');
414
                $itemSchema->setFormat('translatable');
415
                $itemSchema = $this->makeTranslatable($itemSchema, $languages);
416
417
                $property->setType('array');
418
                $property->setItems($itemSchema);
419
                $property->setFormat(null);
420
            }
421
422
            if (in_array($meta->getTypeOfField($field), $property->getMinLengthTypes())) {
0 ignored issues
show
Bug introduced by
The method getMinLengthTypes does only exist in Graviton\SchemaBundle\Document\Schema, but not in stdClass.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
423
                // make sure a required field cannot be blank
424
                if (in_array($documentFieldNames[$field], $requiredFields)) {
425
                    $property->setMinLength(1);
0 ignored issues
show
Bug introduced by
The method setMinLength does only exist in Graviton\SchemaBundle\Document\Schema, but not in stdClass.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
426
                } else {
427
                    // in the other case, make sure also null can be sent..
428
                    $currentType = $property->getType();
0 ignored issues
show
Bug introduced by
The method getType does only exist in Graviton\SchemaBundle\Document\Schema, but not in stdClass.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
429
                    if ($currentType instanceof SchemaType) {
430
                        $property->setType(array_merge($currentType->getTypes(), ['null']));
0 ignored issues
show
Bug introduced by
The method setType does only exist in Graviton\SchemaBundle\Document\Schema, but not in stdClass.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
431
                    } else {
432
                        $property->setType('null');
433
                    }
434
                }
435
            }
436
437
            $property = $this->constraintBuilder->addConstraints($field, $property, $model);
0 ignored issues
show
Bug introduced by
It seems like $property can also be of type object<stdClass>; however, Graviton\SchemaBundle\Co...ilder::addConstraints() does only seem to accept object<Graviton\SchemaBundle\Document\Schema>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
438
439
            $schema->addProperty($documentFieldNames[$field], $property);
440
        }
441
442
        /**
443
         * if we generate schema for internal use; don't have id in required array as
444
         * it's 'requiredness' depends on the method used (POST/PUT/PATCH) and is checked in checks
445
         * before validation.
446
         */
447
        $idPosition = array_search('id', $requiredFields);
448
        if ($internal === true && $idPosition !== false && !$meta->isEmbeddedDocument) {
449
            unset($requiredFields[$idPosition]);
450
        }
451
452
        $schema->setRequired($requiredFields);
453
454
        // set additionalProperties to false (as this is our default policy) if not already set
455
        if (is_null($schema->getAdditionalProperties()) && $online) {
456
            $schema->setAdditionalProperties(new SchemaAdditionalProperties(false));
457
        }
458
459
        $searchableFields = array_merge($subSearchableFields, $model->getSearchableFields());
460
        $schema->setSearchable($searchableFields);
461
462
        if ($serialized === true) {
463
            $schema = json_decode($this->serializer->serialize($schema, 'json'));
464
        }
465
466
        if ($schemaIsCachable === true) {
467
            $this->cache->save($cacheKey, $schema);
468
        }
469
470
        return $schema;
471
    }
472
473
    /**
474
     * gets the name of the variation to apply based on userdata and the service definition
475
     *
476
     * @param \stdClass $userData   user data
477
     * @param \stdClass $variations variations as defined in schema
478
     *
479
     * @return string|null the variation name or null if none
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
480
     */
481
    private function getSchemaVariationName($userData, $variations)
482
    {
483
        foreach ($variations as $variationName => $expressions) {
0 ignored issues
show
Bug introduced by
The expression $variations of type object<stdClass> is not traversable.
Loading history...
484
            $results = array_map(
485
                function ($expression) use ($userData) {
486
                    return $this->jmesRuntime->__invoke($expression, $userData);
487
                },
488
                $expressions
489
            );
490
491
            $results = array_unique($results);
492
493
            if (count($results) == 1 && $results[0] === true) {
494
                return $variationName;
495
            }
496
        }
497
498
        return null;
499
    }
500
501
    /**
502
     * turn a property into a translatable property
503
     *
504
     * @param Schema   $property  simple string property
505
     * @param string[] $languages available languages
506
     *
507
     * @return Schema
508
     */
509
    public function makeTranslatable(Schema $property, $languages)
510
    {
511
        $property->setType('object');
512
        $property->setTranslatable(true);
513
514
        array_walk(
515
            $languages,
516
            function ($language) use ($property) {
517
                $schema = new Schema;
518
                $schema->setType('string');
519
                $schema->setTitle('Translated String');
520
                $schema->setDescription('String in ' . $language . ' locale.');
521
                $property->addProperty($language, $schema);
522
            }
523
        );
524
        $property->setRequired(['en']);
525
        return $property;
526
    }
527
528
    /**
529
     * turn a array property into a translatable property
530
     *
531
     * @param Schema   $property  simple string property
532
     * @param string[] $languages available languages
533
     *
534
     * @return Schema
535
     */
536
    public function makeArrayTranslatable(Schema $property, $languages)
537
    {
538
        $property->setType('array');
539
        $property->setItems($this->makeTranslatable(new Schema(), $languages));
540
        return $property;
541
    }
542
543
    /**
544
     * get canonical route to a schema based on a route
545
     *
546
     * @param string $routeName route name
547
     *
548
     * @return string schema route name
549
     */
550
    public static function getSchemaRouteName($routeName)
551
    {
552
        $routeParts = explode('.', $routeName);
553
554
        $routeType = array_pop($routeParts);
555
        // check if we need to create an item or collection schema
556
        $realRouteType = 'canonicalSchema';
557
        if ($routeType != 'options' && $routeType != 'all') {
558
            $realRouteType = 'canonicalIdSchema';
559
        }
560
561
        return implode('.', array_merge($routeParts, array($realRouteType)));
562
    }
563
564
    /**
565
     * Get item type of collection field
566
     *
567
     * @param string $className Class name
568
     * @param string $fieldName Field name
569
     * @return string|null
570
     */
571
    private function getCollectionItemType($className, $fieldName)
572
    {
573
        $serializerMetadata = $this->serializerMetadataFactory->getMetadataForClass($className);
574
        if ($serializerMetadata === null) {
575
            return null;
576
        }
577
        if (!isset($serializerMetadata->propertyMetadata[$fieldName])) {
578
            return null;
579
        }
580
581
        $type = $serializerMetadata->propertyMetadata[$fieldName]->type;
582
        return isset($type['name'], $type['params'][0]['name']) && $type['name'] === 'array' ?
583
            $type['params'][0]['name'] :
584
            null;
585
    }
586
}
587