Passed
Push — static-analysis ( a61185...2aa7e5 )
by SignpostMarv
03:33
created

Schema   F

Complexity

Total Complexity 67

Size/Duplication

Total Lines 606
Duplicated Lines 0 %

Test Coverage

Coverage 89.05%

Importance

Changes 0
Metric Value
dl 0
loc 606
ccs 179
cts 201
cp 0.8905
rs 3.0244
c 0
b 0
f 0
wmc 67

40 Methods

Rating   Name   Duplication   Size   Complexity  
A getElements() 0 3 1
A getTypes() 0 3 1
A getElementsQualification() 0 3 1
A getGroups() 0 3 1
A setDoc() 0 3 1
A getDoc() 0 3 1
A setElementsQualification() 0 3 1
A getAttributes() 0 3 1
A addType() 0 3 1
A setAttributesQualification() 0 3 1
A getSchemas() 0 3 1
A addElement() 0 3 1
A getTargetNamespace() 0 3 1
A getAttributesQualification() 0 3 1
A setTargetNamespace() 0 3 1
A getGroup() 0 6 2
A getAttribute() 0 6 2
A addAttribute() 0 3 1
A getAttributeGroups() 0 3 1
A addSchema() 0 15 3
A addAttributeGroup() 0 3 1
A addGroup() 0 3 1
A __toString() 0 3 1
A getElement() 0 6 2
A getAttributeGroup() 0 6 2
A getType() 0 6 2
A hasLoadedFile() 0 9 3
A findAttributeGroup() 0 8 1
A findAttribute() 0 8 1
A findSomethingNoThrowSchemas() 0 14 4
B loadImport() 0 35 3
B findSomethingNoThrow() 0 28 5
B loadImportFresh() 0 27 5
A findType() 0 8 1
A setSchemaThingsFromNode() 0 14 3
A findElement() 0 8 1
A findGroup() 0 8 1
A setLoadedFile() 0 5 1
A getLoadedFile() 0 9 3
A findSomething() 0 14 2

How to fix   Complexity   

Complex Class

