Passed
Pull Request — master (#106)
by Alex
04:40
created

MetadataManager::addEntityType()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 21
Code Lines 16

Duplication

Lines 4
Ratio 19.05 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 4
loc 21
rs 9.3142
cc 3
eloc 16
nc 2
nop 4
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\TFunctionReturnTypeType;
19
use AlgoWeb\ODataMetadata\MetadataV3\edm\TFunctionType;
20
use AlgoWeb\ODataMetadata\MetadataV3\edm\TNavigationPropertyType;
21
use AlgoWeb\ODataMetadata\MetadataV3\edm\TPropertyRefType;
22
use AlgoWeb\ODataMetadata\MetadataV3\edm\TReferentialConstraintRoleElementType;
23
use AlgoWeb\ODataMetadata\MetadataV3\edm\TTextType;
24
use AlgoWeb\ODataMetadata\MetadataV3\edmx\Edmx;
25
use Illuminate\Support\Str;
26
use JMS\Serializer\SerializerBuilder;
27
28
class MetadataManager
29
{
30
    private $V3Edmx = null;
31
    private $lastError = null;
32
    private $serializer = 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
    }
42
43
    public function getEdmx()
44
    {
45
        $msg = null;
46
        assert($this->V3Edmx->isOK($msg), $msg);
47
        return $this->V3Edmx;
48
    }
49
50
    public function getEdmxXML()
51
    {
52
        assert(null != $this->getSerialiser(), "Serializer must not be null when trying to get edmx xml");
53
        return $this->getSerialiser()->serialize($this->getEdmx(), "xml");
0 ignored issues
show
Bug introduced by
The method serialize cannot be called on $this->getSerialiser() (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...
54
    }
55
56
    public function addEntityType($name, $accessType = "Public", $summary = null, $longDescription = null)
57
    {
58
        $NewEntity = new TEntityTypeType();
59
        $NewEntity->setName($name);
60 View Code Duplication
        if (null != $summary && null != $longDescription) {
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...
61
            $documentation = $this->generateDocumentation($summary, $longDescription);
62
            $NewEntity->setDocumentation($documentation);
63
        }
64
65
        $entitySet = new EntitySetAnonymousType();
66
        $entitySet->setName(Str::plural($NewEntity->getName()));
67
        $namespace = $this->getNamespace();
68
        $entityTypeName = $namespace . $NewEntity->getName();
69
        $entitySet->setEntityType($entityTypeName);
70
        $entitySet->setGetterAccess($accessType);
71
72
        $this->V3Edmx->getDataServiceType()->getSchema()[0]->addToEntityType($NewEntity);
73
        $this->V3Edmx->getDataServiceType()->getSchema()[0]->getEntityContainer()[0]->addToEntitySet($entitySet);
74
        assert($this->V3Edmx->isOK($this->lastError), $this->lastError);
75
        return [$NewEntity, $entitySet];
76
    }
77
78
    public function addComplexType($name, $accessType = "Public", $summary = null, $longDescription = null)
79
    {
80
        $NewEntity = new TComplexTypeType();
81
        $NewEntity->setName($name);
82
        $NewEntity->setTypeAccess($accessType);
83 View Code Duplication
        if (null != $summary && null != $longDescription) {
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...
84
            $documentation = $this->generateDocumentation($summary, $longDescription);
85
            $NewEntity->setDocumentation($documentation);
86
        }
87
        assert($NewEntity->isOK($this->lastError), $this->lastError);
88
        $this->V3Edmx->getDataServiceType()->getSchema()[0]->addToComplexType($NewEntity);
89
90
        return $NewEntity;
91
    }
92
93
    public function getSerialiser()
94
    {
95
        return $this->serializer;
96
    }
97
98
    public function addPropertyToComplexType(
99
        \AlgoWeb\ODataMetadata\MetadataV3\edm\TComplexTypeType $complexType,
100
        $name,
101
        $type,
102
        $defaultValue = null,
103
        $nullable = false,
104
        $summary = null,
105
        $longDescription = null
106
    ) {
107
        if (is_array($defaultValue) || is_object($defaultValue)) {
108
            throw new \InvalidArgumentException("Default value cannot be object or array");
109
        }
110
        if (null != $defaultValue) {
111
            $defaultValue = var_export($defaultValue, true);
112
        }
113
        $NewProperty = new TComplexTypePropertyType();
114
        $NewProperty->setName($name);
115
        $NewProperty->setType($type);
116
        $NewProperty->setNullable($nullable);
117 View Code Duplication
        if (null != $summary && null != $longDescription) {
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...
118
            $documentation = $this->generateDocumentation($summary, $longDescription);
119
            $NewProperty->addToDocumentation($documentation);
120
        }
121
        if (null != $defaultValue) {
122
            $NewProperty->setDefaultValue($defaultValue);
123
        }
124
        assert($NewProperty->isOK($this->lastError), $this->lastError);
125
        $complexType->addToProperty($NewProperty);
126
        return $NewProperty;
127
    }
128
129
    public function addPropertyToEntityType(
130
        TEntityTypeType $entityType,
131
        $name,
132
        $type,
133
        $defaultValue = null,
134
        $nullable = false,
135
        $isKey = false,
136
        $storeGeneratedPattern = null,
137
        $summary = null,
138
        $longDescription = null
139
    ) {
140
        $NewProperty = new TEntityPropertyType();
141
        $NewProperty->setName($name);
142
        $NewProperty->setType($type);
143
        $NewProperty->setStoreGeneratedPattern($storeGeneratedPattern);
144
        $NewProperty->setNullable($nullable);
145 View Code Duplication
        if (null != $summary && null != $longDescription) {
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...
146
            $documentation = $this->generateDocumentation($summary, $longDescription);
147
            $NewProperty->addToDocumentation($documentation);
148
        }
149
        if (null != $defaultValue) {
150
            $NewProperty->setDefaultValue($defaultValue);
151
        }
152
        $entityType->addToProperty($NewProperty);
153
        if ($isKey) {
154
            $Key = new TPropertyRefType();
155
            $Key->setName($name);
156
            $entityType->addToKey($Key);
157
        }
158
        return $NewProperty;
159
    }
160
161
    public function addNavigationPropertyToEntityType(
162
        TEntityTypeType $principalType,
163
        $principalMultiplicity,
164
        $principalProperty,
165
        TEntityTypeType $dependentType,
166
        $dependentMultiplicity,
167
        $dependentProperty = "",
168
        array $principalConstraintProperty = null,
169
        array $dependentConstraintProperty = null,
170
        $principalGetterAccess = "Public",
171
        $principalSetterAccess = "Public",
172
        $dependentGetterAccess = "Public",
173
        $dependentSetterAccess = "Public",
174
        $principalSummery = null,
175
        $principalLongDescription = null,
176
        $dependentSummery = null,
177
        $dependentLongDescription = null
178
    ) {
179
        $principalEntitySetName = Str::plural($principalType->getName());
180
        $dependentEntitySetName = Str::plural($dependentType->getName());
181
        $relationName = $principalType->getName() . "_" . $principalProperty . "_"
182
                        . $dependentType->getName() . "_" . $dependentProperty;
183
        $relationName = trim($relationName, "_");
184
185
        $namespace = $this->getNamespace();
186
        $relationFQName = $namespace . $relationName;
187
188
        $principalNavigationProperty = new TNavigationPropertyType();
189
        $principalNavigationProperty->setName($principalProperty);
190
        $principalNavigationProperty->setToRole(trim($dependentEntitySetName . "_" . $dependentProperty, "_"));
191
        $principalNavigationProperty->setFromRole($principalEntitySetName . "_" . $principalProperty);
192
        $principalNavigationProperty->setRelationship($relationFQName);
193
        $principalNavigationProperty->setGetterAccess($principalGetterAccess);
194
        $principalNavigationProperty->setSetterAccess($principalSetterAccess);
195
        if (null != $principalSummery && null != $principalLongDescription) {
196
            $principalDocumentation = $this->generateDocumentation($principalSummery, $principalLongDescription);
197
            $principalNavigationProperty->setDocumentation($principalDocumentation);
198
        }
199
        $principalType->addToNavigationProperty($principalNavigationProperty);
200
        $dependentNavigationProperty = null;
201
        if (!empty($dependentProperty)) {
202
            $dependentNavigationProperty = new TNavigationPropertyType();
203
            $dependentNavigationProperty->setName($dependentProperty);
204
            $dependentNavigationProperty->setToRole($principalEntitySetName . "_" . $principalProperty);
205
            $dependentNavigationProperty->setFromRole($dependentEntitySetName . "_" . $dependentProperty);
206
            $dependentNavigationProperty->setRelationship($relationFQName);
207
            $dependentNavigationProperty->setGetterAccess($dependentGetterAccess);
208
            $dependentNavigationProperty->setSetterAccess($dependentSetterAccess);
209
            if (null != $dependentSummery && null != $dependentLongDescription) {
210
                $dependentDocumentation = $this->generateDocumentation($dependentSummery, $dependentLongDescription);
211
                $dependentNavigationProperty->setDocumentation($dependentDocumentation);
212
            }
213
            $dependentType->addToNavigationProperty($dependentNavigationProperty);
214
        }
215
216
        $assocation = $this->createAssocationFromNavigationProperty(
217
            $principalType,
218
            $dependentType,
219
            $principalNavigationProperty,
220
            $dependentNavigationProperty,
221
            $principalMultiplicity,
222
            $dependentMultiplicity,
223
            $principalConstraintProperty,
224
            $dependentConstraintProperty
225
        );
226
227
        $this->V3Edmx->getDataServiceType()->getSchema()[0]->addToAssociation($assocation);
228
229
        $associationSet = $this->createAssocationSetForAssocation(
230
            $assocation,
231
            $principalEntitySetName,
232
            $dependentEntitySetName
233
        );
234
235
        $this->V3Edmx->getDataServiceType()->getSchema()[0]
236
            ->getEntityContainer()[0]->addToAssociationSet($associationSet);
237
238
        assert($this->V3Edmx->isOK($this->lastError), $this->lastError);
239
        return [$principalNavigationProperty, $dependentNavigationProperty];
240
    }
241
242
    protected function createAssocationFromNavigationProperty(
243
        TEntityTypeType $principalType,
244
        TEntityTypeType $dependentType,
245
        TNavigationPropertyType $principalNavigationProperty,
246
        TNavigationPropertyType $dependentNavigationProperty = null,
247
        $principalMultiplicity,
248
        $dependentMultiplicity,
249
        array $principalConstraintProperty = null,
250
        array $dependentConstraintProperty = null
251
    ) {
252
        $multCombo = [ '*' => ['*', '1'], '0..1' => ['1'], '1' => ['*', '0..1']];
253
        $multKeys = array_keys($multCombo);
254
        if (null != $dependentNavigationProperty) {
255
            if ($dependentNavigationProperty->getRelationship() != $principalNavigationProperty->getRelationship()) {
256
                $msg = "If you have both a dependent property and a principal property,"
257
                       ." relationship should match";
258
                throw new \InvalidArgumentException($msg);
259
            }
260
            if ($dependentNavigationProperty->getFromRole() != $principalNavigationProperty->getToRole() ||
261
                $dependentNavigationProperty->getToRole() != $principalNavigationProperty->getFromRole()
262
            ) {
263
                throw new \InvalidArgumentException(
264
                    "Principal to role should match dependent from role, and vice versa"
265
                );
266
            }
267
        }
268
        if (!in_array($principalMultiplicity, $multKeys) || !in_array($dependentMultiplicity, $multKeys)) {
269
            throw new \InvalidArgumentException("Malformed multiplicity - valid values are *, 0..1 and 1");
270
        }
271
        if (!in_array($dependentMultiplicity, $multCombo[$principalMultiplicity])) {
272
            throw new \InvalidArgumentException(
273
                "Invalid multiplicity combination - ". $principalMultiplicity . ' ' . $dependentMultiplicity
274
            );
275
        }
276
277
        $namespace = $this->getNamespace();
278
        $principalTypeFQName = $namespace . $principalType->getName();
279
        $dependentTypeFQName = $namespace . $dependentType->getName();
280
        $association = new TAssociationType();
281
        $relationship = $principalNavigationProperty->getRelationship();
282
        if (false !== strpos($relationship, '.')) {
283
            $relationship = substr($relationship, strpos($relationship, '.') + 1);
284
        }
285
286
        $principalTargRole = $principalNavigationProperty->getToRole();
287
        $principalSrcRole = $principalNavigationProperty->getFromRole();
288
        $dependentTargRole = null != $dependentNavigationProperty ? $dependentNavigationProperty->getToRole() : null;
289
290
        $association->setName($relationship);
291
        $principalEnd = new TAssociationEndType();
292
        $principalEnd->setType($principalTypeFQName);
293
        $principalEnd->setRole($principalTargRole);
294
        $principalEnd->setMultiplicity($principalMultiplicity);
295
        $association->addToEnd($principalEnd);
296
        $dependentEnd = new TAssociationEndType();
297
        $dependentEnd->setType($dependentTypeFQName);
298
        $dependentEnd->setMultiplicity($dependentMultiplicity);
299
        $association->addToEnd($dependentEnd);
300
301
        $dependentEnd->setRole(null != $dependentNavigationProperty ? $dependentTargRole : $principalSrcRole);
302
303
        $hasPrincipalReferral = null != $principalConstraintProperty && 0 < count($principalConstraintProperty);
304
        $hasDependentReferral = null != $dependentConstraintProperty && 0 < count($dependentConstraintProperty);
305
306
        if ($hasPrincipalReferral && $hasDependentReferral) {
307
            $principalReferralConstraint = $this->makeReferentialConstraint(
308
                $principalConstraintProperty, $principalTargRole
309
            );
310
            $dependentReferralConstraint = $this->makeReferentialConstraint(
311
                $dependentConstraintProperty, $dependentTargRole
312
            );
313
            $constraint = new TConstraintType();
314
            $constraint->setPrincipal($principalReferralConstraint);
315
            $constraint->setDependent($dependentReferralConstraint);
316
            $association->setReferentialConstraint($constraint);
317
        }
318
        return $association;
319
    }
320
321
    protected function createAssocationSetForAssocation(
322
        TAssociationType $association,
323
        $principalEntitySetName,
324
        $dependentEntitySetName
325
    ) {
326
        $as = new AssociationSetAnonymousType();
327
        $name = $association->getName();
328
        $as->setName($name);
329
        $namespace = $this->getNamespace();
330
        $associationSetName = $namespace . $association->getName();
331
        $as->setAssociation($associationSetName);
332
        $end1 = new EndAnonymousType();
333
        $end1->setRole($association->getEnd()[0]->getRole());
334
        $end1->setEntitySet($principalEntitySetName);
335
        $end2 = new EndAnonymousType();
336
        $end2->setRole($association->getEnd()[1]->getRole());
337
        $end2->setEntitySet($dependentEntitySetName);
338
        assert($end1->getRole() != $end2->getRole());
339
        $as->addToEnd($end1);
340
        $as->addToEnd($end2);
341
        return $as;
342
    }
343
344
    public function getLastError()
345
    {
346
        return $this->lastError;
347
    }
348
349
    /**
350
     * @param string $name
351
     * @param IsOK $expectedReturnType
352
     * @param TTextType $shortDesc
353
     * @param TTextType $longDesc
354
     * @return FunctionImportAnonymousType
355
     */
356
    public function createSingleton(
357
        $name,
358
        IsOK $expectedReturnType,
359
        TTextType $shortDesc = null,
360
        TTextType $longDesc = null
361
    ) {
362
        if (!($expectedReturnType instanceof TEntityTypeType) && !($expectedReturnType instanceof TComplexTypeType)) {
363
            $msg = "Expected return type must be either TEntityType or TComplexType";
364
            throw new \InvalidArgumentException($msg);
365
        }
366
367
        if (!is_string($name) || empty($name)) {
368
            $msg = "Name must be a non-empty string";
369
            throw new \InvalidArgumentException($msg);
370
        }
371
372
        $funcType = new FunctionImportAnonymousType();
373
        $funcType->setName($name);
374
375
        $typeName = $expectedReturnType->getName();
376
        $returnType = new TFunctionImportReturnTypeType();
377
        $returnType->setType($typeName);
378
        $returnType->setEntitySetAttribute($typeName);
379
        assert($returnType->isOK($msg), $msg);
380
        $funcType->addToReturnType($returnType);
381
        if (null != $shortDesc && null != $longDesc) {
382
            $documentation = $this->generateDocumentation($shortDesc, $longDesc);
383
            $funcType->setDocumentation($documentation);
384
        }
385
386
        $this->getEdmx()->getDataServiceType()->getSchema()[0]->getEntityContainer()[0]->addToFunctionImport($funcType);
387
388
        return $funcType;
389
    }
390
391
    private function initSerialiser()
392
    {
393
        $ymlDir = __DIR__ . DIRECTORY_SEPARATOR . "MetadataV3" . DIRECTORY_SEPARATOR . "JMSmetadata";
394
        $this->serializer =
395
            SerializerBuilder::create()
396
                ->addMetadataDir($ymlDir)
397
                ->build();
398
    }
399
400
    public function __sleep()
401
    {
402
        $this->serializer = null;
403
        $result = array_keys(get_object_vars($this));
404
        return $result;
405
    }
406
407
    public function __wakeup()
408
    {
409
        $this->initSerialiser();
410
    }
411
412
    /**
413
     * @param $summary
414
     * @param $longDescription
415
     * @return TDocumentationType
416
     */
417
    private function generateDocumentation(TTextType $summary = null, TTextType $longDescription = null)
418
    {
419
        $documentation = new TDocumentationType();
420
        $documentation->setSummary($summary);
0 ignored issues
show
Bug introduced by
It seems like $summary defined by parameter $summary on line 417 can be null; however, AlgoWeb\ODataMetadata\Me...ationType::setSummary() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
421
        $documentation->setLongDescription($longDescription);
0 ignored issues
show
Bug introduced by
It seems like $longDescription defined by parameter $longDescription on line 417 can be null; however, AlgoWeb\ODataMetadata\Me...e::setLongDescription() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
422
        return $documentation;
423
    }
424
425
    /**
426
     * @return string
427
     */
428
    private function getNamespace()
429
    {
430
        $namespace = $this->V3Edmx->getDataServiceType()->getSchema()[0]->getNamespace();
431
        if (0 == strlen(trim($namespace))) {
432
            $namespace = "";
433
        } else {
434
            $namespace .= ".";
435
        }
436
        return $namespace;
437
    }
438
439
    /**
440
     * @param array $constraintProperty
441
     * @param string $targRole
442
     * @return TReferentialConstraintRoleElementType
443
     */
444
    protected function makeReferentialConstraint(array $constraintProperty, $targRole)
445
    {
446
        assert(!empty($constraintProperty));
447
        assert(is_string($targRole));
448
        $referralConstraint = new TReferentialConstraintRoleElementType();
449
        $referralConstraint->setRole($targRole);
450
        foreach ($constraintProperty as $propertyRef) {
451
            $TpropertyRef = new TPropertyRefType();
452
            $TpropertyRef->setName($propertyRef);
453
            $referralConstraint->addToPropertyRef($TpropertyRef);
454
        }
455
        return $referralConstraint;
456
    }
457
}
458