Issues (134)

src/mgdschema/manager.php (2 issues)

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