Passed
Branch master (b2f909)
by Andreas
04:01
created

manager   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 221
Duplicated Lines 0 %

Test Coverage

Coverage 88.68%

Importance

Changes 0
Metric Value
eloc 101
c 0
b 0
f 0
dl 0
loc 221
ccs 94
cts 106
cp 0.8868
rs 8.8
wmc 45

11 Methods

Rating   Name   Duplication   Size   Complexity  
A get_fcqn() 0 6 2
A __construct() 0 4 1
A get_types() 0 4 1
A get_inherited_mapping() 0 4 1
A register_child_class() 0 6 2
A add_type() 0 20 6
A resolve_targetclass() 0 14 4
A get_child_classes() 0 4 1
A get_type_by_shortname() 0 10 3
C create_merged_types() 0 50 12
C initialize() 0 41 12

How to fix   Complexity   

Complex Class

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

1
<?php
2
/**
3
 * @author CONTENT CONTROL http://www.contentcontrol-berlin.de/
4
 * @copyright CONTENT CONTROL http://www.contentcontrol-berlin.de/
5
 * @license http://www.gnu.org/licenses/gpl.html GNU General Public License
6
 */
7
8
namespace midgard\portable\mgdschema;
9
10
use midgard\portable\xmlreader;
11
use midgard\portable\storage\connection;
12
13
class manager
14
{
15
    /**
16
     * @var type[]
17
     */
18
    private $types;
19
20
    /**
21
     * @var string[]
22
     */
23
    private $schemadirs;
24
25
    /**
26
     * @var string
27
     */
28
    private $namespace;
29
30
    /**
31
     * @var array
32
     */
33
    private $merged_types = [];
34
35
    /**
36
     *
37
     * @var array
38
     */
39
    private $child_classes = [];
40
41 13
    public function __construct(array $schemadirs, string $namespace)
42
    {
43 13
        $this->schemadirs = $schemadirs;
44 13
        $this->namespace = $namespace;
45
    }
46
47
    /**
48
     * @return type[]
49
     */
50 16
    public function get_types() : array
51
    {
52 16
        $this->initialize();
53 16
        return $this->types;
54
    }
55
56 10
    public function get_inherited_mapping() : array
57
    {
58 10
        $this->initialize();
59 10
        return $this->merged_types;
60
    }
61
62 15
    public function get_child_classes(string $typename) : array
63
    {
64 15
        $this->initialize();
65 15
        return $this->child_classes[$typename] ?? [];
66
    }
67
68 14
    public function resolve_targetclass(property $property) : string
69
    {
70 14
        $this->initialize();
71
72 14
        $fqcn = $this->get_fcqn($property->link['target']);
73
74 14
        if (   isset($this->types[$fqcn])
75 14
            || $property->link['target'] === $property->get_parent()->name) {
76 14
            return $property->link['target'];
77
        }
78
        if (!isset($this->merged_types[$property->link['target']])) {
79
            throw new \Exception('Link to unknown class ' . $property->link['target']);
80
        }
81
        return $this->merged_types[$property->link['target']];
82
    }
83
84 19
    private function initialize()
85
    {
86 19
        if ($this->types !== null) {
87 18
            return;
88
        }
89 13
        $reader = new xmlreader;
90 13
        $types = $reader->parse(dirname(__DIR__, 2) . '/xml/core.xml');
91
92 13
        foreach ($this->schemadirs as $schemadir) {
93 13
            foreach (glob($schemadir . '*.xml') as $filename) {
94 13
                if (!file_exists($filename)) {
95
                    connection::log()->warning('File exists check for ' . $filename . ' returned false, skipping');
96
                    continue;
97
                }
98 13
                $types = array_merge($types, $reader->parse($filename));
99
            }
100
        }
101
102 13
        $tablemap = [];
103 13
        foreach ($types as $name => $type) {
104 13
            if ($type->parent) {
105 13
                $this->register_child_class($type);
106
            }
107
108 13
            if (!isset($tablemap[$type->table])) {
109 13
                $tablemap[$type->table] = [];
110
            }
111 13
            $tablemap[$type->table][] = $type;
112
        }
113
114 13
        foreach ($tablemap as $name => $types) {
115 13
            if (count($types) == 1) {
116 13
                $this->add_type($types[0]);
117 13
                unset($tablemap[$name]);
118
            }
119
        }
120
121
        // We need to process those separately, to be sure the targets for rewriting links are present
122 13
        while ($types = array_pop($tablemap)) {
123 5
            if (!$this->create_merged_types($types)) {
124
                array_push($types, $tablemap);
125
            }
126
        }
127
    }
128
129 13
    private function register_child_class(type $type)
130
    {
131 13
        if (!isset($this->child_classes[$type->parent])) {
132 13
            $this->child_classes[$type->parent] = [];
133
        }
134 13
        $this->child_classes[$type->parent][$type->name] = $type->parentfield;
135
    }
136
137
    /**
138
     * This sort of provides a workaround for situations where two tables use the same name
139
     *
140
     * @param type[] $types
141
     */
142 5
    private function create_merged_types(array $types) : bool
143
    {
144 5
        usort($types, function(type $a, type $b) {
145 5
            return strcasecmp($a->name, $b->name);
146
        });
147
148 5
        $root_type = null;
149 5
        foreach ($types as $i => $type) {
150
            // TODO: We should have a second pass here that prefers classnames starting with midgard_
151 5
            if ($type->extends === '\\midgard\\portable\\api\\mgdobject') {
152 5
                $root_type = $type;
153 5
                unset($types[$i]);
154 5
                break;
155
            }
156
        }
157 5
        if (empty($root_type)) {
158
            throw new \Exception('could not determine root type of merged group');
159
        }
160
161 5
        foreach ($types as $type) {
162 5
            foreach ($type->get_properties() as $property) {
163 5
                if ($root_type->has_property($property->name)) {
164 5
                    $root_property = $root_type->get_property($property->name);
165 5
                    if ($root_property->field !== $property->field) {
166
                        connection::log()->error('Naming collision in ' . $root_type->name . ': Field ' . $type->name . '.' . $property->name . ' cannot use column ' . $property->field);
167
                    }
168 5
                    if ($root_property->type !== $property->type) {
169
                        connection::log()->warning('Naming collision in ' . $root_type->name . ': Field ' . $type->name . '.' . $property->name . ' cannot use type ' . $property->type);
170
                    }
171 5
                    continue;
172
                }
173 5
                $root_type->add_property($property);
174
            }
175
176 5
            if (isset($this->child_classes[$type->name])) {
177 5
                foreach ($this->child_classes[$type->name] as $childname => $parentfield) {
178 5
                    $child_type = $this->get_type_by_shortname($childname);
179 5
                    if ($child_type === null) {
180
                        return false;
181
                    }
182
183 5
                    $child_type->parent = $root_type->name;
184 5
                    $this->register_child_class($child_type);
185
                }
186 5
                unset($this->child_classes[$type->name]);
187
            }
188 5
            $this->merged_types[$type->name] = $root_type->name;
189
        }
190 5
        $this->add_type($root_type);
191 5
        return true;
192
    }
193
194 5
    private function get_type_by_shortname(string $classname) : type
195
    {
196 5
        $fqcn = $this->get_fcqn($classname);
197 5
        if (!isset($this->types[$fqcn])) {
198 5
            if (!isset($this->merged_types[$classname])) {
199
                return null;
200
            }
201 5
            $fqcn = $this->get_fcqn($this->merged_types[$classname]);
202
        }
203 5
        return $this->types[$fqcn];
204
    }
205
206 13
    private function add_type(type $type)
207
    {
208 13
        $classname = $type->name;
209
        // TODO: This should probably be in classgenerator
210 13
        if ($classname === 'midgard_user') {
211 13
            $type->extends = 'base_user';
212
        }
213 13
        if ($classname === 'midgard_person') {
214 13
            $type->extends = 'base_person';
215
        }
216 13
        if ($classname === 'midgard_parameter') {
217 13
            $type->extends = 'base_parameter';
218
        }
219 13
        if ($classname === 'midgard_repligard') {
220 13
            $type->extends = 'base_repligard';
221
        }
222 13
        if ($classname === 'midgard_attachment') {
223 13
            $type->extends = 'base_attachment';
224
        }
225 13
        $this->types[$this->get_fcqn($classname)] = $type;
226
    }
227
228 16
    private function get_fcqn(string $classname) : string
229
    {
230 16
        if (!empty($this->namespace)) {
231 16
            return $this->namespace . '\\' . $classname;
232
        }
233
        return $classname;
234
    }
235
}
236