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

classgenerator   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 246
Duplicated Lines 0 %

Test Coverage

Coverage 94.66%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 133
c 2
b 0
f 0
dl 0
loc 246
ccs 124
cts 131
cp 0.9466
rs 6.4799
wmc 54

12 Methods

Rating   Name   Duplication   Size   Complexity  
A end_class() 0 3 1
A convert_type() 0 10 1
A __construct() 0 5 1
B write() 0 44 8
B register_aliases() 0 16 7
C write_properties() 0 43 15
A write_parent_getter() 0 17 4
A write_constructor() 0 9 3
A get_class_prefix() 0 6 2
A add_line() 0 7 3
A write_annotations() 0 21 6
A begin_class() 0 16 3

How to fix   Complexity   

Complex Class

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