Passed
Push — master ( 7e4fbf...a83a87 )
by Andreas
05:11
created

manager::resolve_targetclass()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4.0218

Importance

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