Issues (134)

src/classgenerator.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;
9
10
use midgard\portable\mgdschema\manager;
11
use midgard\portable\mgdschema\type;
12
use midgard\portable\mgdschema\translator;
13
use midgard\portable\api\mgdobject;
14
use midgard\portable\api\user;
15
use midgard\portable\api\parameter;
16
use midgard\portable\api\person;
17
use midgard\portable\api\repligard;
18
use midgard\portable\api\attachment;
19
use midgard\portable\api\metadata;
20
21
class classgenerator
22
{
23
    private string $output = '';
24
25
    private readonly string $filename;
26
27
    private readonly manager $manager;
28
29
    private bool $dev_mode = false;
30
31 8
    public function __construct(manager $manager, string $filename, bool $dev_mode = false)
32
    {
33 8
        $this->manager = $manager;
0 ignored issues
show
The property manager is declared read-only in midgard\portable\classgenerator.
Loading history...
34 8
        $this->filename = $filename;
0 ignored issues
show
The property filename is declared read-only in midgard\portable\classgenerator.
Loading history...
35 8
        $this->dev_mode = $dev_mode;
36
    }
37
38 8
    private function add_line(string $line, bool $force_break = false)
39
    {
40 8
        $this->output .= $line;
41 8
        if ($force_break || $this->dev_mode) {
42 8
            $this->output .= "\n";
43
        } else {
44 1
            $this->output .= ' ';
45
        }
46
    }
47
48 8
    public function write(string $namespace = '')
49
    {
50 8
        if (file_exists($this->filename)) {
51 5
            unlink($this->filename);
52
        }
53
54 8
        $types = $this->manager->get_types();
55 8
        uasort($types, function (type $a, type $b) {
56 8
            if (   !empty($a->extends)
57 8
                && !empty($b->extends)) {
58 8
                return strnatcmp($a->extends, $b->extends);
59
            }
60
            if (!empty($a->extends)) {
61
                return -1;
62
            }
63
            if (!empty($b->extends)) {
64
                return 1;
65
            }
66
            return 0;
67 8
        });
68
69 8
        $this->add_line('<?php');
70
71 8
        if (!empty($namespace)) {
72 8
            $this->add_line('namespace ' . $namespace . ';');
73 8
            $this->add_line('use ' . mgdobject::class . ' as midgard_object;');
74 8
            $this->add_line('use midgard_datetime;');
75
        }
76 8
        $this->add_line('use ' . user::class . ' as base_user;');
77 8
        $this->add_line('use ' . person::class . ' as base_person;');
78 8
        $this->add_line('use ' . parameter::class . ' as base_parameter;');
79 8
        $this->add_line('use ' . repligard::class . ' as base_repligard;');
80 8
        $this->add_line('use ' . attachment::class . ' as base_attachment; ');
81 8
        $this->add_line('use ' . metadata::class . ' as midgard_metadata; ');
82
83 8
        foreach ($types as $type) {
84 8
            $this->convert_type($type);
85
        }
86
87 8
        $this->register_aliases($namespace);
88
89
        //todo: midgard_blob special handling
90
91 8
        file_put_contents($this->filename, $this->output);
92
    }
93
94 8
    private function register_aliases(string $namespace)
95
    {
96 8
        $prefix = $this->get_class_prefix($namespace);
97
98 8
        foreach ($this->manager->get_types() as $type) {
99 8
            if (   $prefix !== ''
100 8
                && !class_exists($type->name)) {
101 1
                $this->add_line('class_alias( "' . $prefix . $type->name . '", "' . $type->name . '");');
102
            }
103
        }
104
105 8
        foreach ($this->manager->get_inherited_mapping() as $child => $parent) {
106 5
            $this->add_line('class_alias( "' . $prefix . $parent . '", "' . $prefix . $child . '");');
107 5
            if (   $prefix !== ''
108 5
                && !class_exists($child)) {
109 1
                $this->add_line('class_alias( "' . $prefix . $parent . '", "' . $child . '");');
110
            }
111
        }
112
    }
113
114 8
    private function get_class_prefix(string $namespace) : string
115
    {
116 8
        if ($namespace === '') {
117
            return '';
118
        }
119 8
        return str_replace('\\', '\\\\', $namespace) . '\\\\';
120
    }
121
122 8
    private function convert_type(type $type)
123
    {
124 8
        $this->begin_class($type);
125 8
        $objects = $this->write_properties($type);
126
127 8
        $this->write_constructor($objects);
128
129 8
        $this->write_parent_getter($type);
130
131 8
        $this->end_class();
132
    }
133
134 8
    private function write_properties(type $type) : array
135
    {
136 8
        $objects = [];
137
138 8
        foreach ($type->get_properties() as $name => $property) {
139 8
            if ($name == 'guid') {
140 8
                continue;
141
            }
142 8
            $line = ' protected $' . $name;
143 8
            $default = null;
144 8
            switch (translator::to_constant($property->type)) {
145
                case translator::TYPE_BOOLEAN:
146 8
                    $default = 'false';
147 8
                    break;
148
                case translator::TYPE_FLOAT:
149 5
                    $default = '0.0';
150 5
                    break;
151
                case translator::TYPE_UINT:
152 8
                    if ($name == $type->primaryfield) {
153
                        // no default value for identifier, because otherwise, Doctrine will think it's a detached entity
154 8
                        break;
155
                    }
156
                case translator::TYPE_INT:
157 8
                    $default = '0';
158 8
                    break;
159
                case translator::TYPE_GUID:
160
                case translator::TYPE_STRING:
161
                case translator::TYPE_LONGTEXT:
162 8
                    $default = "''";
163 8
                    break;
164
                case translator::TYPE_TIMESTAMP:
165 8
                    $objects[$name] = 'new midgard_datetime("0001-01-01 00:00:00")';
166 8
                    break;
167
            }
168 8
            if (   $default !== null
169
                   // we need to skip working links because in this case, Doctrine expects objects as values
170 8
                && (   !$property->link
171 8
                    || $this->manager->resolve_targetclass($property) === false)) {
172 8
                $line .= ' = ' . $default;
173
            }
174 8
            $this->add_line($line . ';');
175
        }
176 8
        return $objects;
177
    }
178
179 8
    private function write_constructor(array $objects)
180
    {
181 8
        if (!empty($objects)) {
182 8
            $this->add_line('public function __construct($id = null) {');
183 8
            foreach ($objects as $name => $code) {
184 8
                $this->add_line('$this->' . $name . ' = ' . $code . ';');
185
            }
186 8
            $this->add_line('parent::__construct($id);');
187 8
            $this->add_line('}');
188
        }
189
    }
190
191 8
    private function write_parent_getter(type $type)
192
    {
193 8
        $candidates = [];
194
195 8
        if (!empty($type->upfield)) {
196 8
            $candidates[] = $type->upfield;
197
        }
198 8
        if (!empty($type->parentfield)) {
199 8
            $candidates[] = $type->parentfield;
200
        }
201 8
        if (empty($candidates)) {
202 8
            return;
203
        }
204
205 8
        $this->add_line('public function get_parent() {');
206 8
        $this->add_line(' return $this->load_parent(' . var_export($candidates, true) . ');');
207 8
        $this->add_line('}');
208
    }
209
210 8
    private function write_annotations(type $type)
211
    {
212 8
        $this->add_line('/**', true);
213 8
        $properties = $type->get_properties();
214 8
        foreach ($type->field_aliases as $alias => $target) {
215 5
            $properties[$alias] = clone $properties[$target];
216 5
            $properties[$alias]->description = 'Alias for ' . $target;
217
        }
218 8
        foreach ($properties as $name => $property) {
219 8
            if (!str_starts_with($property->name, 'metadata_')) {
220 8
                $line = translator::to_phptype($property->type) . ' $' . $name;
221 8
                if ($property->description) {
222 8
                    $line .= ' ' . trim($property->description);
223
                }
224 8
                $this->add_line(' * @property ' . $line, true);
225
            }
226
        }
227 8
        foreach ($type->get_mixins() as $name => $mixin) {
228 8
            $this->add_line(' * @property ' . $mixin->name . ' $' . $name, true);
229
        }
230 8
        $this->add_line('*/', true);
231
    }
232
233 8
    private function begin_class(type $type)
234
    {
235 8
        $this->write_annotations($type);
236 8
        $this->add_line('class ' . $type->name . ' extends ' . $type->extends);
237 8
        $mixins = $type->get_mixins();
238 8
        $interfaces = array_filter(array_map(function (string $name) {
239 8
            if (interface_exists('\\midgard\\portable\\storage\\interfaces\\' . $name)) {
240 8
                return '\\midgard\\portable\\storage\\interfaces\\' . $name;
241
            }
242
            return false;
243 8
        }, array_keys($mixins)));
244
245 8
        if (!empty($interfaces)) {
246 8
            $this->add_line(' implements ' . implode(', ', $interfaces));
247
        }
248 8
        $this->add_line('{');
249
    }
250
251 8
    private function end_class()
252
    {
253 8
        $this->add_line('}');
254
    }
255
}
256