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 readonly string $vardir; |
||
38 | |||
39 | private ?array $types = null; |
||
40 | |||
41 | private readonly 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 readonly bool $is_fresh_namespace; |
||
55 | |||
56 | 14 | public function __construct(array $schemadirs, string $vardir, string $namespace = 'midgard\\portable\\entities') |
|
57 | { |
||
58 | 14 | $this->vardir = $vardir . '/'; |
|
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
59 | 14 | $this->namespace = $namespace; |
|
0 ignored issues
–
show
|
|||
60 | |||
61 | 14 | $this->is_fresh_namespace = !isset(self::$processed_namespaces[$this->namespace]); |
|
0 ignored issues
–
show
|
|||
62 | 14 | if ($this->is_fresh_namespace) { |
|
63 | 14 | $this->manager = new manager($schemadirs, $this->namespace); |
|
64 | 14 | 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 | 7 | public function is_fresh_namespace() : bool |
|
72 | { |
||
73 | 7 | return $this->is_fresh_namespace; |
|
74 | } |
||
75 | |||
76 | 7 | public function get_namespace() : string |
|
77 | { |
||
78 | 7 | return $this->namespace; |
|
79 | } |
||
80 | |||
81 | 8 | public function get_manager() : manager |
|
82 | { |
||
83 | 8 | return $this->manager; |
|
84 | } |
||
85 | |||
86 | 10 | public function get_vardir() : string |
|
87 | { |
||
88 | 10 | 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 | 3 | public function getAllClassNames() : array |
|
100 | { |
||
101 | 3 | $this->initialize(); |
|
102 | 3 | 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
|
|||
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['links_as_entities'] = $type->links_as_entities; |
|
146 | 15 | $metadata->midgard['field_order'] = array_keys($properties); |
|
147 | |||
148 | 15 | foreach ($properties as $name => $property) { |
|
149 | // doctrine can handle id links only |
||
150 | 15 | if ( $property->link |
|
151 | 15 | && $target_class = $this->manager->resolve_targetclass($property)) { |
|
152 | 11 | $link_mapping = [ |
|
153 | 11 | 'fieldName' => $property->name, |
|
154 | 11 | 'targetEntity' => $target_class, |
|
155 | 11 | 'joinColumns' => [ |
|
156 | 11 | [ |
|
157 | 11 | 'name' => $property->field, |
|
158 | 11 | 'referencedColumnName' => $property->link['field'] |
|
159 | 11 | ] |
|
160 | 11 | ], |
|
161 | 11 | 'midgard:link_target' => $property->link['field'], |
|
162 | 11 | 'midgard:link_name' => $property->link['target'], |
|
163 | 11 | ]; |
|
164 | |||
165 | 11 | if ($link_mapping['fieldName'] == 'id') { |
|
166 | $link_mapping['id'] = true; |
||
167 | } |
||
168 | |||
169 | 11 | $metadata->mapManyToOne($link_mapping); |
|
170 | 11 | continue; |
|
171 | } |
||
172 | |||
173 | 15 | $mapping = $this->dbtypemap[$property->dbtype] ?? $this->parse_dbtype($property); |
|
174 | |||
175 | 15 | if ($property->unique) { |
|
176 | 15 | if ($property->name == 'guid') { |
|
177 | 15 | $mapping['unique'] = true; |
|
178 | } else { |
||
179 | //we can't set this as a real DB constraint because of softdelete and tree hierarchies |
||
180 | 7 | $metadata->midgard['unique_fields'][] = $property->name; |
|
181 | } |
||
182 | } |
||
183 | |||
184 | 15 | $mapping['columnName'] = $property->field; |
|
185 | 15 | $mapping['midgard:midgard_type'] = translator::to_constant($property->type); |
|
186 | 15 | $mapping['midgard:description'] = $property->description; |
|
187 | |||
188 | // its some other link (like guid link) |
||
189 | 15 | if ($property->noidlink) { |
|
190 | 5 | $mapping['noidlink'] = $property->noidlink; |
|
191 | } |
||
192 | |||
193 | 15 | if ($property->name == $type->primaryfield) { |
|
194 | 15 | $mapping['id'] = true; |
|
195 | 15 | unset($mapping['default']); |
|
196 | 15 | if ($mapping['type'] == Types::INTEGER) { |
|
197 | 15 | $metadata->setIdGeneratorType(CM::GENERATOR_TYPE_AUTO); |
|
198 | } else { |
||
199 | 4 | $metadata->setIdGeneratorType(CM::GENERATOR_TYPE_NONE); |
|
200 | } |
||
201 | } |
||
202 | |||
203 | 15 | $mapping['fieldName'] = $name; |
|
204 | |||
205 | 15 | $metadata->mapField($mapping); |
|
206 | |||
207 | 15 | if ($property->index) { |
|
208 | 14 | $metadata->table['indexes'] ??= []; |
|
0 ignored issues
–
show
|
|||
209 | 14 | $metadata->table['indexes'][$type->name . '_' . $property->name . '_idx'] = ['columns' => [$property->field]]; |
|
210 | } |
||
211 | } |
||
212 | } |
||
213 | |||
214 | 6 | private function parse_dbtype(property $property) : array |
|
215 | { |
||
216 | 6 | if (str_starts_with($property->dbtype, 'varchar')) { |
|
217 | 6 | $mapping = [ |
|
218 | 6 | 'type' => Types::STRING, |
|
219 | 6 | ]; |
|
220 | |||
221 | 6 | if (str_ends_with($property->dbtype, ')')) { |
|
222 | 5 | $mapping['length'] = (int) substr($property->dbtype, 8, -1); |
|
223 | 5 | return $mapping; |
|
224 | } |
||
225 | |||
226 | 2 | if (str_ends_with($property->dbtype, ') binary')) { |
|
227 | // see http://www.doctrine-project.org/jira/browse/DDC-1817 |
||
228 | 2 | $mapping['length'] = (int) substr($property->dbtype, 8, -1); |
|
229 | 2 | $mapping['comment'] = 'BINARY'; |
|
230 | 2 | return $mapping; |
|
231 | } |
||
232 | 2 | } elseif (str_starts_with($property->dbtype, 'set')) { |
|
233 | // see http://docs.doctrine-project.org/en/latest/cookbook/mysql-enums.html |
||
234 | 2 | if (!empty($this->dbtypemap[$property->type])) { |
|
235 | 2 | $mapping = $this->dbtypemap[$property->type]; |
|
236 | 2 | $mapping['comment'] = $property->dbtype; |
|
237 | 2 | return $mapping; |
|
238 | } |
||
239 | } elseif (str_starts_with(strtolower($property->dbtype), 'decimal')) { |
||
240 | $matches = []; |
||
241 | preg_match('/DECIMAL\((\d+),(\d+)\)/i', $property->dbtype, $matches); |
||
242 | return [ |
||
243 | 'type' => Types::DECIMAL, |
||
244 | 'precision' => $matches[1], |
||
245 | 'scale' => $matches[2] |
||
246 | ]; |
||
247 | } |
||
248 | |||
249 | throw new \Exception($property->get_parent()->name . ': ' . $property->name . ' ' . $property->dbtype . ' not implemented yet'); |
||
250 | } |
||
251 | } |
||
252 |