Failed Conditions
Pull Request — 2.8.x (#7946)
by
unknown
09:22 queued 12s
created

DatabaseDriver::getTablePrimaryKeys()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2.5

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 9
ccs 2
cts 4
cp 0.5
crap 2.5
rs 10
c 0
b 0
f 0
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM\Mapping\Driver;
21
22
use Doctrine\Common\Inflector\Inflector;
23
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
24
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
25
use Doctrine\DBAL\Schema\AbstractSchemaManager;
26
use Doctrine\DBAL\Schema\SchemaException;
27
use Doctrine\DBAL\Schema\Table;
28
use Doctrine\DBAL\Schema\Column;
29
use Doctrine\DBAL\Types\Type;
30
use Doctrine\ORM\Mapping\ClassMetadataInfo;
31
use Doctrine\ORM\Mapping\MappingException;
32
use function preg_replace;
33
34
/**
35
 * The DatabaseDriver reverse engineers the mapping metadata from a database.
36
 *
37
 * @link    www.doctrine-project.org
38
 * @since   2.0
39
 * @author  Guilherme Blanco <[email protected]>
40
 * @author  Jonathan Wage <[email protected]>
41
 * @author  Benjamin Eberlei <[email protected]>
42
 */
43
class DatabaseDriver implements MappingDriver
0 ignored issues
show
Deprecated Code introduced by
The interface Doctrine\Common\Persiste...ng\Driver\MappingDriver has been deprecated: 1.3 Use Doctrine\Persistence\Mapping\Driver\MappingDriver ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

43
class DatabaseDriver implements /** @scrutinizer ignore-deprecated */ MappingDriver

This interface has been deprecated. The supplier of the interface has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the interface will be removed and what other interface to use instead.

Loading history...
44
{
45
    /**
46
     * @var AbstractSchemaManager
47
     */
48
    private $_sm;
49
50
    /**
51
     * @var array|null
52
     */
53
    private $tables = null;
54
55
    /**
56
     * @var array
57
     */
58
    private $classToTableNames = [];
59
60
    /**
61
     * @var array
62
     */
63
    private $manyToManyTables = [];
64
65
    /**
66
     * @var array
67
     */
68
    private $classNamesForTables = [];
69
70
    /**
71
     * @var array
72
     */
73
    private $fieldNamesForColumns = [];
74
75
    /**
76
     * The namespace for the generated entities.
77
     *
78
     * @var string|null
79
     */
80
    private $namespace;
81
82
    /**
83
     * @param AbstractSchemaManager $schemaManager
84
     */
85 2
    public function __construct(AbstractSchemaManager $schemaManager)
86
    {
87 2
        $this->_sm = $schemaManager;
88 2
    }
89
90
    /**
91
     * Set the namespace for the generated entities.
92
     *
93
     * @param string $namespace
94
     *
95
     * @return void
96
     */
97
    public function setNamespace($namespace)
98
    {
99
        $this->namespace = $namespace;
100
    }
101
102
    /**
103
     * {@inheritDoc}
104
     */
105
    public function isTransient($className)
106
    {
107
        return true;
108
    }
109
110
    /**
111
     * {@inheritDoc}
112
     */
113 2
    public function getAllClassNames()
114
    {
115 2
        $this->reverseEngineerMappingFromDatabase();
116
117 2
        return array_keys($this->classToTableNames);
118
    }
119
120
    /**
121
     * Sets class name for a table.
122
     *
123
     * @param string $tableName
124
     * @param string $className
125
     *
126
     * @return void
127
     */
128
    public function setClassNameForTable($tableName, $className)
129
    {
130
        $this->classNamesForTables[$tableName] = $className;
131
    }
132
133
    /**
134
     * Sets field name for a column on a specific table.
135
     *
136
     * @param string $tableName
137
     * @param string $columnName
138
     * @param string $fieldName
139
     *
140
     * @return void
141
     */
142
    public function setFieldNameForColumn($tableName, $columnName, $fieldName)
143
    {
144
        $this->fieldNamesForColumns[$tableName][$columnName] = $fieldName;
145
    }
146
147
    /**
148
     * Sets tables manually instead of relying on the reverse engineering capabilities of SchemaManager.
149
     *
150
     * @param array $entityTables
151
     * @param array $manyToManyTables
152
     *
153
     * @return void
154
     */
155 2
    public function setTables($entityTables, $manyToManyTables)
156
    {
157 2
        $this->tables = $this->manyToManyTables = $this->classToTableNames = [];
158
159 2
        foreach ($entityTables as $table) {
160 2
            $className = $this->getClassNameForTable($table->getName());
161
162 2
            $this->classToTableNames[$className] = $table->getName();
163 2
            $this->tables[$table->getName()] = $table;
164
        }
165
166 2
        foreach ($manyToManyTables as $table) {
167 1
            $this->manyToManyTables[$table->getName()] = $table;
168
        }
169 2
    }
170
171
    /**
172
     * {@inheritDoc}
173
     */
174 2
    public function loadMetadataForClass($className, ClassMetadata $metadata)
175
    {
176 2
        $this->reverseEngineerMappingFromDatabase();
177
178 2
        if ( ! isset($this->classToTableNames[$className])) {
179
            throw new \InvalidArgumentException("Unknown class " . $className);
180
        }
181
182 2
        $tableName = $this->classToTableNames[$className];
183
184 2
        $metadata->name = $className;
0 ignored issues
show
Bug introduced by
Accessing name on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
185 2
        $metadata->table['name'] = $tableName;
0 ignored issues
show
Bug introduced by
Accessing table on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
186
187 2
        $this->buildIndexes($metadata);
188 2
        $this->buildFieldMappings($metadata);
189 2
        $this->buildToOneAssociationMappings($metadata);
190
191 2
        foreach ($this->manyToManyTables as $manyTable) {
192 1
            foreach ($manyTable->getForeignKeys() as $foreignKey) {
193
                // foreign key maps to the table of the current entity, many to many association probably exists
194 1
                if ( ! (strtolower($tableName) === strtolower($foreignKey->getForeignTableName()))) {
195 1
                    continue;
196
                }
197
198 1
                $myFk = $foreignKey;
199 1
                $otherFk = null;
200
201 1
                foreach ($manyTable->getForeignKeys() as $foreignKey) {
0 ignored issues
show
Comprehensibility Bug introduced by
$foreignKey is overwriting a variable from outer foreach loop.
Loading history...
202 1
                    if ($foreignKey != $myFk) {
203
                        $otherFk = $foreignKey;
204
                        break;
205
                    }
206
                }
207
208 1
                if ( ! $otherFk) {
209
                    // the definition of this many to many table does not contain
210
                    // enough foreign key information to continue reverse engineering.
211 1
                    continue;
212
                }
213
214
                $localColumn = current($myFk->getColumns());
215
216
                $associationMapping = [];
217
                $associationMapping['fieldName'] = $this->getFieldNameForColumn($manyTable->getName(), current($otherFk->getColumns()), true);
218
                $associationMapping['targetEntity'] = $this->getClassNameForTable($otherFk->getForeignTableName());
219
220
                if (current($manyTable->getColumns())->getName() == $localColumn) {
221
                    $associationMapping['inversedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true);
222
                    $associationMapping['joinTable'] = [
223
                        'name' => strtolower($manyTable->getName()),
224
                        'joinColumns' => [],
225
                        'inverseJoinColumns' => [],
226
                    ];
227
228
                    $fkCols = $myFk->getForeignColumns();
229
                    $cols = $myFk->getColumns();
230
231
                    for ($i = 0, $colsCount = count($cols); $i < $colsCount; $i++) {
232
                        $associationMapping['joinTable']['joinColumns'][] = [
233
                            'name' => $cols[$i],
234
                            'referencedColumnName' => $fkCols[$i],
235
                        ];
236
                    }
237
238
                    $fkCols = $otherFk->getForeignColumns();
239
                    $cols = $otherFk->getColumns();
240
241
                    for ($i = 0, $colsCount = count($cols); $i < $colsCount; $i++) {
242
                        $associationMapping['joinTable']['inverseJoinColumns'][] = [
243
                            'name' => $cols[$i],
244
                            'referencedColumnName' => $fkCols[$i],
245
                        ];
246
                    }
247
                } else {
248
                    $associationMapping['mappedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true);
249
                }
250
251
                $metadata->mapManyToMany($associationMapping);
252
253
                break;
254
            }
255
        }
256 2
    }
257
258
    /**
259
     * @return void
260
     *
261
     * @throws \Doctrine\ORM\Mapping\MappingException
262
     */
263 2
    private function reverseEngineerMappingFromDatabase()
264
    {
265 2
        if ($this->tables !== null) {
266 2
            return;
267
        }
268
269
        $tables = [];
270
271
        foreach ($this->_sm->listTableNames() as $tableName) {
272
            $tables[$tableName] = $this->_sm->listTableDetails($tableName);
273
        }
274
275
        $this->tables = $this->manyToManyTables = $this->classToTableNames = [];
276
277
        foreach ($tables as $tableName => $table) {
278
            $foreignKeys = ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints())
279
                ? $table->getForeignKeys()
280
                : [];
281
282
            $allForeignKeyColumns = [];
283
284
            foreach ($foreignKeys as $foreignKey) {
285
                $allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns());
286
            }
287
288
            if ( ! $table->hasPrimaryKey()) {
289
                throw new MappingException(
290
                    "Table " . $table->getName() . " has no primary key. Doctrine does not ".
291
                    "support reverse engineering from tables that don't have a primary key."
292
                );
293
            }
294
295
            $pkColumns = $table->getPrimaryKey()->getColumns();
296
297
            sort($pkColumns);
298
            sort($allForeignKeyColumns);
299
300
            if ($pkColumns == $allForeignKeyColumns && count($foreignKeys) == 2) {
301
                $this->manyToManyTables[$tableName] = $table;
302
            } else {
303
                // lower-casing is necessary because of Oracle Uppercase Tablenames,
304
                // assumption is lower-case + underscore separated.
305
                $className = $this->getClassNameForTable($tableName);
306
307
                $this->tables[$tableName] = $table;
308
                $this->classToTableNames[$className] = $tableName;
309
            }
310
        }
311
    }
312
313
    /**
314
     * Build indexes from a class metadata.
315
     *
316
     * @param \Doctrine\ORM\Mapping\ClassMetadataInfo $metadata
317
     */
318 2
    private function buildIndexes(ClassMetadataInfo $metadata)
319
    {
320 2
        $tableName = $metadata->table['name'];
321 2
        $indexes   = $this->tables[$tableName]->getIndexes();
322
323 2
        foreach ($indexes as $index) {
324 2
            if ($index->isPrimary()) {
325 2
                continue;
326
            }
327
328 1
            $indexName      = $index->getName();
329 1
            $indexColumns   = $index->getColumns();
330 1
            $constraintType = $index->isUnique()
331
                ? 'uniqueConstraints'
332 1
                : 'indexes';
333
334 1
            $metadata->table[$constraintType][$indexName]['columns'] = $indexColumns;
335
        }
336 2
    }
337
338
    /**
339
     * Build field mapping from class metadata.
340
     *
341
     * @param \Doctrine\ORM\Mapping\ClassMetadataInfo $metadata
342
     */
343 2
    private function buildFieldMappings(ClassMetadataInfo $metadata)
344
    {
345 2
        $tableName      = $metadata->table['name'];
346 2
        $columns        = $this->tables[$tableName]->getColumns();
347 2
        $primaryKeys    = $this->getTablePrimaryKeys($this->tables[$tableName]);
348 2
        $foreignKeys    = $this->getTableForeignKeys($this->tables[$tableName]);
349 2
        $allForeignKeys = [];
350
351 2
        foreach ($foreignKeys as $foreignKey) {
352
            $allForeignKeys = array_merge($allForeignKeys, $foreignKey->getLocalColumns());
353
        }
354
355 2
        $ids           = [];
356 2
        $fieldMappings = [];
357
358 2
        foreach ($columns as $column) {
359 2
            if (in_array($column->getName(), $allForeignKeys)) {
360
                continue;
361
            }
362
363 2
            $fieldMapping = $this->buildFieldMapping($tableName, $column);
364
365 2
            if ($primaryKeys && in_array($column->getName(), $primaryKeys)) {
366 2
                $fieldMapping['id'] = true;
367 2
                $ids[] = $fieldMapping;
368
            }
369
370 2
            $fieldMappings[] = $fieldMapping;
371
        }
372
373
        // We need to check for the columns here, because we might have associations as id as well.
374 2
        if ($ids && count($primaryKeys) == 1) {
375 2
            $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO);
376
        }
377
378 2
        foreach ($fieldMappings as $fieldMapping) {
379 2
            $metadata->mapField($fieldMapping);
380
        }
381 2
    }
382
383
    /**
384
     * Build field mapping from a schema column definition
385
     *
386
     * @param string                       $tableName
387
     * @param \Doctrine\DBAL\Schema\Column $column
388
     *
389
     * @return array
390
     */
391 2
    private function buildFieldMapping($tableName, Column $column)
392
    {
393
        $fieldMapping = [
394 2
            'fieldName'  => $this->getFieldNameForColumn($tableName, $column->getName(), false),
395 2
            'columnName' => $column->getName(),
396 2
            'type'       => $column->getType()->getName(),
397 2
            'nullable'   => ( ! $column->getNotnull()),
398
        ];
399
400
        // Type specific elements
401 2
        switch ($fieldMapping['type']) {
402
            case Type::TARRAY:
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\DBAL\Types\Type::TARRAY has been deprecated: Use {@see DefaultTypes::ARRAY} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

402
            case /** @scrutinizer ignore-deprecated */ Type::TARRAY:

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...
403
            case Type::BLOB:
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\DBAL\Types\Type::BLOB has been deprecated: Use {@see DefaultTypes::BLOB} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

403
            case /** @scrutinizer ignore-deprecated */ Type::BLOB:

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...
404
            case Type::GUID:
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\DBAL\Types\Type::GUID has been deprecated: Use {@see DefaultTypes::GUID} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

404
            case /** @scrutinizer ignore-deprecated */ Type::GUID:

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...
405
            case Type::JSON_ARRAY:
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\DBAL\Types\Type::JSON_ARRAY has been deprecated: Use {@see DefaultTypes::JSON_ARRAY} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

405
            case /** @scrutinizer ignore-deprecated */ Type::JSON_ARRAY:

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...
406
            case Type::OBJECT:
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\DBAL\Types\Type::OBJECT has been deprecated: Use {@see DefaultTypes::OBJECT} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

406
            case /** @scrutinizer ignore-deprecated */ Type::OBJECT:

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...
407
            case Type::SIMPLE_ARRAY:
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\DBAL\Types\Type::SIMPLE_ARRAY has been deprecated: Use {@see DefaultTypes::SIMPLE_ARRAY} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

407
            case /** @scrutinizer ignore-deprecated */ Type::SIMPLE_ARRAY:

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...
408
            case Type::STRING:
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\DBAL\Types\Type::STRING has been deprecated: Use {@see DefaultTypes::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

408
            case /** @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...
409
            case Type::TEXT:
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\DBAL\Types\Type::TEXT has been deprecated: Use {@see DefaultTypes::TEXT} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

409
            case /** @scrutinizer ignore-deprecated */ Type::TEXT:

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...
410 1
                $fieldMapping['length'] = $column->getLength();
411 1
                $fieldMapping['options']['fixed']  = $column->getFixed();
412 1
                break;
413
414
            case Type::DECIMAL:
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\DBAL\Types\Type::DECIMAL has been deprecated: Use {@see DefaultTypes::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

414
            case /** @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...
415
            case Type::FLOAT:
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\DBAL\Types\Type::FLOAT has been deprecated: Use {@see DefaultTypes::FLOAT} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

415
            case /** @scrutinizer ignore-deprecated */ Type::FLOAT:

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...
416
                $fieldMapping['precision'] = $column->getPrecision();
417
                $fieldMapping['scale']     = $column->getScale();
418
                break;
419
420
            case Type::INTEGER:
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\DBAL\Types\Type::INTEGER has been deprecated: Use {@see DefaultTypes::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

420
            case /** @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...
421
            case Type::BIGINT:
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\DBAL\Types\Type::BIGINT has been deprecated: Use {@see DefaultTypes::BIGINT} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

421
            case /** @scrutinizer ignore-deprecated */ Type::BIGINT:

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...
422
            case Type::SMALLINT:
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\DBAL\Types\Type::SMALLINT has been deprecated: Use {@see DefaultTypes::SMALLINT} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

422
            case /** @scrutinizer ignore-deprecated */ Type::SMALLINT:

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...
423 2
                $fieldMapping['options']['unsigned'] = $column->getUnsigned();
424 2
                break;
425
        }
426
427
        // Comment
428 2
        if (($comment = $column->getComment()) !== null) {
429
            $fieldMapping['options']['comment'] = $comment;
430
        }
431
432
        // Default
433 2
        if (($default = $column->getDefault()) !== null) {
434
            $fieldMapping['options']['default'] = $default;
435
        }
436
437 2
        return $fieldMapping;
438
    }
439
440
    /**
441
     * Build to one (one to one, many to one) association mapping from class metadata.
442
     *
443
     * @param \Doctrine\ORM\Mapping\ClassMetadataInfo $metadata
444
     */
445 2
    private function buildToOneAssociationMappings(ClassMetadataInfo $metadata)
446
    {
447 2
        $tableName   = $metadata->table['name'];
448 2
        $primaryKeys = $this->getTablePrimaryKeys($this->tables[$tableName]);
449 2
        $foreignKeys = $this->getTableForeignKeys($this->tables[$tableName]);
450
451 2
        foreach ($foreignKeys as $foreignKey) {
452
            $foreignTableName   = $foreignKey->getForeignTableName();
453
            $fkColumns          = $foreignKey->getColumns();
454
            $fkForeignColumns   = $foreignKey->getForeignColumns();
455
            $localColumn        = current($fkColumns);
456
            $associationMapping = [
457
                'fieldName'    => $this->getFieldNameForColumn($tableName, $localColumn, true),
458
                'targetEntity' => $this->getClassNameForTable($foreignTableName),
459
            ];
460
461
            if (isset($metadata->fieldMappings[$associationMapping['fieldName']])) {
462
                $associationMapping['fieldName'] .= '2'; // "foo" => "foo2"
463
            }
464
465
            if ($primaryKeys && in_array($localColumn, $primaryKeys)) {
466
                $associationMapping['id'] = true;
467
            }
468
469
            for ($i = 0, $fkColumnsCount = count($fkColumns); $i < $fkColumnsCount; $i++) {
470
                $associationMapping['joinColumns'][] = [
471
                    'name'                 => $fkColumns[$i],
472
                    'referencedColumnName' => $fkForeignColumns[$i],
473
                ];
474
            }
475
476
            // Here we need to check if $fkColumns are the same as $primaryKeys
477
            if ( ! array_diff($fkColumns, $primaryKeys)) {
478
                $metadata->mapOneToOne($associationMapping);
479
            } else {
480
                $metadata->mapManyToOne($associationMapping);
481
            }
482
        }
483 2
    }
484
485
    /**
486
     * Retrieve schema table definition foreign keys.
487
     *
488
     * @param \Doctrine\DBAL\Schema\Table $table
489
     *
490
     * @return array
491
     */
492 2
    private function getTableForeignKeys(Table $table)
493
    {
494 2
        return ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints())
495
            ? $table->getForeignKeys()
496 2
            : [];
497
    }
498
499
    /**
500
     * Retrieve schema table definition primary keys.
501
     *
502
     * @param \Doctrine\DBAL\Schema\Table $table
503
     *
504
     * @return array
505
     */
506 2
    private function getTablePrimaryKeys(Table $table)
507
    {
508
        try {
509 2
            return $table->getPrimaryKey()->getColumns();
510
        } catch (SchemaException $e) {
511
            // Do nothing
512
        }
513
514
        return [];
515
    }
516
517
    /**
518
     * Returns the mapped class name for a table if it exists. Otherwise return "classified" version.
519
     *
520
     * @param string $tableName
521
     *
522
     * @return string
523
     */
524 2
    private function getClassNameForTable($tableName)
525
    {
526 2
        if (isset($this->classNamesForTables[$tableName])) {
527
            return $this->namespace . $this->classNamesForTables[$tableName];
528
        }
529
530 2
        return $this->namespace . Inflector::classify(strtolower($tableName));
531
    }
532
533
    /**
534
     * Return the mapped field name for a column, if it exists. Otherwise return camelized version.
535
     *
536
     * @param string  $tableName
537
     * @param string  $columnName
538
     * @param boolean $fk Whether the column is a foreignkey or not.
539
     *
540
     * @return string
541
     */
542 2
    private function getFieldNameForColumn($tableName, $columnName, $fk = false)
543
    {
544 2
        if (isset($this->fieldNamesForColumns[$tableName]) && isset($this->fieldNamesForColumns[$tableName][$columnName])) {
545
            return $this->fieldNamesForColumns[$tableName][$columnName];
546
        }
547
548 2
        $columnName = strtolower($columnName);
549
550
        // Replace _id if it is a foreignkey column
551 2
        if ($fk) {
552
            $columnName = preg_replace('/_id$/', '', $columnName);
553
        }
554
555 2
        return Inflector::camelize($columnName);
556
    }
557
}
558