Completed
Push — master ( fd10ff...da2dcf )
by Alex
06:40
created

MetadataManager   C

Complexity

Total Complexity 51

Size/Duplication

Total Lines 356
Duplicated Lines 15.45 %

Coupling/Cohesion

Components 1
Dependencies 17

Importance

Changes 5
Bugs 0 Features 1
Metric Value
wmc 51
c 5
b 0
f 1
lcom 1
cbo 17
dl 55
loc 356
rs 5.2703

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 2
A getEdmx() 0 4 1
A getEdmxXML() 0 4 1
B addEntityType() 11 32 5
A startEdmxTransaction() 0 4 1
B pluralize() 15 19 6
A revertEdmxTransaction() 0 4 1
A commitEdmxTransaction() 0 4 1
B addPropertyToEntityType() 6 39 6
C addNavigationPropertyToEntityType() 0 94 8
D createAssocationFromNavigationProperty() 18 82 16
B createAssocationSetForAssocation() 5 25 2
A getLastError() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like MetadataManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MetadataManager, and based on these observations, apply Extract Interface, too.

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\TAssociationEndType;
9
use AlgoWeb\ODataMetadata\MetadataV3\edm\TAssociationType;
10
use AlgoWeb\ODataMetadata\MetadataV3\edm\TConstraintType;
11
use AlgoWeb\ODataMetadata\MetadataV3\edm\TDocumentationType;
12
use AlgoWeb\ODataMetadata\MetadataV3\edm\TEntityPropertyType;
13
use AlgoWeb\ODataMetadata\MetadataV3\edm\TEntityTypeType;
14
use AlgoWeb\ODataMetadata\MetadataV3\edm\TNavigationPropertyType;
15
use AlgoWeb\ODataMetadata\MetadataV3\edm\TPropertyRefType;
16
use AlgoWeb\ODataMetadata\MetadataV3\edm\TReferentialConstraintRoleElementType;
17
use AlgoWeb\ODataMetadata\MetadataV3\edmx\Edmx;
18
use JMS\Serializer\SerializerBuilder;
19
20
class MetadataManager
21
{
22
    private $V3Edmx = null;
23
    private $oldEdmx = null;
24
    private $lastError = null;
25
    private $serializer = null;
26
27
    public function __construct($namespaceName = "Data", $containerName = "DefaultContainer")
28
    {
29
        $this->V3Edmx = new Edmx($namespaceName, $containerName);
30
        if (!$this->V3Edmx->isOK($msg)) {
31
            throw new \Exception($msg);
32
        }
33
        $ymlDir = __DIR__ . DIRECTORY_SEPARATOR . "MetadataV3" . DIRECTORY_SEPARATOR . "JMSmetadata";
34
        $this->serializer =
35
            SerializerBuilder::create()
36
                ->addMetadataDir($ymlDir)
37
                ->build();
38
    }
39
40
    public function getEdmx()
41
    {
42
        return $this->V3Edmx;
43
    }
44
45
    public function getEdmxXML()
46
    {
47
        return $this->serializer->serialize($this->V3Edmx, "xml");
48
    }
49
50
    public function addEntityType($name, $accessType = "Public", $summary = null, $longDescription = null)
51
    {
52
        $this->startEdmxTransaction();
53
        $NewEntity = new TEntityTypeType();
54
        $NewEntity->setName($name);
55 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...
56
            $documentation = new TDocumentationType();
57
            $documentation->setSummary($summary);
58
            $documentation->setLongDescription($longDescription);
59
            $NewEntity->setDocumentation($documentation);
60
        }
61
62
        $entitySet = new EntitySetAnonymousType();
63
        $entitySet->setName($this->pluralize(2, $NewEntity->getName()));
64
        $namespace = $this->V3Edmx->getDataServices()[0]->getNamespace();
65 View Code Duplication
        if (0 == strlen(trim($namespace))) {
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...
66
            $entityTypeName = $NewEntity->getName();
67
        } else {
68
            $entityTypeName = $namespace . "." . $NewEntity->getName();
69
        }
70
        $entitySet->setEntityType($entityTypeName);
71
        $entitySet->setGetterAccess($accessType);
72
73
        $this->V3Edmx->getDataServices()[0]->addToEntityType($NewEntity);
74
        $this->V3Edmx->getDataServices()[0]->getEntityContainer()[0]->addToEntitySet($entitySet);
75
        if (!$this->V3Edmx->isok($this->lastError)) {
76
            $this->revertEdmxTransaction();
77
            return false;
78
        }
79
        $this->commitEdmxTransaction();
80
        return $NewEntity;
81
    }
