Development

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

You must first sign up to be able to contribute.

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

Revision 24392, 36.3 kB (checked in by FabianLange, 4 years ago)

[1.3, 1.4] reverted r24380 because the solution was incorrect. fixed generation of string representation of false for propel behaviors (fixes #7693)

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