diff options
author | polo <ordipolo@gmx.fr> | 2024-08-13 23:45:21 +0200 |
---|---|---|
committer | polo <ordipolo@gmx.fr> | 2024-08-13 23:45:21 +0200 |
commit | bf6655a534a6775d30cafa67bd801276bda1d98d (patch) | |
tree | c6381e3f6c81c33eab72508f410b165ba05f7e9c /vendor/doctrine/orm/src/Mapping/Driver/DatabaseDriver.php | |
parent | 94d67a4b51f8e62e7d518cce26a526ae1ec48278 (diff) | |
download | AppliGestionPHP-bf6655a534a6775d30cafa67bd801276bda1d98d.zip |
VERSION 0.2 doctrine ORM et entités
Diffstat (limited to 'vendor/doctrine/orm/src/Mapping/Driver/DatabaseDriver.php')
-rw-r--r-- | vendor/doctrine/orm/src/Mapping/Driver/DatabaseDriver.php | 528 |
1 files changed, 528 insertions, 0 deletions
diff --git a/vendor/doctrine/orm/src/Mapping/Driver/DatabaseDriver.php b/vendor/doctrine/orm/src/Mapping/Driver/DatabaseDriver.php new file mode 100644 index 0000000..49e2e93 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Driver/DatabaseDriver.php | |||
@@ -0,0 +1,528 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\ORM\Mapping\Driver; | ||
6 | |||
7 | use Doctrine\DBAL\Schema\AbstractSchemaManager; | ||
8 | use Doctrine\DBAL\Schema\Column; | ||
9 | use Doctrine\DBAL\Schema\SchemaException; | ||
10 | use Doctrine\DBAL\Schema\Table; | ||
11 | use Doctrine\DBAL\Types\Type; | ||
12 | use Doctrine\DBAL\Types\Types; | ||
13 | use Doctrine\Inflector\Inflector; | ||
14 | use Doctrine\Inflector\InflectorFactory; | ||
15 | use Doctrine\ORM\Mapping\ClassMetadata; | ||
16 | use Doctrine\ORM\Mapping\MappingException; | ||
17 | use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata; | ||
18 | use Doctrine\Persistence\Mapping\Driver\MappingDriver; | ||
19 | use InvalidArgumentException; | ||
20 | use TypeError; | ||
21 | |||
22 | use function array_diff; | ||
23 | use function array_keys; | ||
24 | use function array_merge; | ||
25 | use function assert; | ||
26 | use function count; | ||
27 | use function current; | ||
28 | use function get_debug_type; | ||
29 | use function in_array; | ||
30 | use function preg_replace; | ||
31 | use function sort; | ||
32 | use function sprintf; | ||
33 | use function strtolower; | ||
34 | |||
35 | /** | ||
36 | * The DatabaseDriver reverse engineers the mapping metadata from a database. | ||
37 | * | ||
38 | * @link www.doctrine-project.org | ||
39 | */ | ||
40 | class DatabaseDriver implements MappingDriver | ||
41 | { | ||
42 | /** | ||
43 | * Replacement for {@see Types::ARRAY}. | ||
44 | * | ||
45 | * To be removed as soon as support for DBAL 3 is dropped. | ||
46 | */ | ||
47 | private const ARRAY = 'array'; | ||
48 | |||
49 | /** | ||
50 | * Replacement for {@see Types::OBJECT}. | ||
51 | * | ||
52 | * To be removed as soon as support for DBAL 3 is dropped. | ||
53 | */ | ||
54 | private const OBJECT = 'object'; | ||
55 | |||
56 | /** @var array<string,Table>|null */ | ||
57 | private array|null $tables = null; | ||
58 | |||
59 | /** @var array<class-string, string> */ | ||
60 | private array $classToTableNames = []; | ||
61 | |||
62 | /** @psalm-var array<string, Table> */ | ||
63 | private array $manyToManyTables = []; | ||
64 | |||
65 | /** @var mixed[] */ | ||
66 | private array $classNamesForTables = []; | ||
67 | |||
68 | /** @var mixed[] */ | ||
69 | private array $fieldNamesForColumns = []; | ||
70 | |||
71 | /** | ||
72 | * The namespace for the generated entities. | ||
73 | */ | ||
74 | private string|null $namespace = null; | ||
75 | |||
76 | private Inflector $inflector; | ||
77 | |||
78 | public function __construct(private readonly AbstractSchemaManager $sm) | ||
79 | { | ||
80 | $this->inflector = InflectorFactory::create()->build(); | ||
81 | } | ||
82 | |||
83 | /** | ||
84 | * Set the namespace for the generated entities. | ||
85 | */ | ||
86 | public function setNamespace(string $namespace): void | ||
87 | { | ||
88 | $this->namespace = $namespace; | ||
89 | } | ||
90 | |||
91 | public function isTransient(string $className): bool | ||
92 | { | ||
93 | return true; | ||
94 | } | ||
95 | |||
96 | /** | ||
97 | * {@inheritDoc} | ||
98 | */ | ||
99 | public function getAllClassNames(): array | ||
100 | { | ||
101 | $this->reverseEngineerMappingFromDatabase(); | ||
102 | |||
103 | return array_keys($this->classToTableNames); | ||
104 | } | ||
105 | |||
106 | /** | ||
107 | * Sets class name for a table. | ||
108 | */ | ||
109 | public function setClassNameForTable(string $tableName, string $className): void | ||
110 | { | ||
111 | $this->classNamesForTables[$tableName] = $className; | ||
112 | } | ||
113 | |||
114 | /** | ||
115 | * Sets field name for a column on a specific table. | ||
116 | */ | ||
117 | public function setFieldNameForColumn(string $tableName, string $columnName, string $fieldName): void | ||
118 | { | ||
119 | $this->fieldNamesForColumns[$tableName][$columnName] = $fieldName; | ||
120 | } | ||
121 | |||
122 | /** | ||
123 | * Sets tables manually instead of relying on the reverse engineering capabilities of SchemaManager. | ||
124 | * | ||
125 | * @param Table[] $entityTables | ||
126 | * @param Table[] $manyToManyTables | ||
127 | * @psalm-param list<Table> $entityTables | ||
128 | * @psalm-param list<Table> $manyToManyTables | ||
129 | */ | ||
130 | public function setTables(array $entityTables, array $manyToManyTables): void | ||
131 | { | ||
132 | $this->tables = $this->manyToManyTables = $this->classToTableNames = []; | ||
133 | |||
134 | foreach ($entityTables as $table) { | ||
135 | $className = $this->getClassNameForTable($table->getName()); | ||
136 | |||
137 | $this->classToTableNames[$className] = $table->getName(); | ||
138 | $this->tables[$table->getName()] = $table; | ||
139 | } | ||
140 | |||
141 | foreach ($manyToManyTables as $table) { | ||
142 | $this->manyToManyTables[$table->getName()] = $table; | ||
143 | } | ||
144 | } | ||
145 | |||
146 | public function setInflector(Inflector $inflector): void | ||
147 | { | ||
148 | $this->inflector = $inflector; | ||
149 | } | ||
150 | |||
151 | /** | ||
152 | * {@inheritDoc} | ||
153 | * | ||
154 | * @psalm-param class-string<T> $className | ||
155 | * @psalm-param ClassMetadata<T> $metadata | ||
156 | * | ||
157 | * @template T of object | ||
158 | */ | ||
159 | public function loadMetadataForClass(string $className, PersistenceClassMetadata $metadata): void | ||
160 | { | ||
161 | if (! $metadata instanceof ClassMetadata) { | ||
162 | throw new TypeError(sprintf( | ||
163 | 'Argument #2 passed to %s() must be an instance of %s, %s given.', | ||
164 | __METHOD__, | ||
165 | ClassMetadata::class, | ||
166 | get_debug_type($metadata), | ||
167 | )); | ||
168 | } | ||
169 | |||
170 | $this->reverseEngineerMappingFromDatabase(); | ||
171 | |||
172 | if (! isset($this->classToTableNames[$className])) { | ||
173 | throw new InvalidArgumentException('Unknown class ' . $className); | ||
174 | } | ||
175 | |||
176 | $tableName = $this->classToTableNames[$className]; | ||
177 | |||
178 | $metadata->name = $className; | ||
179 | $metadata->table['name'] = $tableName; | ||
180 | |||
181 | $this->buildIndexes($metadata); | ||
182 | $this->buildFieldMappings($metadata); | ||
183 | $this->buildToOneAssociationMappings($metadata); | ||
184 | |||
185 | foreach ($this->manyToManyTables as $manyTable) { | ||
186 | foreach ($manyTable->getForeignKeys() as $foreignKey) { | ||
187 | // foreign key maps to the table of the current entity, many to many association probably exists | ||
188 | if (! (strtolower($tableName) === strtolower($foreignKey->getForeignTableName()))) { | ||
189 | continue; | ||
190 | } | ||
191 | |||
192 | $myFk = $foreignKey; | ||
193 | $otherFk = null; | ||
194 | |||
195 | foreach ($manyTable->getForeignKeys() as $foreignKey) { | ||
196 | if ($foreignKey !== $myFk) { | ||
197 | $otherFk = $foreignKey; | ||
198 | break; | ||
199 | } | ||
200 | } | ||
201 | |||
202 | if (! $otherFk) { | ||
203 | // the definition of this many to many table does not contain | ||
204 | // enough foreign key information to continue reverse engineering. | ||
205 | continue; | ||
206 | } | ||
207 | |||
208 | $localColumn = current($myFk->getLocalColumns()); | ||
209 | |||
210 | $associationMapping = []; | ||
211 | $associationMapping['fieldName'] = $this->getFieldNameForColumn($manyTable->getName(), current($otherFk->getLocalColumns()), true); | ||
212 | $associationMapping['targetEntity'] = $this->getClassNameForTable($otherFk->getForeignTableName()); | ||
213 | |||
214 | if (current($manyTable->getColumns())->getName() === $localColumn) { | ||
215 | $associationMapping['inversedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getLocalColumns()), true); | ||
216 | $associationMapping['joinTable'] = [ | ||
217 | 'name' => strtolower($manyTable->getName()), | ||
218 | 'joinColumns' => [], | ||
219 | 'inverseJoinColumns' => [], | ||
220 | ]; | ||
221 | |||
222 | $fkCols = $myFk->getForeignColumns(); | ||
223 | $cols = $myFk->getLocalColumns(); | ||
224 | |||
225 | for ($i = 0, $colsCount = count($cols); $i < $colsCount; $i++) { | ||
226 | $associationMapping['joinTable']['joinColumns'][] = [ | ||
227 | 'name' => $cols[$i], | ||
228 | 'referencedColumnName' => $fkCols[$i], | ||
229 | ]; | ||
230 | } | ||
231 | |||
232 | $fkCols = $otherFk->getForeignColumns(); | ||
233 | $cols = $otherFk->getLocalColumns(); | ||
234 | |||
235 | for ($i = 0, $colsCount = count($cols); $i < $colsCount; $i++) { | ||
236 | $associationMapping['joinTable']['inverseJoinColumns'][] = [ | ||
237 | 'name' => $cols[$i], | ||
238 | 'referencedColumnName' => $fkCols[$i], | ||
239 | ]; | ||
240 | } | ||
241 | } else { | ||
242 | $associationMapping['mappedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getLocalColumns()), true); | ||
243 | } | ||
244 | |||
245 | $metadata->mapManyToMany($associationMapping); | ||
246 | |||
247 | break; | ||
248 | } | ||
249 | } | ||
250 | } | ||
251 | |||
252 | /** @throws MappingException */ | ||
253 | private function reverseEngineerMappingFromDatabase(): void | ||
254 | { | ||
255 | if ($this->tables !== null) { | ||
256 | return; | ||
257 | } | ||
258 | |||
259 | $this->tables = $this->manyToManyTables = $this->classToTableNames = []; | ||
260 | |||
261 | foreach ($this->sm->listTables() as $table) { | ||
262 | $tableName = $table->getName(); | ||
263 | $foreignKeys = $table->getForeignKeys(); | ||
264 | |||
265 | $allForeignKeyColumns = []; | ||
266 | |||
267 | foreach ($foreignKeys as $foreignKey) { | ||
268 | $allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns()); | ||
269 | } | ||
270 | |||
271 | $primaryKey = $table->getPrimaryKey(); | ||
272 | if ($primaryKey === null) { | ||
273 | throw new MappingException( | ||
274 | 'Table ' . $tableName . ' has no primary key. Doctrine does not ' . | ||
275 | "support reverse engineering from tables that don't have a primary key.", | ||
276 | ); | ||
277 | } | ||
278 | |||
279 | $pkColumns = $primaryKey->getColumns(); | ||
280 | |||
281 | sort($pkColumns); | ||
282 | sort($allForeignKeyColumns); | ||
283 | |||
284 | if ($pkColumns === $allForeignKeyColumns && count($foreignKeys) === 2) { | ||
285 | $this->manyToManyTables[$tableName] = $table; | ||
286 | } else { | ||
287 | // lower-casing is necessary because of Oracle Uppercase Tablenames, | ||
288 | // assumption is lower-case + underscore separated. | ||
289 | $className = $this->getClassNameForTable($tableName); | ||
290 | |||
291 | $this->tables[$tableName] = $table; | ||
292 | $this->classToTableNames[$className] = $tableName; | ||
293 | } | ||
294 | } | ||
295 | } | ||
296 | |||
297 | /** | ||
298 | * Build indexes from a class metadata. | ||
299 | */ | ||
300 | private function buildIndexes(ClassMetadata $metadata): void | ||
301 | { | ||
302 | $tableName = $metadata->table['name']; | ||
303 | $indexes = $this->tables[$tableName]->getIndexes(); | ||
304 | |||
305 | foreach ($indexes as $index) { | ||
306 | if ($index->isPrimary()) { | ||
307 | continue; | ||
308 | } | ||
309 | |||
310 | $indexName = $index->getName(); | ||
311 | $indexColumns = $index->getColumns(); | ||
312 | $constraintType = $index->isUnique() | ||
313 | ? 'uniqueConstraints' | ||
314 | : 'indexes'; | ||
315 | |||
316 | $metadata->table[$constraintType][$indexName]['columns'] = $indexColumns; | ||
317 | } | ||
318 | } | ||
319 | |||
320 | /** | ||
321 | * Build field mapping from class metadata. | ||
322 | */ | ||
323 | private function buildFieldMappings(ClassMetadata $metadata): void | ||
324 | { | ||
325 | $tableName = $metadata->table['name']; | ||
326 | $columns = $this->tables[$tableName]->getColumns(); | ||
327 | $primaryKeys = $this->getTablePrimaryKeys($this->tables[$tableName]); | ||
328 | $foreignKeys = $this->tables[$tableName]->getForeignKeys(); | ||
329 | $allForeignKeys = []; | ||
330 | |||
331 | foreach ($foreignKeys as $foreignKey) { | ||
332 | $allForeignKeys = array_merge($allForeignKeys, $foreignKey->getLocalColumns()); | ||
333 | } | ||
334 | |||
335 | $ids = []; | ||
336 | $fieldMappings = []; | ||
337 | |||
338 | foreach ($columns as $column) { | ||
339 | if (in_array($column->getName(), $allForeignKeys, true)) { | ||
340 | continue; | ||
341 | } | ||
342 | |||
343 | $fieldMapping = $this->buildFieldMapping($tableName, $column); | ||
344 | |||
345 | if ($primaryKeys && in_array($column->getName(), $primaryKeys, true)) { | ||
346 | $fieldMapping['id'] = true; | ||
347 | $ids[] = $fieldMapping; | ||
348 | } | ||
349 | |||
350 | $fieldMappings[] = $fieldMapping; | ||
351 | } | ||
352 | |||
353 | // We need to check for the columns here, because we might have associations as id as well. | ||
354 | if ($ids && count($primaryKeys) === 1) { | ||
355 | $metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_AUTO); | ||
356 | } | ||
357 | |||
358 | foreach ($fieldMappings as $fieldMapping) { | ||
359 | $metadata->mapField($fieldMapping); | ||
360 | } | ||
361 | } | ||
362 | |||
363 | /** | ||
364 | * Build field mapping from a schema column definition | ||
365 | * | ||
366 | * @return mixed[] | ||
367 | * @psalm-return array{ | ||
368 | * fieldName: string, | ||
369 | * columnName: string, | ||
370 | * type: string, | ||
371 | * nullable: bool, | ||
372 | * options: array{ | ||
373 | * unsigned?: bool, | ||
374 | * fixed?: bool, | ||
375 | * comment: string|null, | ||
376 | * default?: mixed | ||
377 | * }, | ||
378 | * precision?: int, | ||
379 | * scale?: int, | ||
380 | * length?: int|null | ||
381 | * } | ||
382 | */ | ||
383 | private function buildFieldMapping(string $tableName, Column $column): array | ||
384 | { | ||
385 | $fieldMapping = [ | ||
386 | 'fieldName' => $this->getFieldNameForColumn($tableName, $column->getName(), false), | ||
387 | 'columnName' => $column->getName(), | ||
388 | 'type' => Type::getTypeRegistry()->lookupName($column->getType()), | ||
389 | 'nullable' => ! $column->getNotnull(), | ||
390 | 'options' => [ | ||
391 | 'comment' => $column->getComment(), | ||
392 | ], | ||
393 | ]; | ||
394 | |||
395 | // Type specific elements | ||
396 | switch ($fieldMapping['type']) { | ||
397 | case self::ARRAY: | ||
398 | case Types::BLOB: | ||
399 | case Types::GUID: | ||
400 | case self::OBJECT: | ||
401 | case Types::SIMPLE_ARRAY: | ||
402 | case Types::STRING: | ||
403 | case Types::TEXT: | ||
404 | $fieldMapping['length'] = $column->getLength(); | ||
405 | $fieldMapping['options']['fixed'] = $column->getFixed(); | ||
406 | break; | ||
407 | |||
408 | case Types::DECIMAL: | ||
409 | case Types::FLOAT: | ||
410 | $fieldMapping['precision'] = $column->getPrecision(); | ||
411 | $fieldMapping['scale'] = $column->getScale(); | ||
412 | break; | ||
413 | |||
414 | case Types::INTEGER: | ||
415 | case Types::BIGINT: | ||
416 | case Types::SMALLINT: | ||
417 | $fieldMapping['options']['unsigned'] = $column->getUnsigned(); | ||
418 | break; | ||
419 | } | ||
420 | |||
421 | // Default | ||
422 | $default = $column->getDefault(); | ||
423 | if ($default !== null) { | ||
424 | $fieldMapping['options']['default'] = $default; | ||
425 | } | ||
426 | |||
427 | return $fieldMapping; | ||
428 | } | ||
429 | |||
430 | /** | ||
431 | * Build to one (one to one, many to one) association mapping from class metadata. | ||
432 | */ | ||
433 | private function buildToOneAssociationMappings(ClassMetadata $metadata): void | ||
434 | { | ||
435 | assert($this->tables !== null); | ||
436 | |||
437 | $tableName = $metadata->table['name']; | ||
438 | $primaryKeys = $this->getTablePrimaryKeys($this->tables[$tableName]); | ||
439 | $foreignKeys = $this->tables[$tableName]->getForeignKeys(); | ||
440 | |||
441 | foreach ($foreignKeys as $foreignKey) { | ||
442 | $foreignTableName = $foreignKey->getForeignTableName(); | ||
443 | $fkColumns = $foreignKey->getLocalColumns(); | ||
444 | $fkForeignColumns = $foreignKey->getForeignColumns(); | ||
445 | $localColumn = current($fkColumns); | ||
446 | $associationMapping = [ | ||
447 | 'fieldName' => $this->getFieldNameForColumn($tableName, $localColumn, true), | ||
448 | 'targetEntity' => $this->getClassNameForTable($foreignTableName), | ||
449 | ]; | ||
450 | |||
451 | if (isset($metadata->fieldMappings[$associationMapping['fieldName']])) { | ||
452 | $associationMapping['fieldName'] .= '2'; // "foo" => "foo2" | ||
453 | } | ||
454 | |||
455 | if ($primaryKeys && in_array($localColumn, $primaryKeys, true)) { | ||
456 | $associationMapping['id'] = true; | ||
457 | } | ||
458 | |||
459 | for ($i = 0, $fkColumnsCount = count($fkColumns); $i < $fkColumnsCount; $i++) { | ||
460 | $associationMapping['joinColumns'][] = [ | ||
461 | 'name' => $fkColumns[$i], | ||
462 | 'referencedColumnName' => $fkForeignColumns[$i], | ||
463 | ]; | ||
464 | } | ||
465 | |||
466 | // Here we need to check if $fkColumns are the same as $primaryKeys | ||
467 | if (! array_diff($fkColumns, $primaryKeys)) { | ||
468 | $metadata->mapOneToOne($associationMapping); | ||
469 | } else { | ||
470 | $metadata->mapManyToOne($associationMapping); | ||
471 | } | ||
472 | } | ||
473 | } | ||
474 | |||
475 | /** | ||
476 | * Retrieve schema table definition primary keys. | ||
477 | * | ||
478 | * @return string[] | ||
479 | */ | ||
480 | private function getTablePrimaryKeys(Table $table): array | ||
481 | { | ||
482 | try { | ||
483 | return $table->getPrimaryKey()->getColumns(); | ||
484 | } catch (SchemaException) { | ||
485 | // Do nothing | ||
486 | } | ||
487 | |||
488 | return []; | ||
489 | } | ||
490 | |||
491 | /** | ||
492 | * Returns the mapped class name for a table if it exists. Otherwise return "classified" version. | ||
493 | * | ||
494 | * @psalm-return class-string | ||
495 | */ | ||
496 | private function getClassNameForTable(string $tableName): string | ||
497 | { | ||
498 | if (isset($this->classNamesForTables[$tableName])) { | ||
499 | return $this->namespace . $this->classNamesForTables[$tableName]; | ||
500 | } | ||
501 | |||
502 | return $this->namespace . $this->inflector->classify(strtolower($tableName)); | ||
503 | } | ||
504 | |||
505 | /** | ||
506 | * Return the mapped field name for a column, if it exists. Otherwise return camelized version. | ||
507 | * | ||
508 | * @param bool $fk Whether the column is a foreignkey or not. | ||
509 | */ | ||
510 | private function getFieldNameForColumn( | ||
511 | string $tableName, | ||
512 | string $columnName, | ||
513 | bool $fk = false, | ||
514 | ): string { | ||
515 | if (isset($this->fieldNamesForColumns[$tableName], $this->fieldNamesForColumns[$tableName][$columnName])) { | ||
516 | return $this->fieldNamesForColumns[$tableName][$columnName]; | ||
517 | } | ||
518 | |||
519 | $columnName = strtolower($columnName); | ||
520 | |||
521 | // Replace _id if it is a foreignkey column | ||
522 | if ($fk) { | ||
523 | $columnName = preg_replace('/_id$/', '', $columnName); | ||
524 | } | ||
525 | |||
526 | return $this->inflector->camelize($columnName); | ||
527 | } | ||
528 | } | ||