Passed
Push — master ( 9d605b...f6d124 )
by Andreas
02:52
created

driver::loadMetadataForClass()   C

Complexity

Conditions 12
Paths 40

Size

Total Lines 92
Code Lines 54

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 58
CRAP Score 12.0006

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 12
eloc 54
nc 40
nop 2
dl 0
loc 92
ccs 58
cts 59
cp 0.9831
crap 12.0006
rs 6.9666
c 3
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\translator;
12
use midgard\portable\mgdschema\property;
13
use Doctrine\Persistence\Mapping\Driver\MappingDriver as driver_interface;
14
use Doctrine\Persistence\Mapping\ClassMetadata;
15
use Doctrine\ORM\Mapping\MappingException;
16
use Doctrine\ORM\Mapping\ClassMetadata as CM;
17
use Doctrine\DBAL\Types\Types;
18
19
class driver implements driver_interface
20
{
21
    private array $dbtypemap = [
22
        'unsigned integer' => ['type' => Types::INTEGER, 'default' => 0], // <== UNSIGNED in Doctrine\DBAL\Schema\Column
23
        'integer' => ['type' => Types::INTEGER, 'default' => 0],
24
        'boolean' => ['type' => Types::BOOLEAN, 'default' => false],
25
        'bool' => ['type' => Types::BOOLEAN, 'default' => false],
26
        'guid' => ['type' => Types::STRING, 'length' => 80, 'default' => ''],
27
        'varchar(80)' => ['type' => Types::STRING, 'length' => 80, 'default' => ''],
28
        'string' => ['type' => Types::STRING, 'length' => 255, 'default' => ''],
29
        'datetime' => ['type' => Types::DATETIME_MUTABLE, 'default' => '0001-01-01 00:00:00'],
30
        'text' => ['type' => Types::TEXT],
31
        'longtext' => ['type' => Types::TEXT],
32
        'float' => ['type' => Types::FLOAT, 'default' => 0.0],
33
        'double' => ['type' => Types::FLOAT, 'default' => 0.0]
34
    ];
35
36
    private string $vardir;
37
38
    private ?array $types = null;
39
40
    private string $namespace;
41
42
    private manager $manager;
43
44
    /**
45
     * keep track of the namespaces already in use and
46
     * remember the used manager instance for resolving types
47
     */
48
    private static array $processed_namespaces = [];
49
50
    /**
51
     * indicates whether the current namespace has been used before
52
     */
53
    private bool $is_fresh_namespace;
54
55 13
    public function __construct(array $schemadirs, string $vardir, string $namespace = 'midgard\\portable\\entities')
56
    {
57 13
        $this->vardir = $vardir . '/';
58 13
        $this->namespace = $namespace;
59
60 13
        $this->is_fresh_namespace = !isset(self::$processed_namespaces[$this->namespace]);
61 13
        if ($this->is_fresh_namespace) {
62 13
            $this->manager = new manager($schemadirs, $this->namespace);
63 13
            self::$processed_namespaces[$this->namespace] = ["manager" => $this->manager];
64
        } else {
65
            // reuse manager instance
66
            $this->manager = self::$processed_namespaces[$this->namespace]["manager"];
67
        }
68
    }
69
70 9
    public function is_fresh_namespace() : bool
71
    {
72 9
        return $this->is_fresh_namespace;
73
    }
74
75 9
    public function get_namespace() : string
76
    {
77 9
        return $this->namespace;
78
    }
79
80 10
    public function get_manager() : manager
81
    {
82 10
        return $this->manager;
83
    }
84
85 9
    public function get_vardir() : string
86
    {
87 9
        return rtrim($this->vardir, '/');
88
    }
89
90 17
    private function initialize()
91
    {
92 17
        $this->types ??= $this->manager->get_types();
93
    }
94
95
    /**
96
     * {@inheritDoc}
97
     */
98 2
    public function getAllClassNames() : array
99
    {
100 2
        $this->initialize();
101 2
        return array_keys($this->types);
102
    }
103
104
    /**
105
     * {@inheritDoc}
106
     */
107 13
    public function isTransient($classname) : bool
108
    {
109 13
        $this->initialize();
110 13
        return !isset($this->types[$classname]);
111
    }
112
113
    /**
114
     * {@inheritDoc}
115
     */
116 16
    public function loadMetadataForClass($classname, ClassMetadata $metadata)
117
    {
118 16
        $this->initialize();
119 16
        if (!isset($this->types[$classname])) {
120 4
            throw MappingException::classIsNotAValidEntityOrMappedSuperClass($classname);
121
        }
122
123 15
        $type = $this->types[$classname];
124
125
        // TODO: extends
126
127 15
        $table = [
128 15
            'name' => '`' . $type->table . '`',
129 15
            'options' => [
130
                //Doctrine's default on MySQL is InnoDB, and the foreign keys don't play well with Midgard logic
131
                //TODO: Maybe at some point we could try to figure out how to explicitly disable foreign key constraint creation instead
132 15
                'engine' => 'MyISAM'
133 15
            ]
134 15
        ];
135
136 15
        $metadata->setPrimaryTable($table);
137
138 15
        $properties = $type->get_properties();
139 15
        $metadata->midgard['parent'] = $type->parent;
0 ignored issues
show
Bug introduced by
Accessing midgard on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
140 15
        $metadata->midgard['parentfield'] = $type->parentfield;
141 15
        $metadata->midgard['upfield'] = $type->upfield;
142 15
        $metadata->midgard['childtypes'] = $this->manager->get_child_classes($type->name);
143 15
        $metadata->midgard['field_aliases'] = $type->field_aliases;
144 15
        $metadata->midgard['field_order'] = array_keys($properties);
145
146 15
        foreach ($properties as $name => $property) {
147
            // doctrine can handle id links only
148 15
            if (   $property->link
149 15
                && $target_class = $this->manager->resolve_targetclass($property)) {
150 10
                $link_mapping = [
151 10
                    'fieldName' => $property->name,
152 10
                    'targetEntity' => $target_class,
153 10
                    'joinColumns' => [
154 10
                        [
155 10
                            'name' => $property->field,
156 10
                            'referencedColumnName' => $property->link['field']
157 10
                        ]
158 10
                    ],
159 10
                    'midgard:link_target' => $property->link['field'],
160 10
                    'midgard:link_name' => $property->link['target'],
161 10
                ];
162
163 10
                if ($link_mapping['fieldName'] == 'id') {
164
                    $link_mapping['id'] = true;
165
                }
166
167 10
                $metadata->mapManyToOne($link_mapping);
168 10
                continue;
169
            }
170
171 15
            $mapping = $this->dbtypemap[$property->dbtype] ?? $this->parse_dbtype($property);
172
173 15
            if ($property->unique) {
174 15
                if ($property->name == 'guid') {
175 15
                    $mapping['unique'] = true;
176
                } else {
177
                    //we can't set this as a real DB constraint because of softdelete and tree hierarchies
178 6
                    $metadata->midgard['unique_fields'][] = $property->name;
179
                }
180
            }
181
182 15
            $mapping['columnName'] = $property->field;
183 15
            $mapping['midgard:midgard_type'] = translator::to_constant($property->type);
184 15
            $mapping['midgard:description'] = $property->description;
185
186
            // its some other link (like guid link)
187 15
            if ($property->noidlink) {
188 5
                $mapping['noidlink'] = $property->noidlink;
189
            }
190
191 15
            if ($property->name == $type->primaryfield) {
192 15
                $mapping['id'] = true;
193 15
                unset($mapping['default']);
194 15
                if ($mapping['type'] == Types::INTEGER) {
195 15
                    $metadata->setIdGeneratorType(CM::GENERATOR_TYPE_AUTO);
196
                } else {
197 3
                    $metadata->setIdGeneratorType(CM::GENERATOR_TYPE_NONE);
198
                }
199
            }
200
201 15
            $mapping['fieldName'] = $name;
202
203 15
            $metadata->mapField($mapping);
204
205 15
            if ($property->index) {
206 14
                $metadata->table['indexes'] ??= [];
0 ignored issues
show
Bug introduced by
Accessing table on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
207 14
                $metadata->table['indexes'][$type->name . '_' . $property->name . '_idx'] = ['columns' => [$property->field]];
208
            }
209
        }
210
    }
211
212 5
    private function parse_dbtype(property $property) : array
213
    {
214 5
        if (strpos($property->dbtype, 'varchar') === 0) {
215 5
            $mapping = [
216 5
                'type' => Types::STRING,
217 5
            ];
218
219 5
            if (substr($property->dbtype, -1) == ')') {
220 4
                $mapping['length'] = (int) substr($property->dbtype, 8, -1);
221 4
                return $mapping;
222
            }
223
224 2
            if (substr($property->dbtype, -8) == ') binary') {
225
                // see http://www.doctrine-project.org/jira/browse/DDC-1817
226 2
                $mapping['length'] = (int) substr($property->dbtype, 8, -1);
227 2
                $mapping['comment'] = 'BINARY';
228 2
                return $mapping;
229
            }
230 2
        } elseif (strpos($property->dbtype, 'set') === 0) {
231
            // see http://docs.doctrine-project.org/en/latest/cookbook/mysql-enums.html
232 2
            if (!empty($this->dbtypemap[$property->type])) {
233 2
                $mapping = $this->dbtypemap[$property->type];
234 2
                $mapping['comment'] = $property->dbtype;
235 2
                return $mapping;
236
            }
237
        } elseif (strpos(strtolower($property->dbtype), 'decimal') === 0) {
238
            $matches = [];
239
            preg_match('/DECIMAL\((\d+),(\d+)\)/i', $property->dbtype, $matches);
240
            $mapping = [
241
                'type' => Types::DECIMAL,
242
                'precision' => $matches[1],
243
                'scale' => $matches[2]
244
            ];
245
            return $mapping;
246
        }
247
248
        throw new \Exception($property->get_parent()->name . ': ' . $property->name . ' ' . $property->dbtype . ' not implemented yet');
249
    }
250
}
251