Completed
Push — master ( 6b01cb...c96224 )
by Andreas
05:57
created

classgenerator   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 244
Duplicated Lines 0 %

Test Coverage

Coverage 94.81%

Importance

Changes 7
Bugs 0 Features 0
Metric Value
eloc 129
c 7
b 0
f 0
dl 0
loc 244
ccs 128
cts 135
cp 0.9481
rs 6.96
wmc 53

12 Methods

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