Development

/branches/1.2/lib/plugins/sfPropelPlugin/lib/addon/sfPropelDatabaseSchema.class.php

You must first sign up to be able to contribute.

root/branches/1.2/lib/plugins/sfPropelPlugin/lib/addon/sfPropelDatabaseSchema.class.php

Revision 15765, 34.2 kB (checked in by fabien, 5 years ago)

[1.0, 1.1, 1.2, 1.3] fixed propel:schema-to-yml composite foreign-Keys wrongly converted (closes #5483)

Line 
1 <?php
2
3 /*
4 * This file is part of the symfony package.
5 * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
6 * (c) Francois Zaninotto <francois.zaninotto@symfony-project.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 /**
13  * Manages propel database schemas as YAML and XML.
14  *
15  * @package    symfony
16  * @subpackage propel
17  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
18  * @author     Fran├žois Zaninotto <francois.zaninotto@symfony-project.com>
19  * @version    SVN: $Id$
20  */
21 class sfPropelDatabaseSchema
22 {
23   protected $connection_name = '';
24   protected $database        = array();
25
26   /**
27    * Dumps schema as array
28    *
29    * @return array
30    */
31   public function asArray()
32   {
33     return array($this->connection_name => $this->database);
34   }
35
36   /**
37    * Load schema from array
38    *
39    * @param array $schema_array
40    */
41   public function loadArray($schema_array)
42   {
43     if (is_array($schema_array) && !empty($schema_array))
44     {
45       $database = array();
46       $connection_name = '';
47
48       if (isset($schema_array['classes']))
49       {
50         // New schema syntax
51         $schema_array = $this->convertNewToOldYaml($schema_array);
52       }
53
54       if (count($schema_array) > 1)
55       {
56         throw new sfException('A schema.yml must only contain 1 database entry.');
57       }
58
59       $tmp = array_keys($schema_array);
60       $connection_name = array_shift($tmp);
61
62       if ($connection_name)
63       {
64         $database = $schema_array[$connection_name];
65       }
66
67       $this->connection_name = $connection_name;
68       $this->database = $database;
69
70       $this->fixYAMLDatabase();
71       $this->fixYAMLI18n();
72       $this->fixYAMLColumns();
73     }
74   }
75
76   /**
77    * Load schema from YAML file
78    *
79    * @param string $file
80    */
81   public function loadYAML($file)
82   {
83     $schema_array = sfYaml::load($file);
84
85     if (!is_array($schema_array))
86     {
87       return; // No defined schema here, skipping
88     }
89
90     if (!isset($schema_array['classes']))
91     {
92       // Old schema syntax: we convert it
93       $schema_array = $this->convertOldToNewYaml($schema_array);
94     }
95
96     $this->loadArray($schema_array);
97   }
98
99   /**
100    * Converts old yaml format schema to new yaml schema
101    *
102    * @param array $schema
103    *
104    * @return array
105    */
106   public function convertOldToNewYaml($schema)
107   {
108     if (is_array($schema) && !empty($schema))
109     {
110       $new_schema = array();
111
112       $tmp = array_keys($schema);
113       $connection_name = array_shift($tmp);
114
115       if(!empty($connection_name) && isset($schema[$connection_name]))
116       {
117         $new_schema['connection'] = $connection_name;
118
119         $classes = array();
120         foreach($schema[$connection_name] as $table => $table_params)
121         {
122           if ($table == '_attributes')
123           {
124             // Database attributes
125             $new_schema = array_merge($new_schema, $table_params);
126           }
127           else
128           {
129             // Table
130             $phpName = sfInflector::camelize($table);
131             if (isset($table_params['_attributes']))
132             {
133               $table_attributes = $table_params['_attributes'];
134               unset($table_params['_attributes']);
135               if (isset($table_attributes['phpName']))
136               {
137                 $phpName = $table_attributes['phpName'];
138                 unset($table_attributes['phpName']);
139               }
140             }
141             else
142             {
143               $table_attributes = array();
144             }
145             $classes[$phpName] = $table_attributes;
146             $classes[$phpName]['tableName'] = $table;
147             $classes[$phpName]['columns'] = array();
148             foreach($table_params as $column => $column_params)
149             {
150               switch($column)
151               {
152                 case '_behaviors':
153                   $classes[$phpName]['behaviors'] = $column_params;
154                   break;
155                 case '_inheritance':
156                   $classes[$phpName]['inheritance'] = $column_params;
157                   break;
158                 case '_nestedSet':
159                   $classes[$phpName]['nestedSet'] = $column_params;
160                   break;
161                 case '_foreignKeys':
162                   $classes[$phpName]['foreignKeys'] = $column_params;
163                   break;
164                 case '_indexes':
165                   $classes[$phpName]['indexes'] = $column_params;
166                   break;
167                 case '_uniques':
168                   $classes[$phpName]['uniques'] = $column_params;
169                   break;
170                 default:
171                   $classes[$phpName]['columns'][$column] = $column_params;
172               }
173             }
174           }
175         }
176
177         $new_schema['classes'] = $classes;
178
179       }
180
181       return $new_schema;
182     }
183   }
184
185   /**
186    * Converts new yaml schema format to old yaml schema
187    *
188    * @param array $schema
189    *
190    * @return array
191    */
192   public function convertNewToOldYaml($schema)
193   {
194     if (isset($schema['connection']))
195     {
196       $connection_name = $schema['connection'];
197       unset($schema['connection']);
198     }
199     else
200     {
201       $connection_name = 'propel';
202     }
203
204     $database = array();
205
206     // Tables
207     if (isset($schema['classes']))
208     {
209       $tables = array();
210       foreach ($schema['classes'] as $className => $classParams)
211       {
212         $tableParams = array();
213
214         // Columns
215         if (isset($classParams['columns']))
216         {
217           $tableParams = array_merge($classParams['columns'], $tableParams);
218           unset($classParams['columns']);
219         }
220
221         // Indexes and foreign keys
222         if (isset($classParams['indexes']))
223         {
224           $tableParams['_indexes'] = $classParams['indexes'];
225           unset($classParams['indexes']);
226         }
227         if (isset($classParams['uniques']))
228         {
229           $tableParams['_uniques'] = $classParams['uniques'];
230           unset($classParams['uniques']);
231         }
232         if (isset($classParams['foreignKeys']))
233         {
234           $tableParams['_foreignKeys'] = $classParams['foreignKeys'];
235           unset($classParams['foreignKeys']);
236         }
237
238         // Behaviors
239         if (isset($classParams['behaviors']))
240         {
241           $tableParams['_behaviors'] = $classParams['behaviors'];
242           unset($classParams['behaviors']);
243         }
244
245         // Inheritance
246         if (isset($classParams['inheritance']))
247         {
248           $tableParams['_inheritance'] = $classParams['inheritance'];
249           unset($classParams['inheritance']);
250         }
251
252         // Nested sets
253         if (isset($classParams['nestedSet']))
254         {
255           $tableParams['_nestedSet'] = $classParams['nestedSet'];
256           unset($classParams['nestedSet']);
257         }
258
259         // Table attributes
260         $tableAttributes = array();
261         if (isset($classParams['tableName']))
262         {
263           $tableName = $classParams['tableName'];
264           unset($classParams['tableName']);
265         }
266         else
267         {
268           $tableName = sfInflector::underscore($className);
269         }
270
271         if (sfInflector::camelize($tableName) != $className)
272         {
273           $tableAttributes['phpName'] = $className;
274         }
275
276         if ($tableAttributes || $classParams)
277         {
278           $tableParams['_attributes'] = array_merge($tableAttributes, $classParams);
279         }
280
281         $tables[$tableName] = $tableParams;
282       }
283       $database = array_merge($database, $tables);
284       unset($schema['classes']);
285     }
286
287     // Database attributes
288     if ($schema)
289     {
290       $database['_attributes'] = $schema;
291     }
292
293     return array($connection_name => $database);
294   }
295
296   /**
297    * Dumps schema as propel xml
298    *
299    * @return unknown
300    */
301   public function asXML()
302   {
303     $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
304
305     $xml .= "<database name=\"$this->connection_name\"".$this->getAttributesFor($this->database).">\n";
306
307     // tables
308     foreach ($this->getChildren($this->database) as $tb_name => $table)
309     {
310       // capture nested set config for this table
311       if ($isNestedSet = isset($table['_nestedSet']))
312       {
313         $treeConfig = $table['_nestedSet'];
314         if (
315           !isset($treeConfig['left'])
316           ||
317           !isset($treeConfig['right'])
318           ||
319           !isset($table[$treeConfig['left']])
320           ||
321           !isset($table[$treeConfig['right']])
322           ||
323           (isset($treeConfig['scope']) && !isset($table[$treeConfig['scope']]))
324         )
325         {
326           throw new sfException(sprintf('Incorrect NestedSet configuration for "%s" table.', $tb_name));
327         }
328       }
329
330       $xml .= "\n  <table name=\"$tb_name\"".$this->getAttributesFor($table);
331       if ($isNestedSet)
332       {
333         $xml .= ' treeMode="NestedSet"';
334       }
335       if (isset($table['_behaviors']))
336       {
337         $xml .= sprintf(" behaviors=\"%s\"", htmlspecialchars(serialize($table['_behaviors'])), ENT_QUOTES, sfConfig::get('sf_charset'));
338       }
339       $xml .= ">\n";
340
341       // columns
342       foreach ($this->getChildren($table) as $col_name => $column)
343       {
344         // inheritance
345         if (
346           isset($table['_inheritance'])
347           &&
348           isset($table['_inheritance']['column'])
349           &&
350           $col_name == $table['_inheritance']['column']
351           &&
352           isset($table['_inheritance']['classes'])
353           &&
354           is_array($table['_inheritance']['classes'])
355         )
356         {
357           $column['inheritance'] = $table['_inheritance']['classes'];
358           unset($table['_inheritance']);
359         }
360
361         // add nested set attributes to this column
362         if ($isNestedSet && in_array($col_name, $treeConfig))
363         {
364           if ($col_name == $treeConfig['left'])
365           {
366             $column['nestedSetLeftKey'] = 'true';
367           }
368           elseif ($col_name == $treeConfig['right'])
369           {
370             $column['nestedSetRightKey'] = 'true';
371           }
372           elseif (isset($treeConfig['scope']) && $col_name == $treeConfig['scope'])
373           {
374             $column['treeScopeKey'] = 'true';
375           }
376         }
377
378         $xml .= "    <column name=\"$col_name\"".$this->getAttributesForColumn($tb_name, $col_name, $column);
379       }
380
381       // indexes
382       if (isset($table['_indexes']))
383       {
384         foreach ($table['_indexes'] as $index_name => $index)
385         {
386           $xml .= "    <index name=\"$index_name\">\n";
387           foreach ($index as $index_column)
388           {
389             preg_match('/^(.+?)\(([\d]+)\)$/', $index_column, $matches);
390             if (isset($matches[2]))
391             {
392               $xml .= "      <index-column name=\"{$matches[1]}\" size=\"{$matches[2]}\" />\n";
393             }
394             else
395             {
396               $xml .= "      <index-column name=\"$index_column\" />\n";
397             }
398           }
399           $xml .= "    </index>\n";
400         }
401       }
402
403       // uniques
404       if (isset($table['_uniques']))
405       {
406         foreach ($table['_uniques'] as $unique_name => $index)
407         {
408           $xml .= "    <unique name=\"$unique_name\">\n";
409           foreach ($index as $unique_column)
410           {
411             preg_match('/^(.+?)\(([\d]+)\)$/', $unique_column, $matches);
412             if (isset($matches[2]))
413             {
414               $xml .= "      <unique-column name=\"{$matches[1]}\" size=\"{$matches[2]}\" />\n";
415             }
416             else
417             {
418               $xml .= "      <unique-column name=\"$unique_column\" />\n";
419             }
420           }
421           $xml .= "    </unique>\n";
422         }
423       }
424
425       // foreign-keys
426       if (isset($table['_foreignKeys']))
427       {
428         foreach ($table['_foreignKeys'] as $fkey_name => $fkey)
429         {
430           if (!isset($fkey['foreignTable']))
431           {
432             if (
433               !isset($fkey['foreignClass'])
434               ||
435               !$fkey['foreignTable'] = $this->findTable($fkey['foreignClass'])
436             )
437             {
438               throw new sfException(sprintf('Unable to resolve foreign table for foreign key "%s": %s', $fkey_name, var_export($fkey, true)));
439             }
440           }
441           unset($fkey['foreignClass']);
442
443           $xml .= '    <foreign-key';
444
445           // foreign key name
446           if (!is_numeric($fkey_name))
447           {
448             $xml .= " name=\"$fkey_name\"";
449           }
450
451           // other attributes
452           foreach ($fkey as $attribute_name => $attribute_value)
453           {
454             if (is_string($attribute_value))
455             {
456               $xml .= " $attribute_name=\"$attribute_value\"";
457             }
458           }
459
460           $xml .= ">\n";
461
462           // references
463           if (isset($fkey['references']))
464           {
465             foreach ($fkey['references'] as $reference)
466             {
467               $xml .= "      <reference local=\"{$reference['local']}\" foreign=\"{$reference['foreign']}\" />\n";
468             }
469           }
470           $xml .= "    </foreign-key>\n";
471         }
472       }
473
474       $xml .= "  </table>\n";
475     }
476     $xml .= "\n</database>\n";
477
478     return $xml;
479   }
480
481   /**
482    * Fixes databases in yaml shorthand schema
483    *
484    */
485   protected function fixYAMLDatabase()
486   {
487     if (!isset($this->database['_attributes']))
488     {
489       $this->database['_attributes'] = array();
490     }
491
492     // conventions for database attributes
493     $this->setIfNotSet($this->database['_attributes'], 'defaultIdMethod', 'native');
494     $this->setIfNotSet($this->database['_attributes'], 'package', 'lib.model');
495   }
496
497   /**
498    * Fixes i18n tables in yaml shorthand schema
499    *
500    */
501   protected function fixYAMLI18n()
502   {
503     foreach ($this->getTables() as $i18n_table => $columns)
504     {
505       $pos = strpos($i18n_table, '_i18n');
506
507       $has_primary_key = false;
508       foreach ($columns as $column => $attributes)
509       {
510         if (is_array($attributes) && array_key_exists('primaryKey', $attributes))
511         {
512           $has_primary_key = true;
513         }
514       }
515
516       if ($pos > 0 && $pos == strlen($i18n_table) - 5 && !$has_primary_key)
517       {
518         // i18n table without primary key
519         $main_table = $this->findTable(substr($i18n_table, 0, $pos));
520
521         if ($main_table)
522         {
523           // set i18n attributes for main table
524           $this->setIfNotSet($this->database[$main_table]['_attributes'], 'isI18N', 1);
525           $this->setIfNotSet($this->database[$main_table]['_attributes'], 'i18nTable', $i18n_table);
526
527           // set id and culture columns for i18n table
528           $this->setIfNotSet($this->database[$i18n_table], 'id', array(
529             'type'             => 'integer',
530             'required'         => true,
531             'primaryKey'       => true,
532             'foreignTable'     => $main_table,
533             'foreignReference' => 'id',
534             'onDelete'         => 'cascade',
535           ));
536           $this->setIfNotSet($this->database[$i18n_table], 'culture', array(
537             'isCulture'  => true,
538             'type'       => 'varchar',
539             'size'       => '7',
540             'required'   => true,
541             'primaryKey' => true,
542           ));
543         }
544         else
545         {
546           throw new sfException(sprintf('Missing main table for internationalized table "%s".', $i18n_table));
547         }
548       }
549     }
550   }
551
552   /**
553    * Fixes columns in yaml shorthand schema
554    *
555    */
556   protected function fixYAMLColumns()
557   {
558     foreach ($this->getTables() as $table => $columns)
559     {
560       $has_primary_key = false;
561
562       foreach ($columns as $column => $attributes)
563       {
564         if ($attributes == null)
565         {
566           // conventions for null attributes
567           if ($column == 'created_at' || $column == 'updated_at')
568           {
569             // timestamp convention
570             $this->database[$table][$column]['type']= 'timestamp';
571           }
572
573           if ($column == 'id')
574           {
575             // primary key convention
576             $this->database[$table]['id'] = array(
577             'type'          => 'integer',
578             'required'      => true,
579             'primaryKey'    => true,
580             'autoIncrement' => true
581             );
582             $has_primary_key = true;
583           }
584
585           $pos = strpos($column, '_id');
586           if ($pos > 0 && $pos == strlen($column) - 3)
587           {
588             // foreign key convention
589             $foreign_table = $this->findTable(substr($column, 0, $pos));
590             if ($foreign_table)
591             {
592               $this->database[$table][$column] = array(
593                 'type'             => 'integer',
594                 'foreignTable'     => $foreign_table,
595                 'foreignReference' => 'id',
596               );
597             }
598             else
599             {
600               throw new sfException(sprintf('Unable to resolve foreign table for column "%s".', $column));
601             }
602           }
603
604         }
605         else
606         {
607           if (!is_array($attributes))
608           {
609             // compact type given as single attribute
610             $this->database[$table][$column] = $this->getAttributesFromCompactType($attributes);
611           }
612           else
613           {
614             if (isset($attributes['type']))
615             {
616               // compact type given as value of the type attribute
617               $this->database[$table][$column] = array_merge($this->database[$table][$column], $this->getAttributesFromCompactType($attributes['type']));
618             }
619             if (isset($attributes['primaryKey']))
620             {
621               $has_primary_key = true;
622             }
623           }
624         }
625       }
626
627       if (!$has_primary_key)
628       {
629         // convention for tables without primary key
630         $this->database[$table]['id'] = array(
631         'type'          => 'integer',
632         'required'      => true,
633         'primaryKey'    => true,
634         'autoIncrement' => true
635         );
636       }
637     }
638   }
639
640   /**
641    * Returns attributes for compact type
642    *
643    * @param string $type
644    *
645    * @return array The attributes for type
646    */
647   protected function getAttributesFromCompactType($type)
648   {
649     preg_match('/varchar\(([\d]+)\)/', $type, $matches);
650     if (isset($matches[1]))
651     {
652       return array('type' => 'varchar', 'size' => $matches[1]);
653     }
654     else
655     {
656       return array('type' => $type);
657     }
658   }
659
660   /**
661    * Sets entry if not set
662    *
663    * @param string $entry
664    * @param string $key
665    * @param string $value
666    */
667   protected function setIfNotSet(&$entry, $key, $value)
668   {
669     if (!isset($entry[$key]))
670     {
671       $entry[$key] = $value;
672     }
673   }
674
675   /**
676    * Find table by name
677    *
678    * @param string $table_name
679    *
680    * @return array
681    */
682   protected function findTable($table_name)
683   {
684     // find a table from a phpName or a name
685     $table_match = false;
686     foreach ($this->getTables() as $tb_name => $table)
687     {
688       if (
689       ($tb_name == $table_name)
690       || (isset($table['_attributes']['phpName']) &&
691       (
692       $table['_attributes']['phpName'] == sfInflector::camelize($table_name)
693       || $table['_attributes']['phpName'] == $table_name
694       )
695       || (sfInflector::underscore($table_name) == $tb_name))
696       )
697       {
698         $table_match = $tb_name;
699         break;
700       }
701     }
702
703     return $table_match;
704   }
705
706   /**
707    * Get attributes for column
708    *
709    * @param string $tb_name
710    * @param string $col_name
711    * @param string $column
712    *
713    * @return array
714    */
715   protected function getAttributesForColumn($tb_name, $col_name, $column)
716   {
717     $attributes_string = '';
718     if (is_array($column))
719     {
720       foreach ($column as $key => $value)
721       {
722         if (!in_array($key, array('foreignClass', 'foreignTable', 'foreignReference', 'fkPhpName', 'fkRefPhpName', 'onDelete', 'onUpdate', 'index', 'unique', 'sequence', 'inheritance')))
723         {
724           $attributes_string .= " $key=\"".htmlspecialchars($this->getCorrectValueFor($key, $value), ENT_QUOTES, sfConfig::get('sf_charset'))."\"";
725         }
726       }
727       if (isset($column['inheritance']))
728       {
729         $attributes_string .= ' inheritance="single">'."\n";
730
731         $extended_package = isset($this->database[$tb_name]['_attributes']['package']) ? $this->database[$tb_name]['_attributes']['package'] : $this->database['_attributes']['package'];
732         $extended_class   = isset($this->database[$tb_name]['_attributes']['phpName']) ? $this->database[$tb_name]['_attributes']['phpName'] : sfInflector::camelize($tb_name);
733
734         foreach ($column['inheritance'] as $key => $class)
735         {
736           // each inheritance class can have its own package
737           $package = null;
738           if (is_array($class))
739           {
740             $package = isset($class['package']) ? $class['package'] : null;
741             $class   = $class['phpName'];
742           }
743
744           $attributes_string .= vsprintf('      <inheritance extends="%s.%s" key="%s" class="%s"%s />', array(
745             $extended_package,
746             $extended_class,
747             $key,
748             $class,
749             $package ? " package=\"$package\"" : '',
750           ))."\n";
751         }
752
753         $attributes_string .= '    </column>'."\n";
754       }
755       else
756       {
757         $attributes_string .= " />\n";
758       }
759     }
760     else
761     {
762       throw new sfException(sprintf('Incorrect settings for column "%s" of table "%s".', $col_name, $tb_name));
763     }
764
765     // conventions for foreign key attributes
766     if (is_array($column) && (isset($column['foreignTable']) || isset($column['foreignClass'])))
767     {
768       if (isset($column['foreignTable']))
769       {
770         $attributes_string .= "    <foreign-key foreignTable=\"{$column['foreignTable']}\"";
771       }
772       else
773       {
774         $foreignTable = $this->findTable($column['foreignClass']);
775         if (!$foreignTable)
776         {
777           // Let's assume that the class given is from another schema
778           // We have no access to the other schema's phpNames
779           // So our last guess is to try to underscore the class name
780           $foreignTable = sfInflector::underscore($column['foreignClass']);
781         }
782         $attributes_string .= "    <foreign-key foreignTable=\"".$foreignTable."\"";
783       }
784
785       if (isset($column['onDelete']))
786       {
787         $attributes_string .= " onDelete=\"{$column['onDelete']}\"";
788       }
789       if (isset($column['onUpdate']))
790       {
791         $attributes_string .= " onUpdate=\"{$column['onUpdate']}\"";
792       }
793       if (isset($column['fkPhpName']))
794       {
795         $attributes_string .= " phpName=\"{$column['fkPhpName']}\"";
796       }
797       if (isset($column['fkRefPhpName']))
798       {
799         $attributes_string .= " refPhpName=\"{$column['fkRefPhpName']}\"";
800       }
801       $attributes_string .= ">\n";
802       $attributes_string .= "      <reference local=\"$col_name\" foreign=\"{$column['foreignReference']}\" />\n";
803       $attributes_string .= "    </foreign-key>\n";
804     }
805
806     // conventions for index and unique index attributes
807     if (is_array($column) && isset($column['index']))
808     {
809       if ($column['index'] === 'unique')
810       {
811         $attributes_string .= "    <unique>\n";
812         $attributes_string .= "      <unique-column name=\"$col_name\" />\n";
813         $attributes_string .= "    </unique>\n";
814       }
815       else
816       {
817         $attributes_string .= "    <index>\n";
818         $attributes_string .= "      <index-column name=\"$col_name\" />\n";
819         $attributes_string .= "    </index>\n";
820       }
821     }
822
823     // conventions for sequence name attributes
824     // required for databases using sequences for auto-increment columns (e.g. PostgreSQL or Oracle)
825     if (is_array($column) && isset($column['sequence']))
826     {
827       $attributes_string .= "    <id-method-parameter value=\"{$column['sequence']}\" />\n";
828     }
829
830     return $attributes_string;
831   }
832
833   /**
834    * Returns attributes for a tag
835    *
836    * @param string $tag
837    *
838    * @return string
839    */
840   protected function getAttributesFor($tag)
841   {
842     if (!isset($tag['_attributes']))
843     {
844       return '';
845     }
846     $attributes = $tag['_attributes'];
847     $attributes_string = '';
848     foreach ($attributes as $key => $value)
849     {
850       $attributes_string .= ' '.$key.'="'.htmlspecialchars($this->getCorrectValueFor($key, $value), ENT_QUOTES, sfConfig::get('sf_charset')).'"';
851     }
852
853     return $attributes_string;
854   }
855
856   protected function getCorrectValueFor($key, $value)
857   {
858     $booleans = array('required', 'primaryKey', 'autoincrement', 'autoIncrement', 'isI18N', 'isCulture');
859     if (in_array($key, $booleans))
860     {
861       return $value == 1 ? 'true' : 'false';
862     }
863     else
864     {
865       return is_null($value) ? 'null' : $value;
866     }
867   }
868
869   /**
870    * Returns the tables for database
871    *
872    * @return array
873    */
874   public function getTables()
875   {
876     return $this->getChildren($this->database);
877   }
878
879   /**
880    * Returns the children for a given hash
881    *
882    * @param string $hash
883    *
884    * @return array
885    */
886   public function getChildren($hash)
887   {
888     foreach ($hash as $key => $value)
889     {
890       // ignore special children (starting with _)
891       if ($key[0] == '_')
892       {
893         unset($hash[$key]);
894       }
895     }
896
897     return $hash;
898   }
899
900   /**
901    * Loads propel xml schema
902    *
903    * @param string $file The path to the propel xml schema
904    */
905   public function loadXML($file)
906   {
907     $schema = simplexml_load_file($file);
908     $database = array();
909
910     // database
911     list($database_name, $database_attributes) = $this->getNameAndAttributes($schema->attributes());
912     if ($database_name)
913     {
914       $this->connection_name = $database_name;
915     }
916     else
917     {
918       throw new sfException('The database tag misses a name attribute.');
919     }
920     if ($database_attributes)
921     {
922       $database['_attributes'] = $database_attributes;
923     }
924
925     // tables
926     foreach ($schema as $table)
927     {
928       list($table_name, $table_attributes) = $this->getNameAndAttributes($table->attributes());
929       if ($table_name)
930       {
931         $database[$table_name] = array();
932       }
933       else
934       {
935         throw new sfException('A table tag misses the name attribute.');
936       }
937       if ($table_attributes)
938       {
939         $database[$table_name]['_attributes'] = $table_attributes;
940       }
941
942       // columns
943       foreach ($table->xpath('column') as $column)
944       {
945         list($column_name, $column_attributes) = $this->getNameAndAttributes($column->attributes());
946         if ($column_name)
947         {
948           $database[$table_name][$column_name] = $column_attributes;
949         }
950         else
951         {
952           throw new sfException('A column tag misses the name attribute.');
953         }
954       }
955
956       // foreign-keys
957       $database[$table_name]['_foreignKeys'] = array();
958       foreach ($table->xpath('foreign-key') as $foreign_key)
959       {
960         $foreign_key_table = array();
961
962         // foreign key attributes
963         if (isset($foreign_key['foreignTable']))
964         {
965           $foreign_key_table['foreignTable'] = (string) $foreign_key['foreignTable'];
966         }
967         else
968         {
969           throw new sfException('A foreign key misses the foreignTable attribute.');
970         }
971         if (isset($foreign_key['onDelete']))
972         {
973           $foreign_key_table['onDelete'] = (string) $foreign_key['onDelete'];
974         }
975         if (isset($foreign_key['onUpdate']))
976         {
977           $foreign_key_table['onUpdate'] = (string) $foreign_key['onUpdate'];
978         }
979
980         // foreign key references
981         $foreign_key_table['references'] = array();
982         foreach ($foreign_key->xpath('reference') as $reference)
983         {
984           $reference_attributes = array();
985           foreach ($reference->attributes() as $reference_attribute_name => $reference_attribute_value)
986           {
987             $reference_attributes[$reference_attribute_name] = strval($reference_attribute_value);
988           }
989           $foreign_key_table['references'][] = $reference_attributes;
990         }
991
992         if (isset($foreign_key['name']))
993         {
994           $database[$table_name]['_foreignKeys'][(string)$foreign_key['name']] = $foreign_key_table;
995         }
996         else
997         {
998           $database[$table_name]['_foreignKeys'][] = $foreign_key_table;
999         }
1000
1001       }
1002       $this->removeEmptyKey($database[$table_name], '_foreignKeys');
1003
1004       // indexes
1005       $database[$table_name]['_indexes'] = array();
1006       foreach ($table->xpath('index') as $index)
1007       {
1008         $index_keys = array();
1009         foreach ($index->xpath('index-column') as $index_key)
1010         {
1011           $index_keys[] = strval($index_key['name']);
1012         }
1013         $database[$table_name]['_indexes'][strval($index['name'])] = $index_keys;
1014       }
1015       $this->removeEmptyKey($database[$table_name], '_indexes');
1016
1017       // unique indexes
1018       $database[$table_name]['_uniques'] = array();
1019       foreach ($table->xpath('unique') as $index)
1020       {
1021         $unique_keys = array();
1022         foreach ($index->xpath('unique-column') as $unique_key)
1023         {
1024           $unique_keys[] = strval($unique_key['name']);
1025         }
1026         $database[$table_name]['_uniques'][strval($index['name'])] = $unique_keys;
1027       }
1028       $this->removeEmptyKey($database[$table_name], '_uniques');
1029     }
1030     $this->database = $database;
1031
1032     $this->fixXML();
1033   }
1034
1035   /**
1036    * Fixed xml for using short hand syntax
1037    *
1038    * @return void
1039    */
1040   public function fixXML()
1041   {
1042     $this->fixXMLForeignKeys();
1043     $this->fixXMLIndexes();
1044     // $this->fixXMLColumns();
1045   }
1046
1047   /**
1048    * Fixes xml foreign keys
1049    *
1050    * @return void
1051    */
1052   protected function fixXMLForeignKeys()
1053   {
1054     foreach ($this->getTables() as $table => $columns)
1055     {
1056       if (isset($this->database[$table]['_foreignKeys']))
1057       {
1058         $foreign_keys = $this->database[$table]['_foreignKeys'];
1059         foreach ($foreign_keys as $foreign_key_name => $foreign_key_attributes)
1060         {
1061           // Only single foreign keys can be simplified
1062           if (count($foreign_key_attributes['references']) == 1)
1063           {
1064             $reference = $foreign_key_attributes['references'][0];
1065
1066             // set simple foreign key
1067             $this->database[$table][$reference['local']]['foreignTable'] = $foreign_key_attributes['foreignTable'];
1068             $this->database[$table][$reference['local']]['foreignReference'] = $reference['foreign'];
1069             if (isset($foreign_key_attributes['onDelete']))
1070             {
1071               $this->database[$table][$reference['local']]['onDelete'] = $foreign_key_attributes['onDelete'];
1072             }
1073             if (isset($foreign_key_attributes['onUpdate']))
1074             {
1075               $this->database[$table][$reference['local']]['onUpdate'] = $foreign_key_attributes['onUpdate'];
1076             }
1077
1078             // remove complex foreign key
1079             unset($this->database[$table]['_foreignKeys'][$foreign_key_name]);
1080           }
1081
1082           $this->removeEmptyKey($this->database[$table], '_foreignKeys');
1083         }
1084       }
1085     }
1086   }
1087
1088   /**
1089    * Fixes xml indices
1090    *
1091    * @return void
1092    */
1093   protected function fixXMLIndexes()
1094   {
1095     foreach ($this->getTables() as $table => $columns)
1096     {
1097       if (isset($this->database[$table]['_indexes']))
1098       {
1099         $indexes = $this->database[$table]['_indexes'];
1100         foreach ($indexes as $index => $references)
1101         {
1102           // Only single indexes can be simplified
1103           if (count($references) == 1 && false !== substr($index, 0, strlen($index) - 6) && array_key_exists(substr($index, 0, strlen($index) - 6), $columns))
1104           {
1105             $reference = $references[0];
1106
1107             // set simple index
1108             $this->database[$table][$reference]['index'] = 'true';
1109
1110             // remove complex index
1111             unset($this->database[$table]['_indexes'][$index]);
1112           }
1113
1114           $this->removeEmptyKey($this->database[$table], '_indexes');
1115         }
1116       }
1117       if (isset($this->database[$table]['_uniques']))
1118       {
1119         $uniques = $this->database[$table]['_uniques'];
1120         foreach ($uniques as $index => $references)
1121         {
1122           // Only single unique indexes can be simplified
1123           if (count($references) == 1 && false !== substr($index, 0, strlen($index) - 7) && array_key_exists(substr($index, 0, strlen($index) - 7), $columns))
1124           {
1125             $reference = $references[0];
1126
1127             // set simple index
1128             $this->database[$table][$reference]['index'] = 'unique';
1129
1130             // remove complex unique index
1131             unset($this->database[$table]['_uniques'][$index]);
1132           }
1133
1134           $this->removeEmptyKey($this->database[$table], '_uniques');
1135         }
1136       }
1137     }
1138   }
1139
1140   /**
1141    * Fixes xml columns
1142    *
1143    * @return void
1144    */
1145   protected function fixXMLColumns()
1146   {
1147     foreach ($this->getTables() as $table => $columns)
1148     {
1149       foreach ($columns as $column => $attributes)
1150       {
1151         if ($column == 'id' && !array_diff($attributes, array('type' => 'integer', 'required' => 'true', 'primaryKey' => 'true', 'autoIncrement' => 'true')))
1152         {
1153           // simplify primary keys
1154           $this->database[$table]['id'] = null;
1155         }
1156
1157         if (($column == 'created_at') || ($column == 'updated_at') && !array_diff($attributes, array('type' => 'timestamp')))
1158         {
1159           // simplify timestamps
1160           $this->database[$table][$column] = null;
1161         }
1162
1163         $pos                 = strpos($column, '_id');
1164         $has_fk_name         = $pos > 0 && $pos == strlen($column) - 3;
1165         $is_foreign_key      = isset($attributes['type']) && $attributes['type'] == 'integer' && isset($attributes['foreignReference']) && $attributes['foreignReference'] == 'id';
1166         $has_foreign_table   = isset($attributes['foreignTable']) && array_key_exists($attributes['foreignTable'], $this->getTables());
1167         $has_other_attribute = isset($attributes['onDelete']);
1168         if ($has_fk_name && $has_foreign_table && $is_foreign_key && !$has_other_attribute)
1169         {
1170           // simplify foreign key
1171           $this->database[$table][$column] = null;
1172         }
1173       }
1174     }
1175   }
1176
1177   /**
1178    * Dumps schema as yaml
1179    *
1180    * @return string
1181    */
1182   public function asYAML()
1183   {
1184     return sfYaml::dump(array($this->connection_name => $this->database), 3);
1185   }
1186
1187   /**
1188    * Returns name and attributes from given hash
1189    *
1190    * @param string $hash
1191    * @param string $name_attribute
1192    *
1193    * @return array
1194    */
1195   protected function getNameAndAttributes($hash, $name_attribute = 'name')
1196   {
1197     // tag name
1198     $name = '';
1199     if (isset($hash[$name_attribute]))
1200     {
1201       $name = strval($hash[$name_attribute]);
1202       unset($hash[$name_attribute]);
1203     }
1204
1205     // tag attributes
1206     $attributes = array();
1207     foreach ($hash as $attribute => $value)
1208     {
1209       $value = (string) $value;
1210       if (in_array($value, array('true', 'on')))
1211       {
1212         $value = true;
1213       }
1214       elseif (in_array($value, array('false', 'off')))
1215       {
1216         $value = false;
1217       }
1218       $attributes[$attribute] = $value;
1219     }
1220
1221     return array($name, $attributes);
1222   }
1223
1224   protected function removeEmptyKey(&$hash, $key)
1225   {
1226     if (isset($hash[$key]) && !$hash[$key])
1227     {
1228       unset($hash[$key]);
1229     }
1230   }
1231 }
1232
Note: See TracBrowser for help on using the browser.