source: pro-violet-viettel/sourcecode/application/libraries/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php @ 345

Last change on this file since 345 was 345, checked in by quyenla, 11 years ago

collaborator page

File size: 14.7 KB
Line 
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 LGPL. For more information, see
17 * <http://www.doctrine-project.org>.
18 */
19
20namespace Doctrine\ORM\Mapping\Driver;
21
22use Doctrine\Common\Cache\ArrayCache,
23    Doctrine\Common\Annotations\AnnotationReader,
24    Doctrine\DBAL\Schema\AbstractSchemaManager,
25    Doctrine\DBAL\Schema\SchemaException,
26    Doctrine\ORM\Mapping\ClassMetadataInfo,
27    Doctrine\ORM\Mapping\MappingException,
28    Doctrine\Common\Util\Inflector;
29
30/**
31 * The DatabaseDriver reverse engineers the mapping metadata from a database.
32 *
33 * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
34 * @link    www.doctrine-project.org
35 * @since   2.0
36 * @author  Guilherme Blanco <guilhermeblanco@hotmail.com>
37 * @author  Jonathan Wage <jonwage@gmail.com>
38 * @author  Benjamin Eberlei <kontakt@beberlei.de>
39 */
40class DatabaseDriver implements Driver
41{
42    /**
43     * @var AbstractSchemaManager
44     */
45    private $_sm;
46
47    /**
48     * @var array
49     */
50    private $tables = null;
51
52    private $classToTableNames = array();
53
54    /**
55     * @var array
56     */
57    private $manyToManyTables = array();
58
59    /**
60     * @var array
61     */
62    private $classNamesForTables = array();
63
64    /**
65     * @var array
66     */
67    private $fieldNamesForColumns = array();
68
69    /**
70     * The namespace for the generated entities.
71     *
72     * @var string
73     */
74    private $namespace;
75
76    /**
77     * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
78     * docblock annotations.
79     *
80     * @param AnnotationReader $reader The AnnotationReader to use.
81     */
82    public function __construct(AbstractSchemaManager $schemaManager)
83    {
84        $this->_sm = $schemaManager;
85    }
86
87    /**
88     * Set tables manually instead of relying on the reverse engeneering capabilities of SchemaManager.
89     *
90     * @param array $entityTables
91     * @param array $manyToManyTables
92     * @return void
93     */
94    public function setTables($entityTables, $manyToManyTables)
95    {
96        $this->tables = $this->manyToManyTables = $this->classToTableNames = array();
97        foreach ($entityTables AS $table) {
98            $className = $this->getClassNameForTable($table->getName());
99            $this->classToTableNames[$className] = $table->getName();
100            $this->tables[$table->getName()] = $table;
101        }
102        foreach ($manyToManyTables AS $table) {
103            $this->manyToManyTables[$table->getName()] = $table;
104        }
105    }
106
107    private function reverseEngineerMappingFromDatabase()
108    {
109        if ($this->tables !== null) {
110            return;
111        }
112
113        $tables = array();
114
115        foreach ($this->_sm->listTableNames() as $tableName) {
116            $tables[$tableName] = $this->_sm->listTableDetails($tableName);
117        }
118
119        $this->tables = $this->manyToManyTables = $this->classToTableNames = array();
120        foreach ($tables AS $tableName => $table) {
121            /* @var $table Table */
122            if ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) {
123                $foreignKeys = $table->getForeignKeys();
124            } else {
125                $foreignKeys = array();
126            }
127
128            $allForeignKeyColumns = array();
129            foreach ($foreignKeys AS $foreignKey) {
130                $allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns());
131            }
132
133            if ( ! $table->hasPrimaryKey()) {
134                throw new MappingException(
135                    "Table " . $table->getName() . " has no primary key. Doctrine does not ".
136                    "support reverse engineering from tables that don't have a primary key."
137                );
138            }
139
140            $pkColumns = $table->getPrimaryKey()->getColumns();
141            sort($pkColumns);
142            sort($allForeignKeyColumns);
143
144            if ($pkColumns == $allForeignKeyColumns && count($foreignKeys) == 2) {
145                $this->manyToManyTables[$tableName] = $table;
146            } else {
147                // lower-casing is necessary because of Oracle Uppercase Tablenames,
148                // assumption is lower-case + underscore separated.
149                $className = $this->getClassNameForTable($tableName);
150                $this->tables[$tableName] = $table;
151                $this->classToTableNames[$className] = $tableName;
152            }
153        }
154    }
155
156    /**
157     * {@inheritdoc}
158     */
159    public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
160    {
161        $this->reverseEngineerMappingFromDatabase();
162
163        if (!isset($this->classToTableNames[$className])) {
164            throw new \InvalidArgumentException("Unknown class " . $className);
165        }
166
167        $tableName = $this->classToTableNames[$className];
168
169        $metadata->name = $className;
170        $metadata->table['name'] = $tableName;
171
172        $columns = $this->tables[$tableName]->getColumns();
173        $indexes = $this->tables[$tableName]->getIndexes();
174        try {
175            $primaryKeyColumns = $this->tables[$tableName]->getPrimaryKey()->getColumns();
176        } catch(SchemaException $e) {
177            $primaryKeyColumns = array();
178        }
179
180        if ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) {
181            $foreignKeys = $this->tables[$tableName]->getForeignKeys();
182        } else {
183            $foreignKeys = array();
184        }
185
186        $allForeignKeyColumns = array();
187        foreach ($foreignKeys AS $foreignKey) {
188            $allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns());
189        }
190
191        $ids = array();
192        $fieldMappings = array();
193        foreach ($columns as $column) {
194            $fieldMapping = array();
195           
196            if (in_array($column->getName(), $allForeignKeyColumns)) {
197                continue;
198            } else if ($primaryKeyColumns && in_array($column->getName(), $primaryKeyColumns)) {
199                $fieldMapping['id'] = true;
200            }
201           
202            $fieldMapping['fieldName'] = $this->getFieldNameForColumn($tableName, $column->getName(), false);
203            $fieldMapping['columnName'] = $column->getName();
204            $fieldMapping['type'] = strtolower((string) $column->getType());
205
206            if ($column->getType() instanceof \Doctrine\DBAL\Types\StringType) {
207                $fieldMapping['length'] = $column->getLength();
208                $fieldMapping['fixed'] = $column->getFixed();
209            } else if ($column->getType() instanceof \Doctrine\DBAL\Types\IntegerType) {
210                $fieldMapping['unsigned'] = $column->getUnsigned();
211            }
212            $fieldMapping['nullable'] = $column->getNotNull() ? false : true;
213
214            if (isset($fieldMapping['id'])) {
215                $ids[] = $fieldMapping;
216            } else {
217                $fieldMappings[] = $fieldMapping;
218            }
219        }
220
221        if ($ids) {
222            if (count($ids) == 1) {
223                $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO);
224            }
225
226            foreach ($ids as $id) {
227                $metadata->mapField($id);
228            }
229        }
230
231        foreach ($fieldMappings as $fieldMapping) {
232            $metadata->mapField($fieldMapping);
233        }
234
235        foreach ($this->manyToManyTables AS $manyTable) {
236            foreach ($manyTable->getForeignKeys() AS $foreignKey) {
237                // foreign  key maps to the table of the current entity, many to many association probably exists
238                if (strtolower($tableName) == strtolower($foreignKey->getForeignTableName())) {
239                    $myFk = $foreignKey;
240                    $otherFk = null;
241                    foreach ($manyTable->getForeignKeys() AS $foreignKey) {
242                        if ($foreignKey != $myFk) {
243                            $otherFk = $foreignKey;
244                            break;
245                        }
246                    }
247
248                    if (!$otherFk) {
249                        // the definition of this many to many table does not contain
250                        // enough foreign key information to continue reverse engeneering.
251                        continue;
252                    }
253
254                    $localColumn = current($myFk->getColumns());
255                    $associationMapping = array();
256                    $associationMapping['fieldName'] = $this->getFieldNameForColumn($manyTable->getName(), current($otherFk->getColumns()), true);
257                    $associationMapping['targetEntity'] = $this->getClassNameForTable($otherFk->getForeignTableName());
258                    if (current($manyTable->getColumns())->getName() == $localColumn) {
259                        $associationMapping['inversedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true);
260                        $associationMapping['joinTable'] = array(
261                            'name' => strtolower($manyTable->getName()),
262                            'joinColumns' => array(),
263                            'inverseJoinColumns' => array(),
264                        );
265
266                        $fkCols = $myFk->getForeignColumns();
267                        $cols = $myFk->getColumns();
268                        for ($i = 0; $i < count($cols); $i++) {
269                            $associationMapping['joinTable']['joinColumns'][] = array(
270                                'name' => $cols[$i],
271                                'referencedColumnName' => $fkCols[$i],
272                            );
273                        }
274
275                        $fkCols = $otherFk->getForeignColumns();
276                        $cols = $otherFk->getColumns();
277                        for ($i = 0; $i < count($cols); $i++) {
278                            $associationMapping['joinTable']['inverseJoinColumns'][] = array(
279                                'name' => $cols[$i],
280                                'referencedColumnName' => $fkCols[$i],
281                            );
282                        }
283                    } else {
284                        $associationMapping['mappedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true);
285                    }
286                    $metadata->mapManyToMany($associationMapping);
287                    break;
288                }
289            }
290        }
291
292        foreach ($foreignKeys as $foreignKey) {
293            $foreignTable = $foreignKey->getForeignTableName();
294            $cols = $foreignKey->getColumns();
295            $fkCols = $foreignKey->getForeignColumns();
296
297            $localColumn = current($cols);
298            $associationMapping = array();
299            $associationMapping['fieldName'] = $this->getFieldNameForColumn($tableName, $localColumn, true);
300            $associationMapping['targetEntity'] = $this->getClassNameForTable($foreignTable);
301
302            if ($primaryKeyColumns && in_array($localColumn, $primaryKeyColumns)) {
303                $associationMapping['id'] = true;
304            }
305
306            for ($i = 0; $i < count($cols); $i++) {
307                $associationMapping['joinColumns'][] = array(
308                    'name' => $cols[$i],
309                    'referencedColumnName' => $fkCols[$i],
310                );
311            }
312           
313            //Here we need to check if $cols are the same as $primaryKeyColums
314            if (!array_diff($cols,$primaryKeyColumns)) {
315                $metadata->mapOneToOne($associationMapping);
316            } else {
317                $metadata->mapManyToOne($associationMapping);
318            }
319        }
320    }
321
322    /**
323     * {@inheritdoc}
324     */
325    public function isTransient($className)
326    {
327        return true;
328    }
329
330    /**
331     * Return all the class names supported by this driver.
332     *
333     * IMPORTANT: This method must return an array of class not tables names.
334     *
335     * @return array
336     */
337    public function getAllClassNames()
338    {
339        $this->reverseEngineerMappingFromDatabase();
340
341        return array_keys($this->classToTableNames);
342    }
343
344    /**
345     * Set class name for a table.
346     *
347     * @param string $tableName
348     * @param string $className
349     * @return void
350     */
351    public function setClassNameForTable($tableName, $className)
352    {
353        $this->classNamesForTables[$tableName] = $className;
354    }
355
356    /**
357     * Set field name for a column on a specific table.
358     *
359     * @param string $tableName
360     * @param string $columnName
361     * @param string $fieldName
362     * @return void
363     */
364    public function setFieldNameForColumn($tableName, $columnName, $fieldName)
365    {
366        $this->fieldNamesForColumns[$tableName][$columnName] = $fieldName;
367    }
368
369    /**
370     * Return the mapped class name for a table if it exists. Otherwise return "classified" version.
371     *
372     * @param string $tableName
373     * @return string
374     */
375    private function getClassNameForTable($tableName)
376    {
377        if (isset($this->classNamesForTables[$tableName])) {
378            return $this->namespace . $this->classNamesForTables[$tableName];
379        }
380
381        return $this->namespace . Inflector::classify(strtolower($tableName));
382    }
383
384    /**
385     * Return the mapped field name for a column, if it exists. Otherwise return camelized version.
386     *
387     * @param string $tableName
388     * @param string $columnName
389     * @param boolean $fk Whether the column is a foreignkey or not.
390     * @return string
391     */
392    private function getFieldNameForColumn($tableName, $columnName, $fk = false)
393    {
394        if (isset($this->fieldNamesForColumns[$tableName]) && isset($this->fieldNamesForColumns[$tableName][$columnName])) {
395            return $this->fieldNamesForColumns[$tableName][$columnName];
396        }
397
398        $columnName = strtolower($columnName);
399
400        // Replace _id if it is a foreignkey column
401        if ($fk) {
402            $columnName = str_replace('_id', '', $columnName);
403        }
404        return Inflector::camelize($columnName);
405    }
406
407    /**
408     * Set the namespace for the generated entities.
409     *
410     * @param string $namespace
411     * @return void
412     */
413    public function setNamespace($namespace)
414    {
415        $this->namespace = $namespace;
416    }
417}
Note: See TracBrowser for help on using the repository browser.