82
83
    private function startEdmxTransaction()
84
    {
85
        $this->oldEdmx = serialize($this->V3Edmx);
86
    }
87
88
    /**
89
     * Pluralizes a word if quantity is not one.
90
     *
91
     * @param int $quantity Number of items
92
     * @param string $singular Singular form of word
93
     * @param string $plural Plural form of word; function will attempt to deduce plural
94
     * form from singular if not provided
95
     * @return string Pluralized word if quantity is not one, otherwise singular
96
     */
97 View Code Duplication
    public static function pluralize($quantity, $singular, $plural = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
98
    {
99
        if ($quantity == 1 || !strlen($singular)) {
100
            return $singular;
101
        }
102
        if ($plural !== null) {
103
            return $plural;
104
        }
105
106
        $last_letter = strtolower($singular[strlen($singular) - 1]);
107
        switch ($last_letter) {
108
            case 'y':
109
                return substr($singular, 0, -1) . 'ies';
110
            case 's':
111
                return $singular . 'es';
112
            default:
113
                return $singular . 's';
114
        }
115
    }
116
117
    private function revertEdmxTransaction()
118
    {
119
        $this->V3Edmx = unserialize($this->oldEdmx);
120
    }
121
122
    private function commitEdmxTransaction()
123
    {
124
        $this->oldEdmx = null;
125
    }
126
127
    public function addPropertyToEntityType(
128
        $entityType,
129
        $name,
130
        $type,
131
        $defaultValue = null,
132
        $nullable = false,
133
        $isKey = false,
134
        $storeGeneratedPattern = null,
135
        $summary = null,
136
        $longDescription = null
137
    ) {
138
        $this->startEdmxTransaction();
139
        $NewProperty = new TEntityPropertyType();
140
        $NewProperty->setName($name);
141
        $NewProperty->setType($type);
142
        $NewProperty->setStoreGeneratedPattern($storeGeneratedPattern);
143
        $NewProperty->setNullable($nullable);
144 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...
145
            $documentation = new TDocumentationType();
146
            $documentation->setSummary($summary);
147
            $documentation->setLongDescription($longDescription);
148
            $NewProperty->addToDocumentation($documentation);
149
        }
150
        if (null != $defaultValue) {
151
            $NewProperty->setDefaultValue($defaultValue);
152
        }
153
        $entityType->addToProperty($NewProperty);
154
        if ($isKey) {
155
            $Key = new TPropertyRefType();
156
            $Key->setName($name);
157
            $entityType->addToKey($Key);
158
        }
159
        if (!$this->V3Edmx->isok($this->lastError)) {
160
            $this->revertEdmxTransaction();
161
            return false;
162
        }
163
        $this->commitEdmxTransaction();
164
        return $NewProperty;
165
    }
