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

driver   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 238
Duplicated Lines 0 %

Test Coverage

Coverage 90.43%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 117
c 1
b 0
f 0
dl 0
loc 238
ccs 85
cts 94
cp 0.9043
rs 10
wmc 30

10 Methods

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