Complex classes like Schema 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 Schema, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace GoetasWebservices\XML\XSDReader\Schema;
3
4
use Closure;
5
use DOMElement;
6
use RuntimeException;
7
use GoetasWebservices\XML\XSDReader\AbstractSchemaReader;
8
use GoetasWebservices\XML\XSDReader\SchemaReaderLoadAbstraction;
9
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
10
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Group as AttributeGroup;
11
use GoetasWebservices\XML\XSDReader\Schema\Element\Group;
12
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementDef;
13
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementItem;
14
use GoetasWebservices\XML\XSDReader\Schema\Exception\TypeNotFoundException;
15
use GoetasWebservices\XML\XSDReader\Schema\Exception\SchemaException;
16
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeItem;
17
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeDef;
18
use GoetasWebservices\XML\XSDReader\Utils\UrlUtils;
19
20
class Schema
21
{
22
    /**
23
    * @var bool
24
    */
25
    protected $elementsQualification = false;
26
27
    /**
28
    * @var bool
29
    */
30
    protected $attributesQualification = false;
31
32
    /**
33
    * @var string|null
34
    */
35
    protected $targetNamespace;
36
37
    /**
38
    * @var Schema[]
39
    */
40
    protected $schemas = array();
41
42
    /**
43
    * @var Type[]
44
    */
45
    protected $types = array();
46
47
    /**
48
    * @var ElementDef[]
49
    */
50
    protected $elements = array();
51
52
    /**
53
    * @var Group[]
54
    */
55
    protected $groups = array();
56
57
    /**
58
    * @var AttributeGroup[]
59
    */
60
    protected $attributeGroups = array();
61
62
    /**
63
    * @var AttributeDef[]
64
    */
65
    protected $attributes = array();
66
67
    /**
68
    * @var string|null
69
    */
70
    protected $doc;
71
72
    /**
73
    * @var \GoetasWebservices\XML\XSDReader\Schema\SchemaItem[]
74
    */
75
    private $typeCache = array();
76
77
    /**
78
    * @return bool
79
    */
80
    public function getElementsQualification()
81
    {
82
        return $this->elementsQualification;
83
    }
84
85
    /**
86
    * @param bool $elementsQualification
87
    */
88 135
    public function setElementsQualification($elementsQualification)
89
    {
90 135
        $this->elementsQualification = $elementsQualification;
91 135
    }
92
93
    /**
94
    * @return bool
95
    */
96
    public function getAttributesQualification()
97
    {
98
        return $this->attributesQualification;
99
    }
100
101
    /**
102
    * @param bool $attributesQualification
103
    */
104 135
    public function setAttributesQualification($attributesQualification)
105
    {
106 135
        $this->attributesQualification = $attributesQualification;
107 135
    }
108
109
    /**
110
    * @return string|null
111
    */
112 135
    public function getTargetNamespace()
113
    {
114 135
        return $this->targetNamespace;
115
    }
116
117
    /**
118
    * @param string|null $targetNamespace
119
    */
120 135
    public function setTargetNamespace($targetNamespace)
121
    {
122 135
        $this->targetNamespace = $targetNamespace;
123 135
    }
124
125
    /**
126
    * @return Type[]
127
    */
128 15
    public function getTypes()
129
    {
130 15
        return $this->types;
131
    }
132
133
    /**
134
    * @return ElementDef[]
135
    */
136 9
    public function getElements()
137
    {
138 9
        return $this->elements;
139
    }
140
141
    /**
142
    * @return Schema[]
143
    */
144 135
    public function getSchemas()
145
    {
146 135
        return $this->schemas;
147
    }
148
149
    /**
150
    * @return AttributeDef[]
151
    */
152 3
    public function getAttributes()
153
    {
154 3
        return $this->attributes;
155
    }
156
157
    /**
158
    * @return Group[]
159
    */
160 6
    public function getGroups()
161
    {
162 6
        return $this->groups;
163
    }
164
165
    /**
166
    * @return string|null
167
    */
168
    public function getDoc()
169
    {
170
        return $this->doc;
171
    }
172
173
    /**
174
    * @param string $doc
175
    */
176 135
    public function setDoc($doc)
177
    {
178 135
        $this->doc = $doc;
179 135
    }
180
181 135
    public function addType(Type $type)
182
    {
183 135
        $this->types[$type->getName()] = $type;
184 135
    }
185
186 135
    public function addElement(ElementDef $element)
187
    {
188 135
        $this->elements[$element->getName()] = $element;
189 135
    }
190
191
    /**
192
    * @param string|null $namespace
193
    */
194 135
    public function addSchema(Schema $schema, $namespace = null)
195
    {
196 135
        if ($namespace !== null) {
197 135
            if ($schema->getTargetNamespace() !== $namespace) {
198
                throw new SchemaException(
199
                    sprintf(
200
                        "The target namespace ('%s') for schema, does not match the declared namespace '%s'",
201
                        $schema->getTargetNamespace(),
202
                        $namespace
203
                    )
204
                );
205
            }
206 135
            $this->schemas[$namespace] = $schema;
207 45
        } else {
208 135
            $this->schemas[] = $schema;
209
        }
210 135
    }
211
212 135
    public function addAttribute(AttributeDef $attribute)
213
    {
214 135
        $this->attributes[$attribute->getName()] = $attribute;
215 135
    }
216
217 135
    public function addGroup(Group $group)
218
    {
219 135
        $this->groups[$group->getName()] = $group;
220 135
    }
221
222 135
    public function addAttributeGroup(AttributeGroup $group)
223
    {
224 135
        $this->attributeGroups[$group->getName()] = $group;
225 135
    }
226
227
    /**
228
    * @return AttributeGroup[]
229
    */
230 3
    public function getAttributeGroups()
231
    {
232 3
        return $this->attributeGroups;
233
    }
234
235
    /**
236
     *
237
     * @param string $name
238
     * @return Group|false
239
     */
240 135
    public function getGroup($name)
241
    {
242 135
        if (isset($this->groups[$name])) {
243 135
            return $this->groups[$name];
244
        }
245 3
        return false;
246
    }
247
248
    /**
249
     *
250
     * @param string $name
251
     * @return ElementItem|false
252
     */
253 135
    public function getElement($name)
254
    {
255 135
        if (isset($this->elements[$name])) {
256 135
            return $this->elements[$name];
257
        }
258 3
        return false;
259
    }
260
261
    /**
262
     *
263
     * @param string $name
264
     * @return Type|false
265
     */
266 135
    public function getType($name)
267
    {
268 135
        if (isset($this->types[$name])) {
269 135
            return $this->types[$name];
270
        }
271 3
        return false;
272
    }
273
274
    /**
275
     *
276
     * @param string $name
277
     * @return AttributeItem|false
278
     */
279 135
    public function getAttribute($name)
280
    {
281 135
        if (isset($this->attributes[$name])) {
282 135
            return $this->attributes[$name];
283
        }
284 3
        return false;
285
    }
286
287
    /**
288
     *
289
     * @param string $name
290
     * @return AttributeGroup|false
291
     */
292 135
    public function getAttributeGroup($name)
293
    {
294 135
        if (isset($this->attributeGroups[$name])) {
295 135
            return $this->attributeGroups[$name];
296
        }
297 3
        return false;
298
    }
299
300
    public function __toString()
301
    {
302
        return sprintf("Target namespace %s", $this->getTargetNamespace());
303
    }
304
305
    /**
306
    * @param string $getter
307
    * @param string $name
308
    * @param string $namespace
309
    * @param bool[] $calling
310
    * @param bool $throw
311
    *
312
    * @return SchemaItem|null
313
    */
314 135
    protected function findSomethingNoThrow(
315
        $getter,
316
        $name,
317
        $namespace = null,
318
        array & $calling = array()
319
    ) {
320 135
        $calling[spl_object_hash($this)] = true;
321 135
        $cid = "$getter, $name, $namespace";
322
323 135
        if (isset($this->typeCache[$cid])) {
324 135
            return $this->typeCache[$cid];
325
        } elseif (
326
            (
327 135
                null === $namespace ||
328 135
                $this->getTargetNamespace() === $namespace
329 45
            ) &&
330 135
            (($item = $this->$getter($name)) instanceof SchemaItem)
331 45
        ) {
332 135
                return $this->typeCache[$cid] = $item;
333
        }
334
335 135
        return $this->findSomethingNoThrowSchemas(
336 135
            $this->getSchemas(),
337 135
            $cid,
338 135
            $getter,
339 135
            $name,
340 135
            $namespace,
341 90
            $calling
342 45
        );
343
    }
344
345
346
    /**
347
    * @param Schema[] $schemas
348
    * @param string $cid
349
    * @param string $getter
350
    * @param string $name
351
    * @param string $namespace
352
    * @param bool[] $calling
353
    * @param bool $throw
354
    *
355
    * @return SchemaItem|null
356
    */
357 135
    protected function findSomethingNoThrowSchemas(
358
        array $schemas,
359
        $cid,
360
        $getter,
361
        $name,
362
        $namespace = null,
363
        array & $calling = array()
364
    ) {
365 135
        foreach ($this->getSchemas() as $childSchema) {
366 135
            if (!isset($calling[spl_object_hash($childSchema)])) {
367 135
                $in = $childSchema->findSomethingNoThrow($getter, $name, $namespace, $calling);
368
369 135
                if ($in instanceof SchemaItem) {
370 135
                    return $this->typeCache[$cid] = $in;
371
                }
372 7
            }
373 7
        }
374 21
    }
375
376
    /**
377
     *
378
     * @param string $getter
379
     * @param string $name
380
     * @param string $namespace
381
     * @param bool[] $calling
382
     * @param bool $throw
383
     *
384
     * @throws TypeNotFoundException
385
     * @return SchemaItem
386
     */
387 135
    protected function findSomething($getter, $name, $namespace = null, &$calling = array())
388
    {
389 135
        $in = $this->findSomethingNoThrow(
390 135
            $getter,
391 135
            $name,
392 135
            $namespace,
393 90
            $calling
394 45
        );
395
396 135
        if ($in instanceof SchemaItem) {
397 135
            return $in;
398
        }
399
400 15
        throw new TypeNotFoundException(sprintf("Can't find the %s named {%s}#%s.", substr($getter, 3), $namespace, $name));
401
    }
402
403
    /**
404
     *
405
     * @param string $name
406
     * @param string $namespace
407
     * @return Type
408
     */
409 135
    public function findType($name, $namespace = null)
410
    {
411
        /**
412
        * @var Type $out
413
        */
414 135
        $out = $this->findSomething('getType', $name, $namespace);
415
416 135
        return $out;
417
    }
418
419
    /**
420
     *
421
     * @param string $name
422
     * @param string $namespace
423
     * @return Group
424
     */
425 135
    public function findGroup($name, $namespace = null)
426
    {
427
        /**
428
        * @var Group $out
429
        */
430 135
        $out = $this->findSomething('getGroup', $name, $namespace);
431
432 135
        return $out;
433
    }
434
435
    /**
436
     *
437
     * @param string $name
438
     * @param string $namespace
439
     * @return ElementDef
440
     */
441 135
    public function findElement($name, $namespace = null)
442
    {
443
        /**
444
        * @var ElementDef $out
445
        */
446 135
        $out = $this->findSomething('getElement', $name, $namespace);
447
448 135
        return $out;
449
    }
450
451
    /**
452
     *
453
     * @param string $name
454
     * @param string $namespace
455
     * @return AttributeItem
456
     */
457 135
    public function findAttribute($name, $namespace = null)
458
    {
459
        /**
460
        * @var AttributeItem $out
461
        */
462 135
        $out = $this->findSomething('getAttribute', $name, $namespace);
463
464 135
        return $out;
465
    }
466
467
    /**
468
     *
469
     * @param string $name
470
     * @param string $namespace
471
     * @return AttributeGroup
472
     */
473 135
    public function findAttributeGroup($name, $namespace = null)
474
    {
475
        /**
476
        * @var AttributeGroup
477
        */
478 135
        $out = $this->findSomething('getAttributeGroup', $name, $namespace);
479
480 135
        return $out;
481
    }
482
483
    /**
484
    * @var Schema[]
485
    */
486
    private static $loadedFiles = array();
487
488
    /**
489
    * @param string ...$keys
490
    *
491
    * @return bool
492
    */
493 135
    public static function hasLoadedFile(...$keys)
494
    {
495 135
        foreach ($keys as $key) {
496 135
            if (isset(self::$loadedFiles[$key])) {
497 135
                return true;
498
            }
499 1
        }
500
501 3
        return false;
502
    }
503
504
    /**
505
    * @param string ...$keys
506
    *
507
    * @return Schema
508
    *
509
    * @throws RuntimeException if loaded file not found
510
    */
511 135
    public static function getLoadedFile(...$keys)
512
    {
513 135
        foreach ($keys as $key) {
514 135
            if (isset(self::$loadedFiles[$key])) {
515 135
        return self::$loadedFiles[$key];
516
            }
517
        }
518
519
        throw new RuntimeException('Loaded file was not found!');
520
    }
521
522
    /**
523
    * @param string $key
524
    *
525
    * @return Schema
526
    */
527 135
    public static function setLoadedFile($key, Schema $schema)
528
    {
529 135
        self::$loadedFiles[$key] = $schema;
530
531 135
        return $schema;
532
    }
533
534 135
    public function setSchemaThingsFromNode(
535
        DOMElement $node,
536
        Schema $parent = null
537
    ) {
538 135
        $this->setDoc(AbstractSchemaReader::getDocumentation($node));
539
540 135
        if ($node->hasAttribute("targetNamespace")) {
541 135
            $this->setTargetNamespace($node->getAttribute("targetNamespace"));
542 45
        } elseif ($parent) {
543
            $this->setTargetNamespace($parent->getTargetNamespace());
544
        }
545 135
        $this->setElementsQualification($node->getAttribute("elementFormDefault") == "qualified");
546 135
        $this->setAttributesQualification($node->getAttribute("attributeFormDefault") == "qualified");
547 135
        $this->setDoc(AbstractSchemaReader::getDocumentation($node));
548 135
    }
549
550
    /**
551
    * @param string $file
552
    * @param string $namespace
553
    *
554
    * @return Closure
555
    */
556 135
    public static function loadImport(
557
        SchemaReaderLoadAbstraction $reader,
558
        Schema $schema,
559
        DOMElement $node
560
    ) {
561 135
        $base = urldecode($node->ownerDocument->documentURI);
562 135
        $file = UrlUtils::resolveRelativeUrl($base, $node->getAttribute("schemaLocation"));
563
564 135
        $namespace = $node->getAttribute("namespace");
565
566 135
        $globalSchemaInfo = $reader->getGlobalSchemaInfo();
567
568 135
        $keys = [];
569
570 135
        if (isset($globalSchemaInfo[$namespace])) {
571 135
            $keys[] = $globalSchemaInfo[$namespace];
572 45
        }
573
574 135
        $keys[] = $reader->getNamespaceSpecificFileIndex(
575 135
            $file,
576 90
            $namespace
577 45
        );
578
579 135
        $keys[] = $file;
580
581
        if (
582 135
            Schema::hasLoadedFile(...$keys)
583 45
        ) {
584 135
            $schema->addSchema(Schema::getLoadedFile(...$keys));
585
586
            return function() {
587 135
            };
588
        }
589
590 3
        return static::loadImportFresh($namespace, $reader, $schema, $file);
591
    }
592
593
    /**
594
    * @param string $namespace
595
    * @param string $file
596
    *
597
    * @return Closure
598
    */
599
    protected static function loadImportFresh(
600
        $namespace,
601
        SchemaReaderLoadAbstraction $reader,
602
        Schema $schema,
603
        $file
604
    ) {
605 3
        return function () use ($namespace, $reader, $schema, $file) {
606 3
            $newSchema = Schema::setLoadedFile(
607 3
                $file,
608 3
                ($namespace ? new Schema() : $schema)
609 1
            );
610
611 3
            if ($namespace) {
612
                $newSchema->addSchema($reader->getGlobalSchema());
613
                $schema->addSchema($newSchema);
614
            }
615 3
            $callbacks = $reader->schemaNode(
616 3
                $newSchema,
617 3
                $reader->getDOM(
618 3
                    $reader->hasKnownSchemaLocation($file)
619 1
                        ? $reader->getKnownSchemaLocation($file)
620 2
                        : $file
621 3
                )->documentElement,
622 2
                $schema
623 1
            );
624 3
            foreach ($callbacks as $callback) {
625 3
                $callback();
626 1
            }
627 3
        };
628
    }
629
}
630