Passed
Push — master ( 622e79...efafc0 )
by Andreas
03:31
created

driver   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 241
Duplicated Lines 0 %

Test Coverage

Coverage 90%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 117
c 5
b 0
f 0
dl 0
loc 241
ccs 90
cts 100
cp 0.9
rs 9.84
wmc 32

10 Methods

Rating   Name   Duplication   Size   Complexity  
A get_namespace() 0 3 1
A is_fresh_namespace() 0 3 1
A initialize() 0 3 1
A get_manager() 0 3 1
A get_vardir() 0 3 1
A isTransient() 0 6 2
C loadMetadataForClass() 0 94 14
B parse_dbtype() 0 37 7
A __construct() 0 12 2
A getAllClassNames() 0 7 2
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 12
    private function initialize()
96
    {
97 12
        $this->types = $this->manager->get_types();
98 12
    }
99
100
    /**
101
     * {@inheritDoc}
102
     */
103 2
    public function getAllClassNames() : array
104
    {
105 2
        if ($this->types === null) {
106 1
            $this->initialize();
107
        }
108
109 2
        return array_keys($this->types);
110
    }
111
112
    /**
113
     * {@inheritDoc}
114
     */
115 12
    public function isTransient($classname) : bool
116
    {
117 12
        if ($this->types === null) {
118 8
            $this->initialize();
119
        }
120 12
        return !isset($this->types[$classname]);
121
    }
122
123
    /**
124
     * {@inheritDoc}
125
     */
126 15
    public function loadMetadataForClass($classname, ClassMetadata $metadata)
127
    {
128 15
        if ($this->types === null) {
129 3
            $this->initialize();
130
        }
131 15
        if (!isset($this->types[$classname])) {
132 4
            throw MappingException::classIsNotAValidEntityOrMappedSuperClass($classname);
133
        }
134
135 14
        $type = $this->types[$classname];
136
137
        // TODO: extends
138
139
        $table = [
140 14
            'name' => '`' . $type->table . '`',
141
            'options' => [
142
                //Doctrine's default on MySQL is InnoDB, and the foreign keys don't play well with Midgard logic
143
                //TODO: Maybe at some point we could try to figure out how to explicitly disable foreign key constraint creation instead
144
                'engine' => 'MyISAM'
145
            ]
146
        ];
147
148 14
        $metadata->setPrimaryTable($table);
149
150 14
        $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...
151 14
        $metadata->midgard['parentfield'] = $type->parentfield;
152 14
        $metadata->midgard['upfield'] = $type->upfield;
153 14
        $metadata->midgard['childtypes'] = $this->manager->get_child_classes($type->name);
154 14
        $metadata->midgard['field_aliases'] = $type->field_aliases;
155
156 14
        foreach ($type->get_properties() as $name => $property) {
157
            // doctrine can handle id links only
158 14
            if (   $property->link
159 14
                && $target_class = $this->manager->resolve_targetclass($property)) {
160
                $link_mapping = [
161 10
                    'fieldName' => $property->name,
162 10
                    'targetEntity' => $target_class,
163
                    'joinColumns' => [
164
                        [
165 10
                            'name' => $property->field,
166 10
                            'referencedColumnName' => $property->link['field']
167
                        ]
168
                    ],
169 10
                    'midgard:link_target' => $property->link['field'],
170 10
                    'midgard:link_name' => $property->link['target'],
171
                ];
172
173 10
                if ($link_mapping['fieldName'] == 'id') {
174
                    $link_mapping['id'] = true;
175
                }
176
177 10
                $metadata->mapManyToOne($link_mapping);
178 10
                continue;
179
            }
180
181 14
            $mapping = $this->dbtypemap[$property->dbtype] ?? $this->parse_dbtype($property);
182
183 14
            if ($property->unique) {
184 14
                if ($property->name == 'guid') {
185 14
                    $mapping['unique'] = true;
186
                } else {
187
                    //we can't set this as a real DB constraint because of softdelete and tree hierarchies
188 6
                    $metadata->midgard['unique_fields'][] = $property->name;
189
                }
190
            }
191
192 14
            $mapping['columnName'] = $property->field;
193 14
            $mapping['midgard:midgard_type'] = translator::to_constant($property->type);
194 14
            $mapping['midgard:description'] = $property->description;
195
196
            // its some other link (like guid link)
197 14
            if ($property->noidlink) {
198 4
                $mapping['noidlink'] = $property->noidlink;
199
            }
200
201 14
            if ($property->name == $type->primaryfield) {
202 14
                $mapping['id'] = true;
203 14
                unset($mapping['default']);
204 14
                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

204
                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...
205 14
                    $metadata->setIdGeneratorType(CM::GENERATOR_TYPE_AUTO);
206
                } else {
207 3
                    $metadata->setIdGeneratorType(CM::GENERATOR_TYPE_NONE);
208
                }
209
            }
210
211 14
            $mapping['fieldName'] = $name;
212
213 14
            $metadata->mapField($mapping);
214
215 14
            if ($property->index) {
216 13
                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...
217 13
                    $metadata->table['indexes'] = [];
218
                }
219 13
                $metadata->table['indexes'][$type->name . '_' . $property->name . '_idx'] = ['columns' => [$property->field]];
220
            }
221
        }
222 14
    }
223
224 5
    private function parse_dbtype(property $property) : array
225
    {
226 5
        if (strpos($property->dbtype, 'varchar') === 0) {
227
            $mapping = [
228 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

228
                '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...
229
            ];
230
231 5
            if (substr($property->dbtype, -1) == ')') {
232 4
                $mapping['length'] = (int) substr($property->dbtype, 8, -1);
233 4
                return $mapping;
234
            }
235
236 2
            if (substr($property->dbtype, -8) == ') binary') {
237
                // see http://www.doctrine-project.org/jira/browse/DDC-1817
238 2
                $mapping['length'] = (int) substr($property->dbtype, 8, -1);
239 2
                $mapping['comment'] = 'BINARY';
240 2
                return $mapping;
241
            }
242 2
        } elseif (strpos($property->dbtype, 'set') === 0) {
243
            // see http://docs.doctrine-project.org/en/latest/cookbook/mysql-enums.html
244 2
            if (!empty($this->dbtypemap[$property->type])) {
245 2
                $mapping = $this->dbtypemap[$property->type];
246 2
                $mapping['comment'] = $property->dbtype;
247 2
                return $mapping;
248
            }
249
        } elseif (strpos(strtolower($property->dbtype), 'decimal') === 0) {
250
            $matches = [];
251
            preg_match('/DECIMAL\((\d+),(\d+)\)/i', $property->dbtype, $matches);
252
            $mapping = [
253
                '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

253
                '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...
254
                'precision' => $matches[1],
255
                'scale' => $matches[2]
256
            ];
257
            return $mapping;
258
        }
259
260
        throw new \Exception($property->get_parent()->name . ': ' . $property->name . ' ' . $property->dbtype . ' not implemented yet');
261
    }
262
}
263