Passed
Push — cleanup ( 1df3c6 )
by Luis
14:39
created

StructureBuilder::extractType()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 12
nc 4
nop 1
dl 0
loc 18
ccs 12
cts 12
cp 1
crap 4
rs 9.2
c 0
b 0
f 0
1
<?php
2
/**
3
 * PHP version 7.1
4
 *
5
 * This source file is subject to the license that is bundled with this package in the file LICENSE.
6
 */
7
namespace PhUml\Parser;
8
9
use PhUml\Code\Attribute;
10
use PhUml\Code\ClassDefinition;
11
use PhUml\Code\Definition;
12
use PhUml\Code\InterfaceDefinition;
13
use PhUml\Code\Method;
14
use PhUml\Code\Structure;
15
use PhUml\Code\TypeDeclaration;
16
use PhUml\Code\Variable;
17
18
class StructureBuilder
19
{
20
    /** @var Structure */
21
    private $structure;
22
23
    public function __construct(Structure $structure = null)
24
    {
25
        $this->structure = $structure ?? new Structure();
26
    }
27
28
    public function buildFromDefinitions(Definitions $definitions): Structure
29
    {
30
        foreach ($definitions->all() as $definition) {
31
            if ($definitions->isClass($definition) && !$this->structure->has($definition['class'])) {
32
                $this->structure->addClass($this->buildClass($definitions, $definition));
33
            } elseif ($definitions->isInterface($definition) && !$this->structure->has($definition['interface'])) {
34
                $this->structure->addInterface($this->buildInterface($definitions, $definition));
35
            }
36
        }
37
        return $this->structure;
38
    }
39
40
    protected function buildInterface(Definitions $definitions, array $interface): InterfaceDefinition
41
    {
42
        return new InterfaceDefinition(
43
            $interface['interface'],
44
            $this->buildMethods($interface),
45
            $this->resolveRelatedInterface($definitions, $interface['extends'])
46
        );
47
    }
48
49
    protected function buildClass(Definitions $definitions, array $class): ClassDefinition
50
    {
51
        return new ClassDefinition(
52
            $class['class'],
53
            $this->buildAttributes($class),
54
            $this->buildMethods($class),
55
            $this->buildInterfaces($definitions, $class['implements']),
56
            $this->resolveParentClass($definitions, $class['extends'])
57
        );
58
    }
59
60
    /** @return Method[] */
61
    protected function buildMethods(array $definition): array
62
    {
63
        $methods = [];
64
        foreach ($definition['functions'] as $method) {
65
            [$name, $modifier, $parameters] = $method;
66
            $methods[] = Method::$modifier($name, $this->buildParameters($parameters));
67
        }
68
        return $methods;
69
    }
70
71
    /** @return Variable[] */
72
    private function buildParameters(array $parameters): array
73
    {
74
        $methodParameters = [];
75
        foreach ($parameters as $parameter) {
76
            [$type, $name] = $parameter;
77
            $methodParameters[] = Variable::declaredWith($name, TypeDeclaration::from($type));
78
        }
79
        return $methodParameters;
80
    }
81
82
    /** @return Attribute[] */
83
    protected function buildAttributes(array $class): array
84
    {
85
        $attributes = [];
86
        foreach ($class['attributes'] as $attribute) {
87
            [$name, $modifier, $comment] = $attribute;
88
            $attributes[] = Attribute::$modifier($name, $this->extractTypeFrom($comment));
89
        }
90
        return $attributes;
91
    }
92
93
    private function extractTypeFrom(?string $comment): TypeDeclaration
94
    {
95
        if ($comment === null) {
96
            return TypeDeclaration::absent();
97
        }
98
99
        $type = null;  // There might be no type information in the comment
100
        $matches = [];
101
        $arrayExpression = '/^[\s*]*@var\s+array\(\s*(\w+\s*=>\s*)?(\w+)\s*\).*$/m';
102
        if (preg_match($arrayExpression, $comment, $matches)) {
103
            $type = $matches[2];
104
        } else {
105
            $typeExpression = '/^[\s*]*@var\s+(\S+).*$/m';
106
            if (preg_match($typeExpression, $comment, $matches)) {
107
                $type = trim($matches[1]);
108
            }
109
        }
110
        return TypeDeclaration::from($type);
111
    }
112
113
    /**
114
     * @param string[] $implements
115
     * @return Definition[]
116
     */
117
    protected function buildInterfaces(Definitions $definitions, array $implements): array
118
    {
119
        $interfaces = [];
120
        foreach ($implements as $interface) {
121
            $interfaces[] = $this->resolveRelatedInterface($definitions, $interface);
122
        }
123
        return $interfaces;
124
    }
125
126
    protected function resolveRelatedInterface(Definitions $definitions, ?string $interface): ?Definition
127
    {
128
        if ($interface === null) {
129
            return null;
130
        }
131
        if (!$this->structure->has($interface)) {
132
            $this->structure->addInterface($this->buildInterface(
133
                $definitions,
134
                $definitions->get($interface)
135
            ));
136
        }
137
        return $this->structure->get($interface);
138
    }
139
140
    protected function resolveParentClass(Definitions $definitions, ?string $parent): ?Definition
141
    {
142
        if ($parent === null) {
143
            return null;
144
        }
145
        if (!$this->structure->has($parent)) {
146
            $this->structure->addClass($this->buildClass($definitions, $definitions->get($parent)));
147
        }
148
        return $this->structure->get($parent);
149
    }
150
}
151