Completed
Push — feature/EVO-5751-mongodb-3.x ( f7410f...d456eb )
by Lucas
67:06 queued 60:09
created

SchemaUtils::makeTranslatable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 0
cts 15
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 13
nc 1
nop 2
crap 2
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 8
    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 8
        $this->repositoryFactory = $repositoryFactory;
134 8
        $this->serializerMetadataFactory = $serializerMetadataFactory;
135 8
        $this->languageRepository = $languageRepository;
136 8
        $this->router = $router;
137 8
        $this->serializer = $serializer;
138 8
        $this->extrefServiceMapping = $extrefServiceMapping;
139 8
        $this->eventMap = $eventMap;
140 8
        $this->documentFieldNames = $documentFieldNames;
141 8
        $this->defaultLocale = $defaultLocale;
142 8
        $this->constraintBuilder = $constraintBuilder;
143 8
        $this->cache = $cache;
144 8
        $this->cacheInvalidationMapKey = $cacheInvalidationMapKey;
145 8
    }
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));
0 ignored issues
show
Bug introduced by
It seems like $this->getModelSchema($modelName, $model) targeting Graviton\SchemaBundle\Sc...Utils::getModelSchema() can also be of type object<stdClass>; however, Graviton\SchemaBundle\Document\Schema::setItems() does only seem to accept object<Graviton\SchemaBundle\Document\Schema>, maybe add an additional type check?

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.

Loading history...
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 2
            (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());
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...
218 2
        $schema->setRecordOriginModifiable($model->getRecordOriginModifiable());
219 2
        $schema->setType('object');
220
221
        // grab schema info from model
222 2
        $repo = $model->getRepository();
223 2
        $meta = $repo->getClassMetadata();
224
225
        // Init sub searchable fields
226 2
        $subSearchableFields = array();
227
228
        // look for translatables in document class
229 2
        $documentReflection = new \ReflectionClass($repo->getClassName());
230 2
        if ($documentReflection->implementsInterface('Graviton\I18nBundle\Document\TranslatableDocumentInterface')) {
231
            /** @var TranslatableDocumentInterface $documentInstance */
232 2
            $documentInstance = $documentReflection->newInstanceWithoutConstructor();
233 2
            $translatableFields = array_merge(
234 2
                $documentInstance->getTranslatableFields(),
235 2
                $documentInstance->getPreTranslatedFields()
236 1
            );
237 1
        } else {
238
            $translatableFields = [];
239
        }
240
241 2
        if (!empty($translatableFields)) {
242
            $invalidateCacheMap[$this->languageRepository->getClassName()][] = $cacheKey;
243
        }
244
245
        // exposed fields
246 2
        $documentFieldNames = isset($this->documentFieldNames[$repo->getClassName()]) ?
247 2
            $this->documentFieldNames[$repo->getClassName()] :
248 2
            [];
249
250 2
        $languages = [];
251 2
        if ($online) {
252 2
            $languages = array_map(
253
                function (Language $language) {
254
                    return $language->getId();
255 2
                },
256 2
                $this->languageRepository->findAll()
257 1
            );
258 1
        }
259 2
        if (empty($languages)) {
260
            $languages = [
261 2
                $this->defaultLocale
262 1
            ];
263 1
        }
264
265
        // exposed events..
266 2
        $classShortName = $documentReflection->getShortName();
267 2
        if (isset($this->eventMap[$classShortName])) {
268 2
            $schema->setEventNames(array_unique($this->eventMap[$classShortName]['events']));
269 1
        }
270
271 2
        $requiredFields = [];
272 2
        $modelRequiredFields = $model->getRequiredFields();
273 2
        if (is_array($modelRequiredFields)) {
274 2
            foreach ($modelRequiredFields as $field) {
275
                // don't describe hidden fields
276
                if (!isset($documentFieldNames[$field])) {
277
                    continue;
278
                }
279
280
                $requiredFields[] = $documentFieldNames[$field];
281 1
            }
282 1
        }
