Passed
Push — master ( b09910...f8fa0e )
by Andreas
15:40
created

driver   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 230
Duplicated Lines 0 %

Test Coverage

Coverage 89.91%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 115
c 5
b 0
f 0
dl 0
loc 230
ccs 98
cts 109
cp 0.8991
rs 10
wmc 28

10 Methods

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