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