283
284 2
        foreach ($meta->getFieldNames() as $field) {
285
            // don't describe hidden fields
286 2
            if (!isset($documentFieldNames[$field])) {
287
                continue;
288
            }
289
            // hide realId field (I was aiming at a cleaner solution than the macig realId string initially)
290 2
            if ($meta->getTypeOfField($field) == 'id' && $field == 'realId') {
291
                continue;
292 1
            }
293
294 2
            $property = new Schema();
295 2
            $property->setTitle($model->getTitleOfField($field));
296 2
            $property->setDescription($model->getDescriptionOfField($field));
297
298 2
            $property->setType($meta->getTypeOfField($field));
299 2
            $property->setReadOnly($model->getReadOnlyOfField($field));
300
301
            // we only want to render if it's true
302 2
            if ($model->getRecordOriginExceptionOfField($field) === true) {
303
                $property->setRecordOriginException(true);
304
            }
305
306 2
            if ($meta->getTypeOfField($field) === 'many') {
307
                $propertyModel = $model->manyPropertyModelForTarget($meta->getAssociationTargetClass($field));
308
309
                if ($model->hasDynamicKey($field)) {
310
                    $property->setType('object');
311
312
                    if ($online) {
313
                        // we generate a complete list of possible keys when we have access to mongodb
314
                        // this makes everything work with most json-schema v3 implementations (ie. schemaform.io)
315
                        $dynamicKeySpec = $model->getDynamicKeySpec($field);
316
317
                        $documentId = $dynamicKeySpec->{'document-id'};
318
                        $dynamicRepository = $this->repositoryFactory->get($documentId);
319
320
                        // put this in invalidate map so when know we have to invalidate when this document is used
321
                        $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...
322
323
                        $repositoryMethod = $dynamicKeySpec->{'repository-method'};
324
                        $records = $dynamicRepository->$repositoryMethod();
325
326
                        $dynamicProperties = array_map(
327
                            function ($record) {
328
                                return $record->getId();
329
                            },
330
                            $records
331
                        );
332
                        foreach ($dynamicProperties as $propertyName) {
333
                            $property->addProperty(
334
                                $propertyName,
335
                                $this->getModelSchema($field, $propertyModel, $online)
0 ignored issues
show
Bug introduced by
It seems like $this->getModelSchema($f...propertyModel, $online) targeting Graviton\SchemaBundle\Sc...Utils::getModelSchema() can also be of type object<stdClass>; however, Graviton\SchemaBundle\Do...t\Schema::addProperty() does only seem to accept object<Graviton\SchemaBundle\Document\Schema>, maybe add an additional type check?

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.

Loading history...
336
                            );
337
                        }
338
                    } else {
339
                        // swagger case
340
                        $property->setAdditionalProperties(
341
                            new SchemaAdditionalProperties($this->getModelSchema($field, $propertyModel, $online))
0 ignored issues
show
Bug introduced by
It seems like $this->getModelSchema($f...propertyModel, $online) targeting Graviton\SchemaBundle\Sc...Utils::getModelSchema() can also be of type object<stdClass>; however, Graviton\SchemaBundle\Do...operties::__construct() does only seem to accept object<Graviton\SchemaBu...ocument\Schema>|boolean, maybe add an additional type check?

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.

Loading history...
342
                        );
343
                    }
344
                } else {
345
                    $property->setItems($this->getModelSchema($field, $propertyModel, $online));
0 ignored issues
show
Bug introduced by
It seems like $this->getModelSchema($f...propertyModel, $online) targeting Graviton\SchemaBundle\Sc...Utils::getModelSchema() can also be of type object<stdClass>; however, Graviton\SchemaBundle\Document\Schema::setItems() does only seem to accept object<Graviton\SchemaBundle\Document\Schema>, maybe add an additional type check?

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.

Loading history...
346
                    $property->setType('array');
347
                }
348 2
            } elseif ($meta->getTypeOfField($field) === 'one') {
349 2
                $propertyModel = $model->manyPropertyModelForTarget($meta->getAssociationTargetClass($field));
350 2
                $property = $this->getModelSchema($field, $propertyModel, $online);
351
352 2
                if ($property->getSearchable()) {
353
                    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...
354 1
                        $subSearchableFields[] = $field . '.' . $searchableSubField;
355
                    }
356
                }
357 2
            } elseif (in_array($field, $translatableFields, true)) {
358
                $property = $this->makeTranslatable($property, $languages);
359 2
            } elseif (in_array($field.'[]', $translatableFields, true)) {
360
                $property = $this->makeArrayTranslatable($property, $languages);
361 2
            } elseif ($meta->getTypeOfField($field) === 'extref') {
362
                $urls = array();
363
                $refCollections = $model->getRefCollectionOfField($field);
364
                foreach ($refCollections as $collection) {
365
                    if (isset($this->extrefServiceMapping[$collection])) {
366
                        $urls[] = $this->router->generate(
367
                            $this->extrefServiceMapping[$collection].'.all',
368
                            [],
369
                            UrlGeneratorInterface::ABSOLUTE_URL
370
                        );
371
                    } elseif ($collection === '*') {
372
                        $urls[] = '*';
373
                    }
374
                }
375
                $property->setRefCollection($urls);
376 2 View Code Duplication
            } elseif ($meta->getTypeOfField($field) === 'collection') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
377 2
                $itemSchema = new Schema();
378 2
                $property->setType('array');
379 2
                $itemSchema->setType($this->getCollectionItemType($meta->name, $field));
380
381 2
                $property->setItems($itemSchema);
382 2
                $property->setFormat(null);
383 2
            } elseif ($meta->getTypeOfField($field) === 'datearray') {
384
                $itemSchema = new Schema();
385
                $property->setType('array');
386
                $itemSchema->setType('string');
387
                $itemSchema->setFormat('date-time');
388
389
                $property->setItems($itemSchema);
390
                $property->setFormat(null);
391 2 View Code Duplication
            } elseif ($meta->getTypeOfField($field) === 'hasharray') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