166
167
    public function addNavigationPropertyToEntityType(
168
        TEntityTypeType $principalType,
169
        $principalMultiplicity,
170
        $principalProperty,
171
        TEntityTypeType $dependentType,
172
        $dependentMultiplicity,
173
        $dependentProperty = "",
174
        array $principalConstraintProperty = null,
175
        array $dependentConstraintProperty = null,
176
        $principalGetterAccess = "Public",
177
        $principalSetterAccess = "Public",
178
        $dependentGetterAccess = "Public",
179
        $dependentSetterAccess = "Public",
180
        $principalSummery = null,
181
        $principalLongDescription = null,
182
        $dependentSummery = null,
183
        $dependentLongDescription = null
184
    ) {
185
        $this->startEdmxTransaction();
186
        $principalEntitySetName = $this->pluralize(2, $principalType->getName());
187
        $dependentEntitySetName = $this->pluralize(2, $dependentType->getName());
188
        $relationName = $principalType->getName() . "_" . $principalProperty . "_"
189
                        . $dependentType->getName() . "_" . $dependentProperty;
190
        $relationName = trim($relationName, "_");
191
192
        $namespace = $this->V3Edmx->getDataServices()[0]->getNamespace();
193
        if (0 == strlen(trim($namespace))) {
194
            $relationFQName = $relationName;
195
        } else {
196
            $relationFQName = $namespace . "." . $relationName;
197
        }
198
199
        $principalNavigationProperty = new TNavigationPropertyType();
200
        $principalNavigationProperty->setName($principalProperty);
201
        $principalNavigationProperty->setToRole($dependentEntitySetName . "_" . $dependentProperty);
202
        $principalNavigationProperty->setFromRole($principalEntitySetName . "_" . $principalProperty);
203
        $principalNavigationProperty->setRelationship($relationFQName);
204
        $principalNavigationProperty->setGetterAccess($principalGetterAccess);
205
        $principalNavigationProperty->setSetterAccess($principalSetterAccess);
206
        if (null != $principalSummery || null != $principalLongDescription) {
207
            $principalDocumentation = new TDocumentationType();
208
            $principalDocumentation->setSummary($principalSummery);
209
            $principalDocumentation->setLongDescription($principalLongDescription);
210
            $principalNavigationProperty->setDocumentation($principalDocumentation);
211
        }
212
        $principalType->addToNavigationProperty($principalNavigationProperty);
213
214
        $dependentNavigationProperty = null;
215
        if (!empty($dependentProperty)) {
216
            $dependentNavigationProperty = new TNavigationPropertyType();
217
            $dependentNavigationProperty->setName($dependentProperty);
218
            $dependentNavigationProperty->setToRole($principalEntitySetName . "_" . $principalProperty);
219
            $dependentNavigationProperty->setFromRole($dependentEntitySetName . "_" . $dependentProperty);
220
            $dependentNavigationProperty->setRelationship($relationFQName);
221
            $dependentNavigationProperty->setGetterAccess($dependentGetterAccess);
222
            $dependentNavigationProperty->setSetterAccess($dependentSetterAccess);
223
            if (null != $dependentSummery || null != $dependentLongDescription) {
224
                $dependentDocumentation = new TDocumentationType();
225
                $dependentDocumentation->setSummary($dependentSummery);
226
                $dependentDocumentation->setLongDescription($dependentLongDescription);
227
                $dependentNavigationProperty->setDocumentation($dependentDocumentation);
228
            }
229
            $dependentType->addToNavigationProperty($dependentNavigationProperty);
230
        }
231
232
        $assocation = $this->createAssocationFromNavigationProperty(
233
            $principalType,
234
            $dependentType,
235
            $principalNavigationProperty,
236
            $dependentNavigationProperty,
237
            $principalMultiplicity,
238
            $dependentMultiplicity,
239
            $principalConstraintProperty,
240
            $dependentConstraintProperty
241
        );
242
243
        $this->V3Edmx->getDataServices()[0]->addToAssociation($assocation);
244
245
        $associationSet = $this->createAssocationSetForAssocation(
246
            $assocation,
247
            $principalEntitySetName,
248
            $dependentEntitySetName
249
        );
250
251
        $this->V3Edmx->getDataServices()[0]->getEntityContainer()[0]->addToAssociationSet($associationSet);
252
253
254
        if (!$this->V3Edmx->isok($this->lastError)) {
255
            $this->revertEdmxTransaction();
256
            return false;
257
        }
258
        $this->commitEdmxTransaction();
259
        return [$principalNavigationProperty, $dependentNavigationProperty];
260
    }
