Completed
Push — master ( 36b8cd...f7ff46 )
by Andreas
02:55
created

driver::loadMetadataForClass()   C

Complexity

Conditions 13
Paths 58

Size

Total Lines 92
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 46
CRAP Score 13.0016

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 13
eloc 53
c 3
b 0
f 0
nc 58
nop 2
dl 0
loc 92
ccs 46
cts 47
cp 0.9787
crap 13.0016
rs 6.6166

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 midgard\portable\storage\type\datetime;
14
use Doctrine\Persistence\Mapping\Driver\MappingDriver as driver_interface;
15
use Doctrine\Persistence\Mapping\ClassMetadata;
16
use Doctrine\ORM\Mapping\MappingException;
17
use Doctrine\ORM\Mapping\ClassMetadata as CM;
18
use Doctrine\DBAL\Types\Type;
19
20
class driver implements driver_interface
21
{
22
    private $dbtypemap = [
23
        'unsigned integer' => ['type' => Type::INTEGER, 'default' => 0], // <== UNSIGNED in Doctrine\DBAL\Schema\Column
24
        'integer' => ['type' => Type::INTEGER, 'default' => 0],
25
        'boolean' => ['type' => Type::BOOLEAN, 'default' => false],
26
        'bool' => ['type' => Type::BOOLEAN, 'default' => false],
27
        'guid' => ['type' => Type::STRING, 'length' => 80, 'default' => ''],
28
        'varchar(80)' => ['type' => Type::STRING, 'length' => 80, 'default' => ''],
29
        'string' => ['type' => Type::STRING, 'length' => 255, 'default' => ''],
30
        'datetime' => ['type' => datetime::TYPE, 'default' => '0001-01-01 00:00:00'],
31
        'text' => ['type' => Type::TEXT],
32
        'longtext' => ['type' => Type::TEXT],
33
        'float' => ['type' => Type::FLOAT, 'default' => 0.0],
34
        'double' => ['type' => Type::FLOAT, 'default' => 0.0]
35
    ];
36
37
    private $vardir;
38
39
    private $types;
40
41
    private $namespace;
42
43
    private $manager;
44
45
    /**
46
     * keep track of the namespaces already in use and
47
     * remember the used manager instance for resolving types
48
     *
49
     * @var array
50
     */
51
    private static $processed_namespaces = [];
52
53
    /**
54
     * indicates whether the current namespace has been used before
55
     *
56
     * @var boolean
57
     */
58
    private $is_fresh_namespace;
59
60 14
    public function __construct(array $schemadirs, string $vardir, string $namespace = 'midgard\\portable\\entities')
61
    {
62 14
        $this->vardir = $vardir . '/';
63 14
        $this->namespace = $namespace;
64
65 14
        $this->is_fresh_namespace = !isset(self::$processed_namespaces[$this->namespace]);
66 14
        if ($this->is_fresh_namespace) {
67 14
            $this->manager = new manager($schemadirs, $this->namespace);
68 14
            self::$processed_namespaces[$this->namespace] = ["manager" => $this->manager];
69
        } else {
70
            // reuse manager instance
71
            $this->manager = self::$processed_namespaces[$this->namespace]["manager"];
72
        }
73 14
    }
74
75 10
    public function is_fresh_namespace() : bool
76
    {
77 10
        return $this->is_fresh_namespace;
78
    }
79
80 10
    public function get_namespace() : string
81
    {
82 10
        return $this->namespace;
83
    }
84
85 11
    public function get_manager() : manager
86
    {
87 11
        return $this->manager;
88
    }
89
90 10
    public function get_vardir() : string
91
    {
92 10
        return rtrim($this->vardir, '/');
93
    }
94
95 17
    private function initialize()
96
    {
97 17
        if ($this->types === null) {
98 13
            $this->types = $this->manager->get_types();
99
        }
100 17
    }
101
102
    /**
103
     * {@inheritDoc}
104
     */
105 2
    public function getAllClassNames() : array
106
    {
107 2
        $this->initialize();
108 2
        return array_keys($this->types);
109
    }
110
111
    /**
112
     * {@inheritDoc}
113
     */
114 13
    public function isTransient($classname) : bool
115
    {
116 13
        $this->initialize();
117 13
        return !isset($this->types[$classname]);
118
    }
119
120
    /**
121
     * {@inheritDoc}
122
     */
123 16
    public function loadMetadataForClass($classname, ClassMetadata $metadata)
124
    {
125 16
        $this->initialize();
126 16
        if (!isset($this->types[$classname])) {
127 4
            throw MappingException::classIsNotAValidEntityOrMappedSuperClass($classname);
128
        }
129
130 15
        $type = $this->types[$classname];
131
132
        // TODO: extends
133
134
        $table = [
135 15
            'name' => '`' . $type->table . '`',
136
            'options' => [
137
                //Doctrine's default on MySQL is InnoDB, and the foreign keys don't play well with Midgard logic
138
                //TODO: Maybe at some point we could try to figure out how to explicitly disable foreign key constraint creation instead
139
                'engine' => 'MyISAM'
140
            ]
141
        ];
142
143 15
        $metadata->setPrimaryTable($table);
144
145 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...
146 15
        $metadata->midgard['parentfield'] = $type->parentfield;
147 15
        $metadata->midgard['upfield'] = $type->upfield;
148 15
        $metadata->midgard['childtypes'] = $this->manager->get_child_classes($type->name);
149 15
        $metadata->midgard['field_aliases'] = $type->field_aliases;
150
151 15
        foreach ($type->get_properties() as $name => $property) {
152
            // doctrine can handle id links only
153 15
            if (   $property->link
154 15
                && $target_class = $this->manager->resolve_targetclass($property)) {
155
                $link_mapping = [
156 10
                    'fieldName' => $property->name,
157 10
                    'targetEntity' => $target_class,
158
                    'joinColumns' => [
159
                        [
160 10
                            'name' => $property->field,
161 10
                            'referencedColumnName' => $property->link['field']
162
                        ]
163
                    ],
164 10
                    'midgard:link_target' => $property->link['field'],
165 10
                    'midgard:link_name' => $property->link['target'],
166
                ];
167
168 10
                if ($link_mapping['fieldName'] == 'id') {
169
                    $link_mapping['id'] = true;
170
                }
171
172 10
                $metadata->mapManyToOne($link_mapping);
173 10
                continue;
174
            }
175
176 15
            $mapping = $this->dbtypemap[$property->dbtype] ?? $this->parse_dbtype($property);
177
178 15
            if ($property->unique) {
179 15
                if ($property->name == 'guid') {
180 15
                    $mapping['unique'] = true;
181
                } else {
182
                    //we can't set this as a real DB constraint because of softdelete and tree hierarchies
183 6
                    $metadata->midgard['unique_fields'][] = $property->name;
184
                }
185
            }
186
187 15
            $mapping['columnName'] = $property->field;
188 15
            $mapping['midgard:midgard_type'] = translator::to_constant($property->type);
189 15
            $mapping['midgard:description'] = $property->description;
190
191
            // its some other link (like guid link)
192 15
            if ($property->noidlink) {
193 5
                $mapping['noidlink'] = $property->noidlink;
194
            }
195
196 15
            if ($property->name == $type->primaryfield) {
197 15
                $mapping['id'] = true;
198 15
                unset($mapping['default']);
199 15
                if ($mapping['type'] == Type::INTEGER) {
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\DBAL\Types\Type::INTEGER has been deprecated: Use {@see Types::INTEGER} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

199
                if ($mapping['type'] == /** @scrutinizer ignore-deprecated */ Type::INTEGER) {

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
200 15
                    $metadata->setIdGeneratorType(CM::GENERATOR_TYPE_AUTO);
201
                } else {
202 3
                    $metadata->setIdGeneratorType(CM::GENERATOR_TYPE_NONE);
203
                }
204
            }
205
206 15
            $mapping['fieldName'] = $name;
207
208 15
            $metadata->mapField($mapping);
209
210 15
            if ($property->index) {
211 14
                if (empty($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...
212 14
                    $metadata->table['indexes'] = [];
213
                }
214 14
                $metadata->table['indexes'][$type->name . '_' . $property->name . '_idx'] = ['columns' => [$property->field]];
215
            }
216
        }
217 15
    }
218
219 5
    private function parse_dbtype(property $property) : array
220
    {
221 5
        if (strpos($property->dbtype, 'varchar') === 0) {
222
            $mapping = [
223 5
                'type' => Type::STRING,
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\DBAL\Types\Type::STRING has been deprecated: Use {@see Types::STRING} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

223
                'type' => /** @scrutinizer ignore-deprecated */ Type::STRING,

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
224
            ];
225
226 5
            if (substr($property->dbtype, -1) == ')') {
227 4
                $mapping['length'] = (int) substr($property->dbtype, 8, -1);
228 4
                return $mapping;
229
            }
230
231 2
            if (substr($property->dbtype, -8) == ') binary') {
232
                // see http://www.doctrine-project.org/jira/browse/DDC-1817
233 2
                $mapping['length'] = (int) substr($property->dbtype, 8, -1);
234 2
                $mapping['comment'] = 'BINARY';
235 2
                return $mapping;
236
            }
237 2
        } elseif (strpos($property->dbtype, 'set') === 0) {
238
            // see http://docs.doctrine-project.org/en/latest/cookbook/mysql-enums.html
239 2
            if (!empty($this->dbtypemap[$property->type])) {
240 2
                $mapping = $this->dbtypemap[$property->type];
241 2
                $mapping['comment'] = $property->dbtype;
242 2
                return $mapping;
243
            }
244
        } elseif (strpos(strtolower($property->dbtype), 'decimal') === 0) {
245
            $matches = [];
246
            preg_match('/DECIMAL\((\d+),(\d+)\)/i', $property->dbtype, $matches);
247
            $mapping = [
248
                'type' => Type::DECIMAL,
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\DBAL\Types\Type::DECIMAL has been deprecated: Use {@see Types::DECIMAL} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

248
                'type' => /** @scrutinizer ignore-deprecated */ Type::DECIMAL,

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
249
                'precision' => $matches[1],
250
                'scale' => $matches[2]
251
            ];
252
            return $mapping;
253
        }
254
255
        throw new \Exception($property->get_parent()->name . ': ' . $property->name . ' ' . $property->dbtype . ' not implemented yet');
256
    }
257
}
258