392
                $itemSchema = new Schema();
393
                $itemSchema->setType('object');
394
395
                $property->setType('array');
396
                $property->setItems($itemSchema);
397
                $property->setFormat(null);
398
            }
399
400 2
            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...
401
                // make sure a required field cannot be blank
402 2
                if (in_array($documentFieldNames[$field], $requiredFields)) {
403
                    $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...
404
                } else {
405
                    // in the other case, make sure also null can be sent..
406 2
                    $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...
407 2
                    if ($currentType instanceof SchemaType) {
408 2
                        $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...
409 1
                    } else {
410
                        $property->setType('null');
411
                    }
412
                }
413 1
            }
414
415 2
            $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...
416
417 2
            $schema->addProperty($documentFieldNames[$field], $property);
418 1
        }
419
420 2
        if ($meta->isEmbeddedDocument && !in_array('id', $model->getRequiredFields())) {
421 2
            $schema->removeProperty('id');
422 1
        }
423
424
        /**
425
         * if we generate schema for internal use; don't have id in required array as
426
         * it's 'requiredness' depends on the method used (POST/PUT/PATCH) and is checked in checks
427
         * before validation.
428
         */
429 2
        $idPosition = array_search('id', $requiredFields);
430 2
        if ($internal === true && $idPosition !== false) {
431
            unset($requiredFields[$idPosition]);
432
        }
433
434 2
        $schema->setRequired($requiredFields);
435
436
        // set additionalProperties to false (as this is our default policy) if not already set
437 2
        if (is_null($schema->getAdditionalProperties()) && $online) {
438 2
            $schema->setAdditionalProperties(new SchemaAdditionalProperties(false));
439 1
        }
440
441 2
        $searchableFields = array_merge($subSearchableFields, $model->getSearchableFields());
442 2
        $schema->setSearchable($searchableFields);
443
444 2
        if ($serialized === true) {
445 2
            $schema = json_decode($this->serializer->serialize($schema, 'json'));
446 1
        }
447
448 2
        $this->cache->save($cacheKey, $schema);
449 2
        $this->cache->save($this->cacheInvalidationMapKey, $invalidateCacheMap);
450
451 2
        return $schema;
452
    }
453
454
    /**
455
     * turn a property into a translatable property
456
     *
457
     * @param Schema   $property  simple string property
458
     * @param string[] $languages available languages
459
     *
460
     * @return Schema
461
     */
462
    public function makeTranslatable(Schema $property, $languages)
463
    {
464
        $property->setType('object');
465
        $property->setTranslatable(true);
466
467
        array_walk(
468
            $languages,
469
            function ($language) use ($property) {
470
                $schema = new Schema;
471
                $schema->setType('string');
472
                $schema->setTitle('Translated String');
473
                $schema->setDescription('String in ' . $language . ' locale.');
474
                $property->addProperty($language, $schema);
475
            }
476
        );
477
        $property->setRequired(['en']);
478
        return $property;
479
    }
480
481
    /**
482
     * turn a array property into a translatable property
483
     *
484
     * @param Schema   $property  simple string property
485
     * @param string[] $languages available languages
486
     *
487
     * @return Schema
488
     */
489
    public function makeArrayTranslatable(Schema $property, $languages)
490
    {
491
        $property->setType('array');
492
        $property->setItems($this->makeTranslatable(new Schema(), $languages));
493
        return $property;
494
    }
495
496
    /**
497
     * get canonical route to a schema based on a route
498
     *
499
     * @param string $routeName route name
500
     *
501
     * @return string schema route name
502
     */
503 4
    public static function getSchemaRouteName($routeName)
504
    {
505 4
        $routeParts = explode('.', $routeName);
506
507 4
        $routeType = array_pop($routeParts);
508
        // check if we need to create an item or collection schema
509 4
        $realRouteType = 'canonicalSchema';
510 4
        if ($routeType != 'options' && $routeType != 'all') {
511 4
            $realRouteType = 'canonicalIdSchema';
512 2
        }
513
514 4
        return implode('.', array_merge($routeParts, array($realRouteType)));
515
    }
516
517
    /**
518
     * Get item type of collection field
519
     *
520
     * @param string $className Class name
521
     * @param string $fieldName Field name
522
     * @return string|null
523
     */
524 2
    private function getCollectionItemType($className, $fieldName)
525
    {
526 2
        $serializerMetadata = $this->serializerMetadataFactory->getMetadataForClass($className);
527 2
        if ($serializerMetadata === null) {
528
            return null;
529
        }
530 2
        if (!isset($serializerMetadata->propertyMetadata[$fieldName])) {
531
            return null;
532
        }
533
534 2
        $type = $serializerMetadata->propertyMetadata[$fieldName]->type;
535 2
        return isset($type['name'], $type['params'][0]['name']) && $type['name'] === 'array' ?
536 2
            $type['params'][0]['name'] :
537 2
            null;
538
    }
539
}
540