261
262
    protected function createAssocationFromNavigationProperty(
263
        TEntityTypeType $principalType,
264
        TEntityTypeType $dependentType,
265
        TNavigationPropertyType $principalNavigationProperty,
266
        TNavigationPropertyType $dependentNavigationProperty = null,
267
        $principalMultiplicity,
268
        $dependentMultiplicity,
269
        array $principalConstraintProperty = null,
270
        array $dependentConstraintProperty = null
271
    ) {
272
        if (null != $dependentNavigationProperty) {
273
            if ($dependentNavigationProperty->getRelationship() != $principalNavigationProperty->getRelationship()) {
274
                $msg = "if you have both a dependent property and a principal property,"
275
                       ." they should both have the same relationship";
276
                throw new \Exception($msg);
277
            }
278
            if ($dependentNavigationProperty->getFromRole() != $principalNavigationProperty->getToRole() ||
279
                $dependentNavigationProperty->getToRole() != $principalNavigationProperty->getFromRole()
280
            ) {
281
                throw new \Exception("The from roles and two roles from matching properties should match");
282
            }
283
        }
284
        $namespace = $this->V3Edmx->getDataServices()[0]->getNamespace();
285
286
        if (0 == strlen(trim($namespace))) {
287
            $principalTypeFQName = $principalType->getName();
288
            $dependentTypeFQName = $dependentType->getName();
289
        } else {
290
            $principalTypeFQName = $namespace . "." . $principalType->getName();
291
            $dependentTypeFQName = $namespace . "." . $dependentType->getName();
292
        }
293
        $association = new TAssociationType();
294
        $relationship = $principalNavigationProperty->getRelationship();
295
        if (strpos($relationship, '.') !== false) {
296
            $relationship = substr($relationship, strpos($relationship, '.') + 1);
297
        }
298
299
        $association->setName($relationship);
300
        $principalEnd = new TAssociationEndType();
301
        $principalEnd->setType($principalTypeFQName);
302
        $principalEnd->setRole($principalNavigationProperty->getFromRole());
303
        $principalEnd->setMultiplicity($principalMultiplicity);
304
        $association->addToEnd($principalEnd);
305
        $dependentEnd = new TAssociationEndType();
306
        $dependentEnd->setType($dependentTypeFQName);
307
        $dependentEnd->setMultiplicity($dependentMultiplicity);
308
        $association->addToEnd($dependentEnd);
309
310
        if (null != $dependentNavigationProperty) {
311
            $dependentEnd->setRole($dependentNavigationProperty->getFromRole());
312
        }
313
314
        $principalReferralConstraint = null;
315
        $dependentReferralConstraint = null;
316
317 View Code Duplication
        if (null != $principalConstraintProperty && 0 < count($principalConstraintProperty)) {
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...
318
            $principalReferralConstraint = new TReferentialConstraintRoleElementType();
319
            $principalReferralConstraint->setRole($principalNavigationProperty->getFromRole());
320
            foreach ($principalConstraintProperty as $propertyRef) {
321
                $TpropertyRef = new TPropertyRefType();
322
                $TpropertyRef->setName($propertyRef);
323
                $principalReferralConstraint->addToPropertyRef($TpropertyRef);
324
            }
325
        }
326 View Code Duplication
        if (null != $dependentConstraintProperty && 0 < count($dependentConstraintProperty)) {
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...
327
            $dependentReferralConstraint = new TReferentialConstraintRoleElementType();
328
            $dependentReferralConstraint->setRole($dependentNavigationProperty->getFromRole());
0 ignored issues
show
Bug introduced by
It seems like $dependentNavigationProperty is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
329
            foreach ($dependentConstraintProperty as $propertyRef) {
330
                $TpropertyRef = new TPropertyRefType();
331
                $TpropertyRef->setName($propertyRef);
332
                $dependentReferralConstraint->addToPropertyRef($TpropertyRef);
333
            }
334
        }
335
336
        if (null != $dependentReferralConstraint || null != $principalReferralConstraint) {
337
            $constraint = new TConstraintType();
338
            $constraint->setPrincipal($principalReferralConstraint);
0 ignored issues
show
Bug introduced by
It seems like $principalReferralConstraint defined by null on line 314 can be null; however, AlgoWeb\ODataMetadata\Me...intType::setPrincipal() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
339
            $constraint->setDependent($dependentReferralConstraint);
0 ignored issues
show
Bug introduced by
It seems like $dependentReferralConstraint defined by null on line 315 can be null; however, AlgoWeb\ODataMetadata\Me...intType::setDependent() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
340
            $association->setReferentialConstraint($constraint);
341
        }
342
        return $association;
343
    }
344
345
    protected function createAssocationSetForAssocation(
346
        TAssociationType $association,
347
        $principalEntitySetName,
348
        $dependentEntitySetName
349
    ) {
350
        $as = new AssociationSetAnonymousType();
351
        $name = $association->getName();
352
        $as->setName($name);
353
        $namespace = $this->V3Edmx->getDataServices()[0]->getNamespace();
354 View Code Duplication
        if (0 == strlen(trim($namespace))) {
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...
355
            $associationSetName = $association->getName();
356
        } else {
357
            $associationSetName = $namespace . "." . $association->getName();
358
        }
359
        $as->setAssociation($associationSetName);
360
        $end1 = new EndAnonymousType();
361
        $end1->setRole($association->getEnd()[0]->getRole());
362
        $end1->setEntitySet($principalEntitySetName);
363
        $end2 = new EndAnonymousType();
364
        $end2->setRole($association->getEnd()[1]->getRole());
365
        $end2->setEntitySet($dependentEntitySetName);
366
        $as->addToEnd($end1);
367
        $as->addToEnd($end2);
368
        return $as;
369
    }
370
371
    public function getLastError()
372
    {
373
        return $this->lastError;
374
    }
375
}
376