Passed
Pull Request — master (#150)
by Christopher
15:53
created

MetadataManager::pluralize()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
3
namespace AlgoWeb\ODataMetadata;
4
5
use AlgoWeb\ODataMetadata\MetadataV3\edm\EntityContainer\AssociationSetAnonymousType;
6
use AlgoWeb\ODataMetadata\MetadataV3\edm\EntityContainer\AssociationSetAnonymousType\EndAnonymousType;
7
use AlgoWeb\ODataMetadata\MetadataV3\edm\EntityContainer\EntitySetAnonymousType;
8
use AlgoWeb\ODataMetadata\MetadataV3\edm\EntityContainer\FunctionImportAnonymousType;
9
use AlgoWeb\ODataMetadata\MetadataV3\edm\TAssociationEndType;
10
use AlgoWeb\ODataMetadata\MetadataV3\edm\TAssociationType;
11
use AlgoWeb\ODataMetadata\MetadataV3\edm\TComplexTypePropertyType;
12
use AlgoWeb\ODataMetadata\MetadataV3\edm\TComplexTypeType;
13
use AlgoWeb\ODataMetadata\MetadataV3\edm\TConstraintType;
14
use AlgoWeb\ODataMetadata\MetadataV3\edm\TDocumentationType;
15
use AlgoWeb\ODataMetadata\MetadataV3\edm\TEntityPropertyType;
16
use AlgoWeb\ODataMetadata\MetadataV3\edm\TEntityTypeType;
17
use AlgoWeb\ODataMetadata\MetadataV3\edm\TFunctionImportReturnTypeType;
18
use AlgoWeb\ODataMetadata\MetadataV3\edm\TNavigationPropertyType;
19
use AlgoWeb\ODataMetadata\MetadataV3\edm\TPropertyRefType;
20
use AlgoWeb\ODataMetadata\MetadataV3\edm\TReferentialConstraintRoleElementType;
21
use AlgoWeb\ODataMetadata\MetadataV3\edm\TTextType;
22
use AlgoWeb\ODataMetadata\MetadataV3\edmx\Edmx;
23
use Doctrine\Common\Inflector\Inflector;
24
use JMS\Serializer\Serializer;
25
use JMS\Serializer\SerializerBuilder;
26
27
class MetadataManager
28
{
29
    private $v3Edmx = null;
30
    private $lastError = null;
31
    private $serializer = null;
32
    private static $typeNameToSetName = null;
33
34
    public function __construct($namespaceName = 'Data', $containerName = 'DefaultContainer', Edmx $edmx = null)
35
    {
36
        $msg = null;
37
        $this->v3Edmx = (null == $edmx) ? new Edmx($namespaceName, $containerName) : $edmx;
38
        assert($this->v3Edmx->isOK($msg), $msg);
39
        $this->initSerialiser();
40
        assert(null != $this->serializer, 'Serializer must not be null at end of constructor');
41
        self::$typeNameToSetName = new BidirectionalMap();
42
    }
43
44
    public function getEdmx()
45
    {
46
        $msg = null;
47
        assert($this->v3Edmx->isOK($msg), $msg);
48
        return $this->v3Edmx;
49
    }
50
51
    /**
52
     * @return \JMS\Serializer\Serializer
0 ignored issues
show
Documentation introduced by
Should the return type not be string?

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...
53
     */
54
    public function getEdmxXML()
55
    {
56
        $cereal = $this->getSerialiser();
57
        if (!$cereal instanceof Serializer) {
58
            throw new \Exception('Serializer must not be null when trying to get edmx xml');
59
        }
60
        return $cereal->serialize($this->getEdmx(), 'xml');
61
    }
62
63
    /**
64
     * @param string $name
65
     * @param TEntityTypeType|null $baseType
66
     * @param bool $isAbstract
67
     * @param string $accessType
68
     * @param null $summary
69
     * @param null $longDescription
70
     * @param null|mixed $pluralName
71
     * @return IsOK[]
72
     */
73
    public function addEntityType(
74
        $name,
75
        TEntityTypeType $baseType = null,
76
        $isAbstract = false,
77
        $accessType = 'Public',
78
        $summary = null,
79
        $longDescription = null,
80
        $pluralName = null
81
    )
82
    {
83
        $newEntity = new TEntityTypeType();
84
        $newEntity->setName($name);
85
        $this->addDocumentation($summary, $longDescription, $newEntity);
86
        $newEntity->setAbstract($isAbstract);
87
        $newEntity->setBaseType(null === $baseType ? null : $this->getNamespace() . $baseType->getName());
88
89
        if (null === $pluralName) {
90
            $pluralName = self::pluralize($newEntity->getName());
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $pluralName. This often makes code more readable.
Loading history...
91
        }
92
93
        $entitySet = new EntitySetAnonymousType();
94
        $entitySet->setName($pluralName);
95
        $namespace = $this->getNamespace();
96
        $entityTypeName = $namespace . $newEntity->getName();
97
        $entitySet->setEntityType($entityTypeName);
98
        $entitySet->setGetterAccess($accessType);
99
100
        $this->v3Edmx->getDataServiceType()->getSchema()[0]->addToEntityType($newEntity);
0 ignored issues
show
Compatibility introduced by
$newEntity of type object<AlgoWeb\ODataMetadata\IsOK> is not a sub-type of object<AlgoWeb\ODataMeta...V3\edm\TEntityTypeType>. It seems like you assume a child class of the class AlgoWeb\ODataMetadata\IsOK to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
101
        $this->v3Edmx->getDataServiceType()->getSchema()[0]->getEntityContainer()[0]->addToEntitySet($entitySet);
102
        assert($this->v3Edmx->isOK($this->lastError), $this->lastError);
103
        self::$typeNameToSetName->put($name, $pluralName);
104
        return [$newEntity, $entitySet];
105
    }
106
107
    public function addComplexType($name, $accessType = 'Public', $summary = null, $longDescription = null)
108
    {
109
        $newEntity = new TComplexTypeType();
110
        $newEntity->setName($name);
111
        $newEntity->setTypeAccess($accessType);
112
        $this->addDocumentation($summary, $longDescription, $newEntity);
113
        assert($newEntity->isOK($this->lastError), $this->lastError);
114
        $this->v3Edmx->getDataServiceType()->getSchema()[0]->addToComplexType($newEntity);
0 ignored issues
show
Compatibility introduced by
$newEntity of type object<AlgoWeb\ODataMetadata\IsOK> is not a sub-type of object<AlgoWeb\ODataMeta...3\edm\TComplexTypeType>. It seems like you assume a child class of the class AlgoWeb\ODataMetadata\IsOK to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
115
116
        return $newEntity;
117
    }
118
119
    public function getSerialiser()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
120
    {
121
        if (null == $this->serializer || is_string($this->serializer)) {
122
            $this->initSerialiser();
123
        }
124
        return $this->serializer;
125
    }
126
127
    public function addPropertyToComplexType(
128
        \AlgoWeb\ODataMetadata\MetadataV3\edm\TComplexTypeType $complexType,
129
        $name,
130
        $type,
131
        $defaultValue = null,
132
        $nullable = false,
133
        $summary = null,
134
        $longDescription = null
135
    )
136
    {
137
        if (is_array($defaultValue) || is_object($defaultValue)) {
138
            throw new \InvalidArgumentException('Default value cannot be object or array');
139
        }
140
        if (null != $defaultValue) {
141
            $defaultValue = var_export($defaultValue, true);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $defaultValue. This often makes code more readable.
Loading history...
142
        }
143
        $newProperty = new TComplexTypePropertyType();
144
        $newProperty->setName($name);
145
        $newProperty->setType($type);
146
        $newProperty->setNullable($nullable);
147
        $this->addDocumentation($summary, $longDescription, $newProperty);
148
        if (null != $defaultValue) {
149
            $newProperty->setDefaultValue($defaultValue);
150
        }
151
        assert($newProperty->isOK($this->lastError), $this->lastError);
152
        $complexType->addToProperty($newProperty);
0 ignored issues
show
Compatibility introduced by
$newProperty of type object<AlgoWeb\ODataMetadata\IsOK> is not a sub-type of object<AlgoWeb\ODataMeta...omplexTypePropertyType>. It seems like you assume a child class of the class AlgoWeb\ODataMetadata\IsOK to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
153
        return $newProperty;
154
    }
155
156
    /**
157
     * @param TEntityTypeType $entityType
158
     * @param $name
159
     * @param $type
160
     * @param null $defaultValue
161
     * @param bool $nullable
162
     * @param bool $isKey
163
     * @param null $storeGeneratedPattern
164
     * @param null $summary
165
     * @param null $longDescription
166
     * @return TEntityPropertyType
167
     */
168
    public function addPropertyToEntityType(
169
        TEntityTypeType $entityType,
170
        $name,
171
        $type,
172
        $defaultValue = null,
173
        $nullable = false,
174
        $isKey = false,
175
        $storeGeneratedPattern = null,
176
        $summary = null,
177
        $longDescription = null
178
    )
179
    {
180
        $newProperty = new TEntityPropertyType();
181
        $newProperty->setName($name);
182
        $newProperty->setType($type);
183
        $newProperty->setStoreGeneratedPattern($storeGeneratedPattern);
184
        $newProperty->setNullable($nullable);
185
        $this->addDocumentation($summary, $longDescription, $newProperty);
186
        if (null != $defaultValue) {
187
            $newProperty->setDefaultValue($defaultValue);
188
        }
189
        $entityType->addToProperty($newProperty);
0 ignored issues
show
Compatibility introduced by
$newProperty of type object<AlgoWeb\ODataMetadata\IsOK> is not a sub-type of object<AlgoWeb\ODataMeta...dm\TEntityPropertyType>. It seems like you assume a child class of the class AlgoWeb\ODataMetadata\IsOK to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
190
        if ($isKey) {
191
            $key = new TPropertyRefType();
192
            $key->setName($name);
193
            $entityType->addToKey($key);
194
        }
195
        return $newProperty;
196
    }
197
198
    /**
199
     * @param TEntityTypeType $principalType
200
     * @param  $principalMultiplicity
201
     * @param  $principalProperty
202
     * @param TEntityTypeType $dependentType
203
     * @param  $dependentMultiplicity
204
     * @param string $dependentProperty
205
     * @param array|null $principalConstraintProperty
206
     * @param array|null $dependentConstraintProperty
207
     * @param string $principalGetterAccess
208
     * @param string $principalSetterAccess
209
     * @param string $dependentGetterAccess
210
     * @param string $dependentSetterAccess
211
     * @param null $principalSummery
212
     * @param null $principalLongDescription
213
     * @param null $dependentSummery
214
     * @param null $dependentLongDescription
215
     * @return array<IsOK|null>
216
     */
217
    public function addNavigationPropertyToEntityType(
218
        TEntityTypeType $principalType,
219
        $principalMultiplicity,
220
        $principalProperty,
221
        TEntityTypeType $dependentType,
222
        $dependentMultiplicity,
223
        $dependentProperty = '',
224
        array $principalConstraintProperty = null,
225
        array $dependentConstraintProperty = null,
226
        $principalGetterAccess = 'Public',
227
        $principalSetterAccess = 'Public',
228
        $dependentGetterAccess = 'Public',
229
        $dependentSetterAccess = 'Public',
230
        $principalSummery = null,
231
        $principalLongDescription = null,
232
        $dependentSummery = null,
233
        $dependentLongDescription = null
234
    )
235
    {
236
        $principalEntitySetName = self::getResourceSetNameFromResourceType($principalType->getName());
237
        $dependentEntitySetName = self::getResourceSetNameFromResourceType($dependentType->getName());
238
        $relationName = $principalType->getName() . '_' . $principalProperty . '_'
239
            . $dependentType->getName() . '_' . $dependentProperty;
240
        $relationName = trim($relationName, '_');
241
242
        $namespace = $this->getNamespace();
243
        $relationFQName = $namespace . $relationName;
244
245
        $principalNavigationProperty = new TNavigationPropertyType();
246
        $principalNavigationProperty->setName($principalProperty);
247
        $principalNavigationProperty->setToRole(trim($dependentEntitySetName . '_' . $dependentProperty, '_'));
248
        $principalNavigationProperty->setFromRole($principalEntitySetName . '_' . $principalProperty);
249
        $principalNavigationProperty->setRelationship($relationFQName);
250
        $principalNavigationProperty->setGetterAccess($principalGetterAccess);
251
        $principalNavigationProperty->setSetterAccess($principalSetterAccess);
252
        $this->addDocumentation($principalSummery, $principalLongDescription, $principalNavigationProperty);
253
        $principalType->addToNavigationProperty($principalNavigationProperty);
0 ignored issues
show
Compatibility introduced by
$principalNavigationProperty of type object<AlgoWeb\ODataMetadata\IsOK> is not a sub-type of object<AlgoWeb\ODataMeta...NavigationPropertyType>. It seems like you assume a child class of the class AlgoWeb\ODataMetadata\IsOK to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
254
        $dependentNavigationProperty = null;
255
        if (!empty($dependentProperty)) {
256
            $dependentNavigationProperty = new TNavigationPropertyType();
257
            $dependentNavigationProperty->setName($dependentProperty);
258
            $dependentNavigationProperty->setToRole($principalEntitySetName . '_' . $principalProperty);
259
            $dependentNavigationProperty->setFromRole($dependentEntitySetName . '_' . $dependentProperty);
260
            $dependentNavigationProperty->setRelationship($relationFQName);
261
            $dependentNavigationProperty->setGetterAccess($dependentGetterAccess);
262
            $dependentNavigationProperty->setSetterAccess($dependentSetterAccess);
263
            $this->addDocumentation($dependentSummery, $dependentLongDescription, $dependentNavigationProperty);
264
            $dependentType->addToNavigationProperty($dependentNavigationProperty);
0 ignored issues
show
Compatibility introduced by
$dependentNavigationProperty of type object<AlgoWeb\ODataMetadata\IsOK> is not a sub-type of object<AlgoWeb\ODataMeta...NavigationPropertyType>. It seems like you assume a child class of the class AlgoWeb\ODataMetadata\IsOK to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
265
        }
266
267
        $assocation = $this->createAssocationFromNavigationProperty(
268
            $principalType,
269
            $dependentType,
270
            $principalNavigationProperty,
0 ignored issues
show
Compatibility introduced by
$principalNavigationProperty of type object<AlgoWeb\ODataMetadata\IsOK> is not a sub-type of object<AlgoWeb\ODataMeta...NavigationPropertyType>. It seems like you assume a child class of the class AlgoWeb\ODataMetadata\IsOK to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
271
            $dependentNavigationProperty,
0 ignored issues
show
Bug introduced by
It seems like $dependentNavigationProperty can also be of type object<AlgoWeb\ODataMetadata\IsOK>; however, AlgoWeb\ODataMetadata\Me...romNavigationProperty() does only seem to accept null|object<AlgoWeb\ODat...NavigationPropertyType>, 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...
272
            $principalMultiplicity,
273
            $dependentMultiplicity,
274
            $principalConstraintProperty,
275
            $dependentConstraintProperty
276
        );
277
278
        $this->v3Edmx->getDataServiceType()->getSchema()[0]->addToAssociation($assocation);
279
280
        $associationSet = $this->createAssocationSetForAssocation(
281
            $assocation,
282
            $principalEntitySetName,
283
            $dependentEntitySetName
284
        );
285
286
        $this->v3Edmx->getDataServiceType()->getSchema()[0]
287
            ->getEntityContainer()[0]->addToAssociationSet($associationSet);
288
289
        assert($this->v3Edmx->isOK($this->lastError), $this->lastError);
290
        return [$principalNavigationProperty, $dependentNavigationProperty];
291
    }
292
293
    /**
294
     * @param TEntityTypeType $principalType
295
     * @param TEntityTypeType $dependentType
296
     * @param TNavigationPropertyType $principalNavigationProperty
297
     * @param TNavigationPropertyType|null $dependentNavigationProperty
298
     * @param $principalMultiplicity
299
     * @param $dependentMultiplicity
300
     * @param array|null $principalConstraintProperty
301
     * @param array|null $dependentConstraintProperty
302
     * @return TAssociationType
303
     */
304
    protected function createAssocationFromNavigationProperty(
305
        TEntityTypeType $principalType,
306
        TEntityTypeType $dependentType,
307
        TNavigationPropertyType $principalNavigationProperty,
308
        TNavigationPropertyType $dependentNavigationProperty = null,
309
        $principalMultiplicity,
310
        $dependentMultiplicity,
311
        array $principalConstraintProperty = null,
312
        array $dependentConstraintProperty = null
313
    )
314
    {
315
        $multCombo = ['*' => ['*', '1', '0..1'], '0..1' => ['1', '*'], '1' => ['*', '0..1']];
316
        $multKeys = array_keys($multCombo);
317
        if (null != $dependentNavigationProperty) {
318
            if ($dependentNavigationProperty->getRelationship() != $principalNavigationProperty->getRelationship()) {
319
                $msg = 'If you have both a dependent property and a principal property,'
320
                    . ' relationship should match';
321
                throw new \InvalidArgumentException($msg);
322
            }
323
            if ($dependentNavigationProperty->getFromRole() != $principalNavigationProperty->getToRole()
324
                || $dependentNavigationProperty->getToRole() != $principalNavigationProperty->getFromRole()
325
            ) {
326
                throw new \InvalidArgumentException(
327
                    'Principal to role should match dependent from role, and vice versa'
328
                );
329
            }
330
        }
331
        if (!in_array($principalMultiplicity, $multKeys) || !in_array($dependentMultiplicity, $multKeys)) {
332
            throw new \InvalidArgumentException('Malformed multiplicity - valid values are *, 0..1 and 1');
333
        }
334
        if (!in_array($dependentMultiplicity, $multCombo[$principalMultiplicity])) {
335
            throw new \InvalidArgumentException(
336
                'Invalid multiplicity combination - ' . $principalMultiplicity . ' ' . $dependentMultiplicity
337
            );
338
        }
339
340
        $namespace = $this->getNamespace();
341
        $principalTypeFQName = $namespace . $principalType->getName();
342
        $dependentTypeFQName = $namespace . $dependentType->getName();
343
        $association = new TAssociationType();
344
        $relationship = $principalNavigationProperty->getRelationship();
345
        if (false !== strpos($relationship, '.')) {
346
            $relationship = substr($relationship, strpos($relationship, '.') + 1);
347
        }
348
349
        $principalTargRole = $principalNavigationProperty->getFromRole();
350
        $principalSrcRole = $principalNavigationProperty->getToRole();
351
        $dependentTargRole = null != $dependentNavigationProperty ? $dependentNavigationProperty->getFromRole() : null;
352
353
        $association->setName($relationship);
354
        $principalEnd = new TAssociationEndType();
355
        $principalEnd->setType($principalTypeFQName);
356
        $principalEnd->setRole($principalTargRole);
357
        $principalEnd->setMultiplicity($principalMultiplicity);
358
        $association->addToEnd($principalEnd);
359
        $dependentEnd = new TAssociationEndType();
360
        $dependentEnd->setType($dependentTypeFQName);
361
        $dependentEnd->setMultiplicity($dependentMultiplicity);
362
        $association->addToEnd($dependentEnd);
363
364
        $dependentEnd->setRole(null != $dependentNavigationProperty ? $dependentTargRole : $principalSrcRole);
365
366
        $hasPrincipalReferral = null != $principalConstraintProperty && 0 < count($principalConstraintProperty);
367
        $hasDependentReferral = null != $dependentConstraintProperty && 0 < count($dependentConstraintProperty);
368
369
        if ($hasPrincipalReferral && $hasDependentReferral) {
370
            $principalReferralConstraint = $this->makeReferentialConstraint(
371
                $principalConstraintProperty,
372
                $principalTargRole
373
            );
374
            $dependentReferralConstraint = $this->makeReferentialConstraint(
375
                $dependentConstraintProperty,
376
                $dependentTargRole
377
            );
378
            $constraint = new TConstraintType();
379
            $constraint->setPrincipal($principalReferralConstraint);
380
            $constraint->setDependent($dependentReferralConstraint);
381
            $association->setReferentialConstraint($constraint);
382
        }
383
        return $association;
384
    }
385
386
    /**
387
     * @param TAssociationType $association
388
     * @param string $principalEntitySetName
389
     * @param string $dependentEntitySetName
390
     * @return AssociationSetAnonymousType
391
     */
392
    protected function createAssocationSetForAssocation(
393
        TAssociationType $association,
394
        $principalEntitySetName,
395
        $dependentEntitySetName
396
    )
397
    {
398
        $as = new AssociationSetAnonymousType();
399
        $name = $association->getName();
400
        $as->setName($name);
401
        $namespace = $this->getNamespace();
402
        $associationSetName = $namespace . $association->getName();
403
        $as->setAssociation($associationSetName);
404
        $end1 = new EndAnonymousType();
405
        $end1->setRole($association->getEnd()[0]->getRole());
406
        $end1->setEntitySet($principalEntitySetName);
407
        $end2 = new EndAnonymousType();
408
        $end2->setRole($association->getEnd()[1]->getRole());
409
        $end2->setEntitySet($dependentEntitySetName);
410
        assert($end1->getRole() != $end2->getRole());
411
        $as->addToEnd($end1);
412
        $as->addToEnd($end2);
413
        return $as;
414
    }
415
416
    /**
417
     * @return string|null
418
     */
419
    public function getLastError()
420
    {
421
        return $this->lastError;
422
    }
423
424
    /**
425
     * @param string $name
426
     * @param IsOK $expectedReturnType
427
     * @param EntitySetAnonymousType|null $entitySet
428
     * @param TTextType|null $shortDesc
429
     * @param TTextType|null $longDesc
430
     * @return FunctionImportAnonymousType
431
     */
432
    public function createSingleton(
433
        $name,
434
        IsOK $expectedReturnType,
435
        EntitySetAnonymousType $entitySet = null,
436
        TTextType $shortDesc = null,
437
        TTextType $longDesc = null
438
    )
439
    {
440
        if (!($expectedReturnType instanceof TEntityTypeType) && !($expectedReturnType instanceof TComplexTypeType)) {
441
            $msg = 'Expected return type must be either TEntityType or TComplexType';
442
            throw new \InvalidArgumentException($msg);
443
        }
444
445
        if (!is_string($name) || empty($name)) {
446
            $msg = 'Name must be a non-empty string';
447
            throw new \InvalidArgumentException($msg);
448
        }
449
450
        $funcType = new FunctionImportAnonymousType();
451
        $funcType->setName($name);
452
453
        $namespace = $this->getNamespace();
454
        $typeName = $expectedReturnType->getName();
455
        $fqTypeName = $namespace . $typeName;
456
        $fqSetName = ($entitySet == null) ? $typeName : $entitySet->getName();
457
458
        $returnType = new TFunctionImportReturnTypeType();
459
        $returnType->setType($fqTypeName);
460
        $returnType->setEntitySetAttribute($fqSetName);
461
        assert($returnType->isOK($msg), $msg);
462
        $funcType->addToReturnType($returnType);
463
        $this->addDocumentation($shortDesc, $longDesc, $funcType);
464
465
        $this->getEdmx()->getDataServiceType()->getSchema()[0]->getEntityContainer()[0]->addToFunctionImport($funcType);
0 ignored issues
show
Compatibility introduced by
$funcType of type object<AlgoWeb\ODataMetadata\IsOK> is not a sub-type of object<AlgoWeb\ODataMeta...ionImportAnonymousType>. It seems like you assume a child class of the class AlgoWeb\ODataMetadata\IsOK to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
466
467
        return $funcType;
468
    }
469
470
    protected function initSerialiser()
471
    {
472
        $ymlDir = __DIR__ . DIRECTORY_SEPARATOR . 'MetadataV3' . DIRECTORY_SEPARATOR . 'JMSmetadata';
473
        $this->serializer =
474
            SerializerBuilder::create()
475
                ->addMetadataDir($ymlDir)
476
                ->build();
477
    }
478
479
    public function __sleep()
480
    {
481
        $this->serializer = serialize(self::$typeNameToSetName);
482
        $result = array_keys(get_object_vars($this));
483
        return $result;
484
    }
485
486
    public function __wakeup()
487
    {
488
        self::$typeNameToSetName = unserialize($this->serializer);
489
    }
490
491
    public static function getResourceSetNameFromResourceType($typeName)
492
    {
493
        return self::$typeNameToSetName->getValue($typeName);
494
    }
495
496
    public static function getResourceTypeNameFromResourceSet($setName)
497
    {
498
        return self::$typeNameToSetName->getKey($setName);
499
    }
500
501
    /**
502
     * @param $summary
503
     * @param $longDescription
504
     * @return TDocumentationType
505
     */
506
    private function generateDocumentation(TTextType $summary, TTextType $longDescription)
507
    {
508
        $documentation = new TDocumentationType();
509
        $documentation->setSummary($summary);
510
        $documentation->setLongDescription($longDescription);
511
        return $documentation;
512
    }
513
514
    /**
515
     * @return string
516
     */
517
    protected function getNamespace()
518
    {
519
        $namespace = $this->v3Edmx->getDataServiceType()->getSchema()[0]->getNamespace();
520
        if (0 == strlen(trim($namespace))) {
521
            $namespace = '';
522
        } else {
523
            $namespace .= '.';
524
        }
525
        return $namespace;
526
    }
527
528
    /**
529
     * @param array $constraintProperty
530
     * @param string $targRole
531
     * @return TReferentialConstraintRoleElementType
532
     */
533
    protected function makeReferentialConstraint(array $constraintProperty, $targRole)
534
    {
535
        assert(!empty($constraintProperty));
536
        assert(is_string($targRole));
537
        $referralConstraint = new TReferentialConstraintRoleElementType();
538
        $referralConstraint->setRole($targRole);
539
        foreach ($constraintProperty as $propertyRef) {
540
            $tPropertyRef = new TPropertyRefType();
541
            $tPropertyRef->setName($propertyRef);
542
            $referralConstraint->addToPropertyRef($tPropertyRef);
543
        }
544
        return $referralConstraint;
545
    }
546
547
    /**
548
     * @param $summary
549
     * @param $longDescription
550
     * @param $newEntity
551
     */
552
    private function addDocumentation($summary, $longDescription, IsOK & $newEntity)
553
    {
554
        if (null != $summary && null != $longDescription) {
555
            $documentation = $this->generateDocumentation($summary, $longDescription);
556
            if (method_exists($newEntity, 'addToDocumentation')) {
557
                $newEntity->addToDocumentation($documentation);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class AlgoWeb\ODataMetadata\IsOK as the method addToDocumentation() does only exist in the following sub-classes of AlgoWeb\ODataMetadata\IsOK: AlgoWeb\ODataMetadata\Me...\EntitySetAnonymousType, AlgoWeb\ODataMetadata\Me...ComplexTypePropertyType, AlgoWeb\ODataMetadata\Me...edm\TEntityPropertyType. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
558
            } else {
559
                $newEntity->setDocumentation($documentation);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class AlgoWeb\ODataMetadata\IsOK as the method setDocumentation() does only exist in the following sub-classes of AlgoWeb\ODataMetadata\IsOK: AlgoWeb\ODataMetadata\Me...aV1\edm\EntityContainer, AlgoWeb\ODataMetadata\Me...ciationSetAnonymousType, AlgoWeb\ODataMetadata\Me...usType\EndAnonymousType, AlgoWeb\ODataMetadata\Me...\EntitySetAnonymousType, AlgoWeb\ODataMetadata\Me...tionImportAnonymousType, AlgoWeb\ODataMetadata\Me...edm\TAssociationEndType, AlgoWeb\ODataMetadata\Me...V1\edm\TAssociationType, AlgoWeb\ODataMetadata\Me...ComplexTypePropertyType, AlgoWeb\ODataMetadata\Me...V1\edm\TComplexTypeType, AlgoWeb\ODataMetadata\Me...aV1\edm\TConstraintType, AlgoWeb\ODataMetadata\Me...edm\TEntityPropertyType, AlgoWeb\ODataMetadata\Me...aV1\edm\TEntityTypeType, AlgoWeb\ODataMetadata\Me...tionImportParameterType, AlgoWeb\ODataMetadata\Me...TNavigationPropertyType, AlgoWeb\ODataMetadata\MetadataV1\edm\TOnActionType, AlgoWeb\ODataMetadata\MetadataV1\edm\TUsingType, AlgoWeb\ODataMetadata\Me...dm\ssdl\EntityContainer, AlgoWeb\ODataMetadata\Me...ciationSetAnonymousType, AlgoWeb\ODataMetadata\Me...usType\EndAnonymousType, AlgoWeb\ODataMetadata\Me...\EntitySetAnonymousType, AlgoWeb\ODataMetadata\Me...sdl\TAssociationEndType, AlgoWeb\ODataMetadata\Me...m\ssdl\TAssociationType, AlgoWeb\ODataMetadata\Me...dm\ssdl\TConstraintType, AlgoWeb\ODataMetadata\Me...sdl\TEntityPropertyType, AlgoWeb\ODataMetadata\Me...dm\ssdl\TEntityTypeType, AlgoWeb\ODataMetadata\Me...\edm\ssdl\TFunctionType, AlgoWeb\ODataMetadata\Me...\edm\ssdl\TOnActionType, AlgoWeb\ODataMetadata\Me...edm\ssdl\TParameterType, AlgoWeb\ODataMetadata\Me...m\ssdl\TPropertyRefType, AlgoWeb\ODataMetadata\Me...nstraintRoleElementType, AlgoWeb\ODataMetadata\Me...aV2\edm\EntityContainer, AlgoWeb\ODataMetadata\Me...ciationSetAnonymousType, AlgoWeb\ODataMetadata\Me...usType\EndAnonymousType, AlgoWeb\ODataMetadata\Me...\EntitySetAnonymousType, AlgoWeb\ODataMetadata\Me...tionImportAnonymousType, AlgoWeb\ODataMetadata\Me...edm\TAssociationEndType, AlgoWeb\ODataMetadata\Me...V2\edm\TAssociationType, AlgoWeb\ODataMetadata\Me...ComplexTypePropertyType, AlgoWeb\ODataMetadata\Me...V2\edm\TComplexTypeType, AlgoWeb\ODataMetadata\Me...aV2\edm\TConstraintType, AlgoWeb\ODataMetadata\Me...edm\TEntityPropertyType, AlgoWeb\ODataMetadata\Me...aV2\edm\TEntityTypeType, AlgoWeb\ODataMetadata\Me...tionImportParameterType, AlgoWeb\ODataMetadata\MetadataV2\edm\TFunctionType, AlgoWeb\ODataMetadata\Me...TNavigationPropertyType, AlgoWeb\ODataMetadata\MetadataV2\edm\TOnActionType, AlgoWeb\ODataMetadata\Me...\edm\TReferenceTypeType, AlgoWeb\ODataMetadata\MetadataV2\edm\TTypeRefType, AlgoWeb\ODataMetadata\MetadataV2\edm\TUsingType, AlgoWeb\ODataMetadata\Me...dm\ssdl\EntityContainer, AlgoWeb\ODataMetadata\Me...ciationSetAnonymousType, AlgoWeb\ODataMetadata\Me...usType\EndAnonymousType, AlgoWeb\ODataMetadata\Me...\EntitySetAnonymousType, AlgoWeb\ODataMetadata\Me...sdl\TAssociationEndType, AlgoWeb\ODataMetadata\Me...m\ssdl\TAssociationType, AlgoWeb\ODataMetadata\Me...dm\ssdl\TConstraintType, AlgoWeb\ODataMetadata\Me...sdl\TEntityPropertyType, AlgoWeb\ODataMetadata\Me...dm\ssdl\TEntityTypeType, AlgoWeb\ODataMetadata\Me...\edm\ssdl\TFunctionType, AlgoWeb\ODataMetadata\Me...\edm\ssdl\TOnActionType, AlgoWeb\ODataMetadata\Me...edm\ssdl\TParameterType, AlgoWeb\ODataMetadata\Me...m\ssdl\TPropertyRefType, AlgoWeb\ODataMetadata\Me...nstraintRoleElementType, AlgoWeb\ODataMetadata\Me...aV3\edm\EntityContainer, AlgoWeb\ODataMetadata\Me...ciationSetAnonymousType, AlgoWeb\ODataMetadata\Me...usType\EndAnonymousType, AlgoWeb\ODataMetadata\Me...\EntitySetAnonymousType, AlgoWeb\ODataMetadata\Me...tionImportAnonymousType, AlgoWeb\ODataMetadata\Me...edm\TAssociationEndType, AlgoWeb\ODataMetadata\Me...V3\edm\TAssociationType, AlgoWeb\ODataMetadata\Me...ComplexTypePropertyType, AlgoWeb\ODataMetadata\Me...V3\edm\TComplexTypeType, AlgoWeb\ODataMetadata\Me...aV3\edm\TConstraintType, AlgoWeb\ODataMetadata\Me...edm\TEntityPropertyType, AlgoWeb\ODataMetadata\Me...aV3\edm\TEntityTypeType, AlgoWeb\ODataMetadata\Me...edm\TEnumTypeMemberType, AlgoWeb\ODataMetadata\MetadataV3\edm\TEnumTypeType, AlgoWeb\ODataMetadata\Me...tionImportParameterType, AlgoWeb\ODataMetadata\MetadataV3\edm\TFunctionType, AlgoWeb\ODataMetadata\Me...TNavigationPropertyType, AlgoWeb\ODataMetadata\MetadataV3\edm\TOnActionType, AlgoWeb\ODataMetadata\Me...\edm\TReferenceTypeType, AlgoWeb\ODataMetadata\MetadataV3\edm\TTypeRefType, AlgoWeb\ODataMetadata\MetadataV3\edm\TUsingType, AlgoWeb\ODataMetadata\Me...taV3\edm\TValueTermType, AlgoWeb\ODataMetadata\Me...dm\ssdl\EntityContainer, AlgoWeb\ODataMetadata\Me...ciationSetAnonymousType, AlgoWeb\ODataMetadata\Me...usType\EndAnonymousType, AlgoWeb\ODataMetadata\Me...\EntitySetAnonymousType, AlgoWeb\ODataMetadata\Me...sdl\TAssociationEndType, AlgoWeb\ODataMetadata\Me...m\ssdl\TAssociationType, AlgoWeb\ODataMetadata\Me...dm\ssdl\TConstraintType, AlgoWeb\ODataMetadata\Me...sdl\TEntityPropertyType, AlgoWeb\ODataMetadata\Me...dm\ssdl\TEntityTypeType, AlgoWeb\ODataMetadata\Me...\edm\ssdl\TFunctionType, AlgoWeb\ODataMetadata\Me...\edm\ssdl\TOnActionType, AlgoWeb\ODataMetadata\Me...edm\ssdl\TParameterType, AlgoWeb\ODataMetadata\Me...m\ssdl\TPropertyRefType, AlgoWeb\ODataMetadata\Me...\edm\ssdl\TPropertyType, AlgoWeb\ODataMetadata\Me...nstraintRoleElementType. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
560
            }
561
        }
562
    }
563
564
    public function pluralize($string): string
565
    {
566
        if (class_exists('Doctrine\Inflector\InflectorFactory')) {
567
            return Doctrine\Inflector\InflectorFactory::create()->build()->pluralize($string);
568
        }
569
        return Inflector::pluralize($string);
570
    }
571
}
572