ModelElementInstanceImpl   F
last analyzed

Complexity

Total Complexity 68

Size/Duplication

Total Lines 345
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 159
c 1
b 0
f 0
dl 0
loc 345
rs 2.96
wmc 68

31 Methods

Rating   Name   Duplication   Size   Complexity  
A addChildElement() 0 5 1
A setAttributeValueNs() 0 17 4
A equals() 0 6 2
A getDomElement() 0 3 1
A replaceChildElement() 0 10 1
A updateIncomingReferences() 0 16 6
A removeAttribute() 0 10 3
A removeAttributeNs() 0 10 3
A getAttributeValue() 0 3 1
A getUniqueChildElementByType() 0 8 2
A removeChildElement() 0 6 1
A unlinkAllReferences() 0 7 3
A setAttributeValue() 0 15 4
A getTextContent() 0 3 1
A getChildElementsByType() 0 22 5
A setTextContent() 0 3 1
A __construct() 0 5 1
A determineNamespace() 0 15 5
A getUniqueChildElementByNameNs() 0 13 2
A findElementToInsertAfter() 0 17 3
A updateAfterReplacement() 0 2 1
A getElementType() 0 3 1
A replaceWithElement() 0 7 2
A getParentElement() 0 7 2
A getModelInstance() 0 3 1
A getRawTextContent() 0 3 1
A insertElementAfter() 0 10 3
A unlinkAllChildReferences() 0 7 3
A registerType() 0 4 1
A setUniqueChildElementByNameNs() 0 13 2
A getAttributeValueNs() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like ModelElementInstanceImpl 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.

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 ModelElementInstanceImpl, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Xml\Impl\Instance;
4
5
use Xml\{
6
    ModelBuilder
7
};
8
use Xml\Impl\{
9
    ModelInstanceImpl
10
};
11
use Xml\Impl\Util\ModelUtil;
12
use Xml\Instance\{
13
    DomElementInterface,
14
    ModelElementInstanceInterface
15
};
16
use Xml\Type\{
17
    ModelElementTypeInterface
18
};
19
use Xml\Exception\ModelException;
20
21
class ModelElementInstanceImpl implements ModelElementInstanceInterface
22
{
23
    protected $modelInstance;
24
25
    private $domElement;
26
27
    private $elementType;
28
29
    public static function registerType(ModelBuilder $modelBuilder): void
30
    {
31
        $typeBuilder = $modelBuilder->defineType(ModelElementInstanceInterface::class, "")->abstractType();
32
        $typeBuilder->build();
33
    }
34
35
    public function __construct(ModelTypeInstanceContext $instanceContext)
36
    {
37
        $this->domElement = $instanceContext->getDomElement();
38
        $this->modelInstance = $instanceContext->getModel();
39
        $this->elementType = $instanceContext->getModelType();
40
    }
41
42
    public function getDomElement(): ?DomElementInterface
43
    {
44
        return $this->domElement;
45
    }
46
47
    public function getModelInstance(): ModelInstanceImpl
48
    {
49
        return $this->modelInstance;
50
    }
51
52
    public function getParentElement(): ?ModelElementInstanceInterface
53
    {
54
        $parentElement = $this->domElement->getParentElement();
0 ignored issues
show
Bug introduced by
The method getParentElement() does not exist on Xml\Instance\DomElementInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Xml\Instance\DomElementInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

54
        /** @scrutinizer ignore-call */ 
55
        $parentElement = $this->domElement->getParentElement();
Loading history...
55
        if ($parentElement !== null) {
56
            return ModelUtil::getModelElement($parentElement, $this->modelInstance);
57
        } else {
58
            return null;
59
        }
60
    }
61
62
    public function getElementType(): ModelElementTypeInterface
63
    {
64
        return $this->elementType;
65
    }
66
67
    public function getAttributeValue(string $attributeName): ?string
68
    {
69
        return $this->domElement->getAttribute(null, $attributeName);
70
    }
71
72
    public function getAttributeValueNs(string $namespaceUri, string $attributeName): ?string
73
    {
74
        return $this->domElement->getAttribute($namespaceUri, $attributeName);
75
    }
76
77
    public function setAttributeValue(
78
        string $attributeName,
79
        string $xmlValue,
80
        bool $isIdAttribute = false,
81
        bool $withReferenceUpdate = true
82
    ): void {
83
        $oldValue = $this->getAttributeValue($attributeName);
84
        if ($isIdAttribute) {
85
            $this->domElement->setIdAttribute(null, $attributeName, $xmlValue);
86
        } else {
87
            $this->domElement->setAttribute(null, $attributeName, $xmlValue, $isIdAttribute);
88
        }
89
        $attribute = $this->elementType->getAttribute($attributeName);
90
        if ($attribute !== null && $withReferenceUpdate) {
91
            $attribute->updateIncomingReferences($this, $xmlValue, $oldValue);
0 ignored issues
show
Bug introduced by
The method updateIncomingReferences() does not exist on Xml\Type\Attribute\AttributeInterface. Did you maybe mean getIncomingReferences()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

91
            $attribute->/** @scrutinizer ignore-call */ 
92
                        updateIncomingReferences($this, $xmlValue, $oldValue);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
92
        }
93
    }
94
95
    public function setAttributeValueNs(
96
        string $namespaceUri,
97
        string $attributeName,
98
        string $xmlValue,
99
        bool $isIdAttribute = false,
100
        bool $withReferenceUpdate = true
101
    ): void {
102
        $namespaceForSetting = $this->determineNamespace($namespaceUri, $attributeName);
103
        $oldValue = $this->getAttributeValueNs($namespaceForSetting, $attributeName);
104
        if ($isIdAttribute) {
105
            $this->domElement->setIdAttribute($namespaceForSetting, $attributeName, $xmlValue);
106
        } else {
107
            $this->domElement->setAttribute($namespaceForSetting, $attributeName, $xmlValue, false);
108
        }
109
        $attribute = $this->elementType->getAttribute($attributeName);
110
        if ($attribute !== null && $withReferenceUpdate) {
111
            $attribute->updateIncomingReferences($this, $xmlValue, $oldValue);
112
        }
113
    }
114
115
    private function determineNamespace(string $intendedNamespace, string $attributeName): string
116
    {
117
        $isSetInIntendedNamespace = $this->getAttributeValueNs($intendedNamespace, $attributeName) !== null;
118
        if ($isSetInIntendedNamespace) {
119
            return $intendedNamespace;
120
        } else {
121
            $alternativeNamespaces = $this->modelInstance->getModel()->getAlternativeNamespaces($intendedNamespace);
122
            if (!empty($alternativeNamespaces)) {
123
                foreach ($alternativeNamespaces as $alternativeNamespace) {
124
                    if ($this->getAttributeValueNs($alternativeNamespace, $attributeName) !== null) {
125
                        return $alternativeNamespace;
126
                    }
127
                }
128
            }
129
            return $intendedNamespace;
130
        }
131
    }
132
133
    public function removeAttribute(string $attributeName): void
134
    {
135
        $attribute = $this->elementType->getAttribute($attributeName);
136
        if ($attribute !== null) {
137
            $identifier = $attribute->getValue($this);
138
            if ($identifier !== null) {
139
                $attribute->unlinkReference($this, $identifier);
0 ignored issues
show
Bug introduced by
The method unlinkReference() does not exist on Xml\Type\Attribute\AttributeInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Xml\Type\Attribute\AttributeInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

139
                $attribute->/** @scrutinizer ignore-call */ 
140
                            unlinkReference($this, $identifier);
Loading history...
140
            }
141
        }
142
        $this->domElement->removeAttribute(null, $attributeName);
143
    }
144
145
    public function removeAttributeNs(string $namespaceUri, string $attributeName): void
146
    {
147
        $attribute = $this->elementType->getAttribute($attributeName);
148
        if ($attribute !== null) {
149
            $identifier = $attribute->getValue($this);
150
            if ($identifier !== null) {
151
                $attribute->unlinkReference($this, $identifier);
152
            }
153
        }
154
        $this->domElement->removeAttribute($namespaceUri, $attributeName);
155
    }
156
157
    public function getTextContent(): string
158
    {
159
        return trim($this->getRawTextContent());
160
    }
161
162
    public function setTextContent(string $textContent): void
163
    {
164
        $this->domElement->setTextContent($textContent);
165
    }
166
167
    public function getRawTextContent(): string
168
    {
169
        return $this->domElement->getTextContent();
170
    }
171
172
    public function getUniqueChildElementByNameNs(
173
        string $namespaceUri,
174
        string $elementName
175
    ): ?ModelElementInstanceInterface {
176
        $model = $this->modelInstance->getModel();
177
        $childElements = $this->domElement->getChildElementsByNameNs(
178
            [$namespaceUri, ...$model->getAlternativeNamespaces($namespaceUri)],
179
            $elementName
180
        );
181
        if (!empty($childElements)) {
182
            return ModelUtil::getModelElement($childElements[0], $this->modelInstance);
183
        } else {
184
            return null;
185
        }
186
    }
187
188
    public function getUniqueChildElementByType(
189
        string $elementType
190
    ): ?ModelElementInstanceInterface {
191
        $childElements = $this->domElement->getChildElementsByType($this->modelInstance, $elementType);
192
        if (!empty($childElements)) {
193
            return ModelUtil::getModelElement($childElements[0], $this->modelInstance);
194
        } else {
195
            return null;
196
        }
197
    }
198
199
    public function setUniqueChildElementByNameNs(ModelElementInstanceInterface $newChild): void
200
    {
201
        ModelUtil::ensureInstanceOf($newChild, ModelElementInstanceImpl::class);
202
        $newChildElement = $newChild;
203
        $childElement =  $newChildElement->getDomElement();
204
        $existingChild = $this->getUniqueChildElementByNameNs(
205
            $childElement->getNameSpaceURI(),
206
            $childElement->getLocalName()
207
        );
208
        if ($existingChild === null) {
209
            $this->addChildElement($newChild);
210
        } else {
211
            $this->replaceChildElement($existingChild, $newChildElement);
212
        }
213
    }
214
215
    public function replaceChildElement(
216
        ModelElementInstanceInterface $existingChild,
217
        ModelElementInstanceInterface $newChild
218
    ): void {
219
        $existingChildDomElement = $existingChild->getDomElement();
220
        $newChildDomElement = $newChild->getDomElement();
221
        $existingChild->unlinkAllChildReferences();
0 ignored issues
show
Bug introduced by
The method unlinkAllChildReferences() does not exist on Xml\Instance\ModelElementInstanceInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Xml\Instance\ModelElementInstanceInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

221
        $existingChild->/** @scrutinizer ignore-call */ 
222
                        unlinkAllChildReferences();
Loading history...
222
        $this->updateIncomingReferences($existingChild, $newChild);
223
        $this->domElement->replaceChild($newChildDomElement, $existingChildDomElement);
0 ignored issues
show
Bug introduced by
It seems like $existingChildDomElement can also be of type null; however, parameter $existingChildDomElement of Xml\Instance\DomElementInterface::replaceChild() does only seem to accept Xml\Instance\DomElementInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

223
        $this->domElement->replaceChild($newChildDomElement, /** @scrutinizer ignore-type */ $existingChildDomElement);
Loading history...
Bug introduced by
It seems like $newChildDomElement can also be of type null; however, parameter $newChildDomElement of Xml\Instance\DomElementInterface::replaceChild() does only seem to accept Xml\Instance\DomElementInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

223
        $this->domElement->replaceChild(/** @scrutinizer ignore-type */ $newChildDomElement, $existingChildDomElement);
Loading history...
224
        $newChild->updateAfterReplacement();
225
    }
226
227
    private function updateIncomingReferences(
228
        ModelElementInstanceInterface $oldInstance,
229
        ModelElementInstanceInterface $newInstance
230
    ): void {
231
        $oldId = $oldInstance->getAttributeValue("id");
232
        $newId = $newInstance->getAttributeValue("id");
233
234
        if ($oldId === null || $newId === null) {
235
            return;
236
        }
237
238
        $attributes = $oldInstance->getElementType()->getAllAttributes();
0 ignored issues
show
Bug introduced by
The method getAllAttributes() does not exist on Xml\Type\ModelElementTypeInterface. Did you maybe mean getAttributes()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

238
        $attributes = $oldInstance->getElementType()->/** @scrutinizer ignore-call */ getAllAttributes();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
239
        foreach ($attributes as $attribute) {
240
            if ($attribute->isIdAttribute()) {
241
                foreach ($attribute->getIncomingReferences() as $incomingReference) {
242
                    $incomingReference->referencedElementUpdated($newInstance, $oldId, $newId);
243
                }
244
            }
245
        }
246
    }
247
248
    public function replaceWithElement(ModelElementInstanceInterface $newElement): void
249
    {
250
        $parentElement = $this->getParentElement();
251
        if ($parentElement !== null) {
252
            $parentElement->replaceChildElement($this, $newElement);
253
        } else {
254
            throw new ModelException("Unable to remove replace without parent");
255
        }
256
    }
257
258
    public function addChildElement(ModelElementInstanceInterface $newChild): void
259
    {
260
        ModelUtil::ensureInstanceOf($newChild, ModelElementInstanceImpl::class);
261
        $elementToInsertAfter = $this->findElementToInsertAfter($newChild);
262
        $this->insertElementAfter($newChild, $elementToInsertAfter);
263
    }
264
265
    public function removeChildElement(ModelElementInstanceInterface $child): bool
266
    {
267
        $child->unlinkAllReferences();
0 ignored issues
show
Bug introduced by
The method unlinkAllReferences() does not exist on Xml\Instance\ModelElementInstanceInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Xml\Instance\ModelElementInstanceInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

267
        $child->/** @scrutinizer ignore-call */ 
268
                unlinkAllReferences();
Loading history...
268
        $child->unlinkAllChildReferences();
269
        $res = $this->domElement->removeChild($child->getDomElement());
0 ignored issues
show
Bug introduced by
It seems like $child->getDomElement() can also be of type null; however, parameter $domElement of Xml\Instance\DomElementInterface::removeChild() does only seem to accept Xml\Instance\DomElementInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

269
        $res = $this->domElement->removeChild(/** @scrutinizer ignore-type */ $child->getDomElement());
Loading history...
270
        return $res;
271
    }
272
273
    /**
274
     * @param mixed $childElementType
275
     */
276
    public function getChildElementsByType($childElementType): array
277
    {
278
        if ($childElementType instanceof ModelElementTypeInterface) {
279
            $instances = [];
280
            foreach ($childElementType->getExtendingTypes() as $extendingType) {
281
                $instances = array_merge($instances, $this->getChildElementsByType($extendingType));
282
            }
283
            $model = $this->modelInstance->getModel();
284
            $alternativeNamespaces = $model->getAlternativeNamespaces($childElementType->getTypeNamespace());
0 ignored issues
show
Bug introduced by
It seems like $childElementType->getTypeNamespace() can also be of type null; however, parameter $actualNs of Xml\Impl\ModelImpl::getAlternativeNamespaces() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

284
            $alternativeNamespaces = $model->getAlternativeNamespaces(/** @scrutinizer ignore-type */ $childElementType->getTypeNamespace());
Loading history...
285
            $elements = $this->domElement->getChildElementsByNameNs(
286
                [$childElementType->getTypeNamespace(), ...$alternativeNamespaces],
287
                $childElementType->getTypeName()
288
            );
289
            $instances = array_merge($instances, ModelUtil::getModelElementCollection($elements, $this->modelInstance));
290
            return $instances;
291
        } elseif (
292
            is_subclass_of($childElementType, ModelElementInstanceImpl::class) ||
293
            is_subclass_of($childElementType, ModelElementInstanceInterface::class)
294
        ) {
295
            return $this->getChildElementsByType($this->getModelInstance()->getModel()->getType($childElementType));
296
        }
297
        return [];
298
    }
299
300
    private function findElementToInsertAfter(
301
        ModelElementInstanceInterface $elementToInsert
302
    ): ?ModelElementInstanceInterface {
303
        $childElementTypes = $this->elementType->getAllChildElementTypes();
304
        $childDomElements = $this->domElement->getChildElements();
305
        $childElements = ModelUtil::getModelElementCollection($childDomElements, $this->modelInstance);
306
        $insertAfterElement = null;
307
        $newElementTypeIndex = ModelUtil::getIndexOfElementType($elementToInsert, $childElementTypes);
308
        foreach ($childElements as $childElement) {
309
            $childElementTypeIndex = ModelUtil::getIndexOfElementType($childElement, $childElementTypes);
310
            if ($newElementTypeIndex >= $childElementTypeIndex) {
311
                $insertAfterElement = $childElement;
312
            } else {
313
                break;
314
            }
315
        }
316
        return $insertAfterElement;
317
    }
318
319
    public function insertElementAfter(
320
        ModelElementInstanceInterface $elementToInsert,
321
        ?ModelElementInstanceInterface $insertAfterElement
322
    ): void {
323
        if ($insertAfterElement === null || $insertAfterElement->getDomElement() === null) {
324
            $this->domElement->insertChildElementAfter($elementToInsert->getDomElement(), null);
0 ignored issues
show
Bug introduced by
It seems like $elementToInsert->getDomElement() can also be of type null; however, parameter $elementToInsert of Xml\Instance\DomElementI...sertChildElementAfter() does only seem to accept Xml\Instance\DomElementInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

324
            $this->domElement->insertChildElementAfter(/** @scrutinizer ignore-type */ $elementToInsert->getDomElement(), null);
Loading history...
325
        } else {
326
            $this->domElement->insertChildElementAfter(
327
                $elementToInsert->getDomElement(),
328
                $insertAfterElement->getDomElement()
329
            );
330
        }
331
    }
332
333
    public function updateAfterReplacement(): void
334
    {
335
        //
336
    }
337
338
    private function unlinkAllReferences(): void
339
    {
340
        $attributes = $this->elementType->getAllAttributes();
341
        foreach ($attributes as $attribute) {
342
            $identifier = $attribute->getValue($this);
343
            if ($identifier !== null) {
344
                $attribute->unlinkReference($this, $identifier);
345
            }
346
        }
347
    }
348
349
    private function unlinkAllChildReferences(): void
350
    {
351
        $childElementTypes = $this->elementType->getAllChildElementTypes();
352
        foreach ($childElementTypes as $type) {
353
            $childElementsForType = $this->getChildElementsByType($type);
354
            foreach ($childElementsForType as $childElement) {
355
                $childElement->unlinkAllReferences();
356
            }
357
        }
358
    }
359
360
    public function equals(?ModelElementInstanceInterface $obj): bool
361
    {
362
        if ($obj === null) {
363
            return false;
364
        } else {
365
            return $obj->domElement->equals($this->domElement);
0 ignored issues
show
Bug introduced by
Accessing domElement on the interface Xml\Instance\ModelElementInstanceInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
366
        }
367
    }
368
}
369