Passed
Push — master ( be0edb...ac67e8 )
by Andreas
03:49
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 6
CRAP Score 4.5923

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