Completed
Push — master ( 819957...3609f8 )
by Alex
55s queued 20s
created

getResourceSetNameFromResourceType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
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 Illuminate\Support\Str;
24
use JMS\Serializer\SerializerBuilder;
25
26
class MetadataManager
27
{
28
    private $v3Edmx = null;
29
    private $lastError = null;
30
    private $serializer = null;
31
    private static $typeNameToSetName = null;
32
33
    public function __construct($namespaceName = 'Data', $containerName = 'DefaultContainer', Edmx $edmx = null)
34
    {
35
        $msg = null;
36
        $this->v3Edmx = (null == $edmx) ? new Edmx($namespaceName, $containerName) : $edmx;
37
        assert($this->v3Edmx->isOK($msg), $msg);
38
        $this->initSerialiser();
39
        assert(null != $this->serializer, 'Serializer must not be null at end of constructor');
40
        self::$typeNameToSetName = new BidirectionalMap();
41
    }
42
43
    public function getEdmx()
44
    {
45
        $msg = null;
46
        assert($this->v3Edmx->isOK($msg), $msg);
47
        return $this->v3Edmx;
48
    }
49
50
    /**
51
     * @return \JMS\Serializer\Serializer
52
     */
53
    public function getEdmxXML()
54
    {
55
        if (null == $this->serializer) {
56
            $this->initSerialiser();
57
        }
58
        $cereal = $this->getSerialiser();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $cereal is correct as $this->getSerialiser() (which targets AlgoWeb\ODataMetadata\Me...anager::getSerialiser()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
59
        assert(null != $cereal, 'Serializer must not be null when trying to get edmx xml');
60
        return $cereal->serialize($this->getEdmx(), 'xml');
0 ignored issues
show
Bug introduced by
The method serialize cannot be called on $cereal (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
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
        $newEntity = new TEntityTypeType();
83
        $newEntity->setName($name);
84
        $this->addDocumentation($summary, $longDescription, $newEntity);
85
        $newEntity->setAbstract($isAbstract);
86
        $newEntity->setBaseType(null === $baseType ? null:$this->getNamespace() . $baseType->getName());
87
88
        if (null === $pluralName) {
89
            $pluralName = Str::plural($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...
90
        }
91
92
        $entitySet = new EntitySetAnonymousType();
93
        $entitySet->setName($pluralName);
94
        $namespace = $this->getNamespace();
95
        $entityTypeName = $namespace . $newEntity->getName();
96
        $entitySet->setEntityType($entityTypeName);
97
        $entitySet->setGetterAccess($accessType);
98
99
        $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...
100
        $this->v3Edmx->getDataServiceType()->getSchema()[0]->getEntityContainer()[0]->addToEntitySet($entitySet);
101
        assert($this->v3Edmx->isOK($this->lastError), $this->lastError);
102
        self::$typeNameToSetName->put($name, $pluralName);
103
        return [$newEntity, $entitySet];
104
    }
105
106
    public function addComplexType($name, $accessType = 'Public', $summary = null, $longDescription = null)
107
    {
108
        $newEntity = new TComplexTypeType();
109
        $newEntity->setName($name);
110
        $newEntity->setTypeAccess($accessType);
111
        $this->addDocumentation($summary, $longDescription, $newEntity);
112
        assert($newEntity->isOK($this->lastError), $this->lastError);
113
        $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...
114
115
        return $newEntity;
116
    }
117
118
    public function getSerialiser()
119
    {
120
        return $this->serializer;
121
    }
122
123
    public function addPropertyToComplexType(
124
        \AlgoWeb\ODataMetadata\MetadataV3\edm\TComplexTypeType $complexType,
125
        $name,
126
        $type,
127
        $defaultValue = null,
128
        $nullable = false,
129
        $summary = null,
130
        $longDescription = null
131
    ) {
132
        if (is_array($defaultValue) || is_object($defaultValue)) {
133
            throw new \InvalidArgumentException('Default value cannot be object or array');
134
        }
135
        if (null != $defaultValue) {
136
            $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...
137
        }
138
        $newProperty = new TComplexTypePropertyType();
139
        $newProperty->setName($name);
140
        $newProperty->setType($type);
141
        $newProperty->setNullable($nullable);
142
        $this->addDocumentation($summary, $longDescription, $newProperty);
143
        if (null != $defaultValue) {
144
            $newProperty->setDefaultValue($defaultValue);
145
        }
146
        assert($newProperty->isOK($this->lastError), $this->lastError);
147
        $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...
148
        return $newProperty;
149
    }
150
151
    /**
152
     * @param TEntityTypeType $entityType
153
     * @param $name
154
     * @param $type
155
     * @param  null                $defaultValue
156
     * @param  bool                $nullable
157
     * @param  bool                $isKey
158
     * @param  null                $storeGeneratedPattern
159
     * @param  null                $summary
160
     * @param  null                $longDescription
161
     * @return TEntityPropertyType
162
     */
163
    public function addPropertyToEntityType(
164
        TEntityTypeType $entityType,
165
        $name,
166
        $type,
167
        $defaultValue = null,
168
        $nullable = false,
169
        $isKey = false,
170
        $storeGeneratedPattern = null,
171
        $summary = null,
172
        $longDescription = null
173
    ) {
174
        $newProperty = new TEntityPropertyType();
175
        $newProperty->setName($name);
176
        $newProperty->setType($type);
177
        $newProperty->setStoreGeneratedPattern($storeGeneratedPattern);
178
        $newProperty->setNullable($nullable);
179
        $this->addDocumentation($summary, $longDescription, $newProperty);
180
        if (null != $defaultValue) {
181
            $newProperty->setDefaultValue($defaultValue);
182
        }
183
        $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...
184
        if ($isKey) {
185
            $key = new TPropertyRefType();
186
            $key->setName($name);
187
            $entityType->addToKey($key);
188
        }
189
        return $newProperty;
190
    }
191
192
    /**
193
     * @param TEntityTypeType $principalType
194
     * @param  $principalMultiplicity
195
     * @param  $principalProperty
196
     * @param TEntityTypeType $dependentType
197
     * @param  $dependentMultiplicity
198
     * @param  string           $dependentProperty
199
     * @param  array|null       $principalConstraintProperty
200
     * @param  array|null       $dependentConstraintProperty
201
     * @param  string           $principalGetterAccess
202
     * @param  string           $principalSetterAccess
203
     * @param  string           $dependentGetterAccess
204
     * @param  string           $dependentSetterAccess
205
     * @param  null             $principalSummery
206
     * @param  null             $principalLongDescription
207
     * @param  null             $dependentSummery
208
     * @param  null             $dependentLongDescription
209
     * @return array<IsOK|null>
210
     */
211
    public function addNavigationPropertyToEntityType(
212
        TEntityTypeType $principalType,
213
        $principalMultiplicity,
214
        $principalProperty,
215
        TEntityTypeType $dependentType,
216
        $dependentMultiplicity,
217
        $dependentProperty = '',
218
        array $principalConstraintProperty = null,
219
        array $dependentConstraintProperty = null,
220
        $principalGetterAccess = 'Public',
221
        $principalSetterAccess = 'Public',
222
        $dependentGetterAccess = 'Public',
223
        $dependentSetterAccess = 'Public',
224
        $principalSummery = null,
225
        $principalLongDescription = null,
226
        $dependentSummery = null,
227
        $dependentLongDescription = null
228
    ) {
229
        $principalEntitySetName = self::getResourceSetNameFromResourceType($principalType->getName());
230
        $dependentEntitySetName = self::getResourceSetNameFromResourceType($dependentType->getName());
231
        $relationName = $principalType->getName() . '_' . $principalProperty . '_'
232
                        . $dependentType->getName() . '_' . $dependentProperty;
233
        $relationName = trim($relationName, '_');
234
235
        $namespace = $this->getNamespace();
236
        $relationFQName = $namespace . $relationName;
237
238
        $principalNavigationProperty = new TNavigationPropertyType();
239
        $principalNavigationProperty->setName($principalProperty);
240
        $principalNavigationProperty->setToRole(trim($dependentEntitySetName . '_' . $dependentProperty, '_'));
241
        $principalNavigationProperty->setFromRole($principalEntitySetName . '_' . $principalProperty);
242
        $principalNavigationProperty->setRelationship($relationFQName);
243
        $principalNavigationProperty->setGetterAccess($principalGetterAccess);
244
        $principalNavigationProperty->setSetterAccess($principalSetterAccess);
245
        $this->addDocumentation($principalSummery, $principalLongDescription, $principalNavigationProperty);
246
        $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...
247
        $dependentNavigationProperty = null;
248
        if (!empty($dependentProperty)) {
249
            $dependentNavigationProperty = new TNavigationPropertyType();
250
            $dependentNavigationProperty->setName($dependentProperty);
251
            $dependentNavigationProperty->setToRole($principalEntitySetName . '_' . $principalProperty);
252
            $dependentNavigationProperty->setFromRole($dependentEntitySetName . '_' . $dependentProperty);
253
            $dependentNavigationProperty->setRelationship($relationFQName);
254
            $dependentNavigationProperty->setGetterAccess($dependentGetterAccess);
255
            $dependentNavigationProperty->setSetterAccess($dependentSetterAccess);
256
            $this->addDocumentation($dependentSummery, $dependentLongDescription, $dependentNavigationProperty);
257
            $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...
258
        }
259
260
        $assocation = $this->createAssocationFromNavigationProperty(
261
            $principalType,
262
            $dependentType,
263
            $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...
264
            $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...
265
            $principalMultiplicity,
266
            $dependentMultiplicity,
267
            $principalConstraintProperty,
268
            $dependentConstraintProperty
269
        );
270
271
        $this->v3Edmx->getDataServiceType()->getSchema()[0]->addToAssociation($assocation);
272
273
        $associationSet = $this->createAssocationSetForAssocation(
274
            $assocation,
275
            $principalEntitySetName,
276
            $dependentEntitySetName
277
        );
278
279
        $this->v3Edmx->getDataServiceType()->getSchema()[0]
280
            ->getEntityContainer()[0]->addToAssociationSet($associationSet);
281
282
        assert($this->v3Edmx->isOK($this->lastError), $this->lastError);
283
        return [$principalNavigationProperty, $dependentNavigationProperty];
284
    }
285
286
    /**
287
     * @param TEntityTypeType              $principalType
288
     * @param TEntityTypeType              $dependentType
289
     * @param TNavigationPropertyType      $principalNavigationProperty
290
     * @param TNavigationPropertyType|null $dependentNavigationProperty
291
     * @param $principalMultiplicity
292
     * @param $dependentMultiplicity
293
     * @param  array|null       $principalConstraintProperty
294
     * @param  array|null       $dependentConstraintProperty
295
     * @return TAssociationType
296
     */
297
    protected function createAssocationFromNavigationProperty(
298
        TEntityTypeType $principalType,
299
        TEntityTypeType $dependentType,
300
        TNavigationPropertyType $principalNavigationProperty,
301
        TNavigationPropertyType $dependentNavigationProperty = null,
302
        $principalMultiplicity,
0 ignored issues
show
Coding Style introduced by
Parameters which have default values should be placed at the end.

If you place a parameter with a default value before a parameter with a default value, the default value of the first parameter will never be used as it will always need to be passed anyway:

// $a must always be passed; it's default value is never used.
function someFunction($a = 5, $b) { }
Loading history...
303
        $dependentMultiplicity,
304
        array $principalConstraintProperty = null,
305
        array $dependentConstraintProperty = null
306
    ) {
307
        $multCombo = ['*' => ['*', '1', '0..1'], '0..1' => ['1', '*'], '1' => ['*', '0..1']];
308
        $multKeys = array_keys($multCombo);
309
        if (null != $dependentNavigationProperty) {
310
            if ($dependentNavigationProperty->getRelationship() != $principalNavigationProperty->getRelationship()) {
311
                $msg = 'If you have both a dependent property and a principal property,'
312
                       .' relationship should match';
313
                throw new \InvalidArgumentException($msg);
314
            }
315
            if ($dependentNavigationProperty->getFromRole() != $principalNavigationProperty->getToRole()
316
                || $dependentNavigationProperty->getToRole() != $principalNavigationProperty->getFromRole()
317
            ) {
318
                throw new \InvalidArgumentException(
319
                    'Principal to role should match dependent from role, and vice versa'
320
                );
321
            }
322
        }
323
        if (!in_array($principalMultiplicity, $multKeys) || !in_array($dependentMultiplicity, $multKeys)) {
324
            throw new \InvalidArgumentException('Malformed multiplicity - valid values are *, 0..1 and 1');
325
        }
326
        if (!in_array($dependentMultiplicity, $multCombo[$principalMultiplicity])) {
327
            throw new \InvalidArgumentException(
328
                'Invalid multiplicity combination - ' . $principalMultiplicity . ' ' . $dependentMultiplicity
329
            );
330
        }
331
332
        $namespace = $this->getNamespace();
333
        $principalTypeFQName = $namespace . $principalType->getName();
334
        $dependentTypeFQName = $namespace . $dependentType->getName();
335
        $association = new TAssociationType();
336
        $relationship = $principalNavigationProperty->getRelationship();
337
        if (false !== strpos($relationship, '.')) {
338
            $relationship = substr($relationship, strpos($relationship, '.') + 1);
339
        }
340
341
        $principalTargRole = $principalNavigationProperty->getFromRole();
342
        $principalSrcRole = $principalNavigationProperty->getToRole();
343
        $dependentTargRole = null != $dependentNavigationProperty ? $dependentNavigationProperty->getFromRole() : null;
344
345
        $association->setName($relationship);
346
        $principalEnd = new TAssociationEndType();
347
        $principalEnd->setType($principalTypeFQName);
348
        $principalEnd->setRole($principalTargRole);
349
        $principalEnd->setMultiplicity($principalMultiplicity);
350
        $association->addToEnd($principalEnd);
351
        $dependentEnd = new TAssociationEndType();
352
        $dependentEnd->setType($dependentTypeFQName);
353
        $dependentEnd->setMultiplicity($dependentMultiplicity);
354
        $association->addToEnd($dependentEnd);
355
356
        $dependentEnd->setRole(null != $dependentNavigationProperty ? $dependentTargRole : $principalSrcRole);
357
358
        $hasPrincipalReferral = null != $principalConstraintProperty && 0 < count($principalConstraintProperty);
359
        $hasDependentReferral = null != $dependentConstraintProperty && 0 < count($dependentConstraintProperty);
360
361
        if ($hasPrincipalReferral && $hasDependentReferral) {
362
            $principalReferralConstraint = $this->makeReferentialConstraint(
363
                $principalConstraintProperty,
364
                $principalTargRole
365
            );
366
            $dependentReferralConstraint = $this->makeReferentialConstraint(
367
                $dependentConstraintProperty,
368
                $dependentTargRole
369
            );
370
            $constraint = new TConstraintType();
371
            $constraint->setPrincipal($principalReferralConstraint);
372
            $constraint->setDependent($dependentReferralConstraint);
373
            $association->setReferentialConstraint($constraint);
374
        }
375
        return $association;
376
    }
377
378
    /**
379
     * @param  TAssociationType            $association
380
     * @param  string                      $principalEntitySetName
381
     * @param  string                      $dependentEntitySetName
382
     * @return AssociationSetAnonymousType
383
     */
384
    protected function createAssocationSetForAssocation(
385
        TAssociationType $association,
386
        $principalEntitySetName,
387
        $dependentEntitySetName
388
    ) {
389
        $as = new AssociationSetAnonymousType();
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $as. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
390
        $name = $association->getName();
391
        $as->setName($name);
392
        $namespace = $this->getNamespace();
393
        $associationSetName = $namespace . $association->getName();
394
        $as->setAssociation($associationSetName);
395
        $end1 = new EndAnonymousType();
396
        $end1->setRole($association->getEnd()[0]->getRole());
397
        $end1->setEntitySet($principalEntitySetName);
398
        $end2 = new EndAnonymousType();
399
        $end2->setRole($association->getEnd()[1]->getRole());
400
        $end2->setEntitySet($dependentEntitySetName);
401
        assert($end1->getRole() != $end2->getRole());
402
        $as->addToEnd($end1);
403
        $as->addToEnd($end2);
404
        return $as;
405
    }
406
407
    /**
408
     * @return string|null
409
     */
410
    public function getLastError()
411
    {
412
        return $this->lastError;
413
    }
414
415
    /**
416
     * @param  string                      $name
417
     * @param  IsOK                        $expectedReturnType
418
     * @param  EntitySetAnonymousType|null $entitySet
419
     * @param  TTextType|null              $shortDesc
420
     * @param  TTextType|null              $longDesc
421
     * @return FunctionImportAnonymousType
422
     */
423
    public function createSingleton(
424
        $name,
425
        IsOK $expectedReturnType,
426
        EntitySetAnonymousType $entitySet = null,
427
        TTextType $shortDesc = null,
428
        TTextType $longDesc = null
429
    ) {
430
        if (!($expectedReturnType instanceof TEntityTypeType) && !($expectedReturnType instanceof TComplexTypeType)) {
431
            $msg = 'Expected return type must be either TEntityType or TComplexType';
432
            throw new \InvalidArgumentException($msg);
433
        }
434
435
        if (!is_string($name) || empty($name)) {
436
            $msg = 'Name must be a non-empty string';
437
            throw new \InvalidArgumentException($msg);
438
        }
439
440
        $funcType = new FunctionImportAnonymousType();
441
        $funcType->setName($name);
442
443
        $namespace = $this->getNamespace();
444
        $typeName = $expectedReturnType->getName();
445
        $fqTypeName = $namespace.$typeName;
446
        $fqSetName = ($entitySet == null) ? $typeName : $entitySet->getName();
447
448
        $returnType = new TFunctionImportReturnTypeType();
449
        $returnType->setType($fqTypeName);
450
        $returnType->setEntitySetAttribute($fqSetName);
451
        assert($returnType->isOK($msg), $msg);
452
        $funcType->addToReturnType($returnType);
453
        $this->addDocumentation($shortDesc, $longDesc, $funcType);
454
455
        $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...
456
457
        return $funcType;
458
    }
459
460
    protected function initSerialiser()
461
    {
462
        $ymlDir = __DIR__ . DIRECTORY_SEPARATOR . 'MetadataV3' . DIRECTORY_SEPARATOR . 'JMSmetadata';
463
        $this->serializer =
464
            SerializerBuilder::create()
465
                ->addMetadataDir($ymlDir)
466
                ->build();
467
    }
468
469
    public function __sleep()
470
    {
471
        $this->serializer = null;
472
        $result = array_keys(get_object_vars($this));
473
        return $result;
474
    }
475
476
    public function __wakeup()
477
    {
478
        $this->initSerialiser();
479
    }
480
481
    public static function getResourceSetNameFromResourceType($typeName)
482
    {
483
        return self::$typeNameToSetName->getValue($typeName);
484
    }
485
486
    public static function getResourceTypeNameFromResourceSet($setName)
487
    {
488
        return self::$typeNameToSetName->getKey($setName);
489
    }
490
491
    /**
492
     * @param $summary
493
     * @param $longDescription
494
     * @return TDocumentationType
495
     */
496
    private function generateDocumentation(TTextType $summary, TTextType $longDescription)
497
    {
498
        $documentation = new TDocumentationType();
499
        $documentation->setSummary($summary);
500
        $documentation->setLongDescription($longDescription);
501
        return $documentation;
502
    }
503
504
    /**
505
     * @return string
506
     */
507
    protected function getNamespace()
508
    {
509
        $namespace = $this->v3Edmx->getDataServiceType()->getSchema()[0]->getNamespace();
510
        if (0 == strlen(trim($namespace))) {
511
            $namespace = '';
512
        } else {
513
            $namespace .= '.';
514
        }
515
        return $namespace;
516
    }
517
518
    /**
519
     * @param  array                                 $constraintProperty
520
     * @param  string                                $targRole
521
     * @return TReferentialConstraintRoleElementType
522
     */
523
    protected function makeReferentialConstraint(array $constraintProperty, $targRole)
524
    {
525
        assert(!empty($constraintProperty));
526
        assert(is_string($targRole));
527
        $referralConstraint = new TReferentialConstraintRoleElementType();
528
        $referralConstraint->setRole($targRole);
529
        foreach ($constraintProperty as $propertyRef) {
530
            $tPropertyRef = new TPropertyRefType();
531
            $tPropertyRef->setName($propertyRef);
532
            $referralConstraint->addToPropertyRef($tPropertyRef);
533
        }
534
        return $referralConstraint;
535
    }
536
537
    /**
538
     * @param $summary
539
     * @param $longDescription
540
     * @param $newEntity
541
     */
542
    private function addDocumentation($summary, $longDescription, IsOK & $newEntity)
543
    {
544
        if (null != $summary && null != $longDescription) {
545
            $documentation = $this->generateDocumentation($summary, $longDescription);
546
            if (method_exists($newEntity, 'addToDocumentation')) {
547
                $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...
548
            } else {
549
                $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...
550
            }
551
        }
552
    }
553
}
554