Development

/branches/1.4/lib/plugins/sfDoctrinePlugin/lib/generator/sfDoctrineFormGenerator.class.php

You must first sign up to be able to contribute.

root/branches/1.4/lib/plugins/sfDoctrinePlugin/lib/generator/sfDoctrineFormGenerator.class.php

Revision 32892, 18.5 kB (checked in by fabien, 3 years ago)

[1.4] fixed Doctrine form generation when a schema has abstract tables

  • 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) Jonathan H. Wage <jonwage@gmail.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  * Doctrine form generator.
14  *
15  * This class generates a Doctrine forms.
16  *
17  * @package    symfony
18  * @subpackage generator
19  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
20  * @author     Jonathan H. Wage <jonwage@gmail.com>
21  * @version    SVN: $Id$
22  */
23 class sfDoctrineFormGenerator extends sfGenerator
24 {
25   /**
26    * Array of all the loaded models
27    *
28    * @var array
29    */
30   public $models = array();
31
32   /**
33    * Array of all plugin models
34    *
35    * @var array
36    */
37   public $pluginModels = array();
38
39   /**
40    * Initializes the current sfGenerator instance.
41    *
42    * @param sfGeneratorManager A sfGeneratorManager instance
43    */
44   public function initialize(sfGeneratorManager $generatorManager)
45   {
46     parent::initialize($generatorManager);
47
48     $this->getPluginModels();
49     $this->setGeneratorClass('sfDoctrineForm');
50   }
51
52   /**
53    * Generates classes and templates in cache.
54    *
55    * @param array The parameters
56    *
57    * @return string The data to put in configuration cache
58    */
59   public function generate($params = array())
60   {
61     $this->params = $params;
62
63     if (!isset($this->params['model_dir_name']))
64     {
65       $this->params['model_dir_name'] = 'model';
66     }
67
68     if (!isset($this->params['form_dir_name']))
69     {
70       $this->params['form_dir_name'] = 'form';
71     }
72
73     $models = $this->loadModels();
74
75     // create the project base class for all forms
76     $file = sfConfig::get('sf_lib_dir').'/form/doctrine/BaseFormDoctrine.class.php';
77     if (!file_exists($file))
78     {
79       if (!is_dir($directory = dirname($file)))
80       {
81         mkdir($directory, 0777, true);
82       }
83
84       file_put_contents($file, $this->evalTemplate('sfDoctrineFormBaseTemplate.php'));
85     }
86
87     $pluginPaths = $this->generatorManager->getConfiguration()->getAllPluginPaths();
88
89     // create a form class for every Doctrine class
90     foreach ($models as $model)
91     {
92       $this->table = Doctrine_Core::getTable($model);
93       $this->modelName = $model;
94
95       $baseDir = sfConfig::get('sf_lib_dir') . '/form/doctrine';
96
97       $isPluginModel = $this->isPluginModel($model);
98       if ($isPluginModel)
99       {
100         $pluginName = $this->getPluginNameForModel($model);
101         $baseDir .= '/' . $pluginName;
102       }
103
104       if (!is_dir($baseDir.'/base'))
105       {
106         mkdir($baseDir.'/base', 0777, true);
107       }
108
109       file_put_contents($baseDir.'/base/Base'.$model.'Form.class.php', $this->evalTemplate(null === $this->getParentModel() ? 'sfDoctrineFormGeneratedTemplate.php' : 'sfDoctrineFormGeneratedInheritanceTemplate.php'));
110
111       if ($isPluginModel)
112       {
113         $pluginBaseDir = $pluginPaths[$pluginName].'/lib/form/doctrine';
114         if (!file_exists($classFile = $pluginBaseDir.'/Plugin'.$model.'Form.class.php'))
115         {
116             if (!is_dir($pluginBaseDir))
117             {
118               mkdir($pluginBaseDir, 0777, true);
119             }
120             file_put_contents($classFile, $this->evalTemplate('sfDoctrineFormPluginTemplate.php'));
121         }
122       }
123       if (!file_exists($classFile = $baseDir.'/'.$model.'Form.class.php'))
124       {
125         if ($isPluginModel)
126         {
127            file_put_contents($classFile, $this->evalTemplate('sfDoctrinePluginFormTemplate.php'));
128         } else {
129            file_put_contents($classFile, $this->evalTemplate('sfDoctrineFormTemplate.php'));
130         }
131       }
132     }
133   }
134
135   /**
136    * Get all the models which are a part of a plugin and the name of the plugin.
137    * The array format is modelName => pluginName
138    *
139    * @todo This method is ugly and is a very weird way of finding the models which
140    *       belong to plugins. If we could come up with a better way that'd be great
141    * @return array $pluginModels
142    */
143   public function getPluginModels()
144   {
145     if (!$this->pluginModels)
146     {
147       $plugins     = $this->generatorManager->getConfiguration()->getPlugins();
148       $pluginPaths = $this->generatorManager->getConfiguration()->getAllPluginPaths();
149
150       foreach ($pluginPaths as $pluginName => $path)
151       {
152         if (!in_array($pluginName, $plugins))
153         {
154           continue;
155         }
156
157         foreach (sfFinder::type('file')->name('*.php')->in($path.'/lib/model/doctrine') as $path)
158         {
159           $info = pathinfo($path);
160           $e = explode('.', $info['filename']);
161           $modelName = substr($e[0], 6, strlen($e[0]));
162
163           if (class_exists($e[0]) && class_exists($modelName))
164           {
165             $parent = new ReflectionClass('Doctrine_Record');
166             $reflection = new ReflectionClass($modelName);
167             if ($reflection->isSubClassOf($parent))
168             {
169               $this->pluginModels[$modelName] = $pluginName;
170               
171               if ($reflection->isInstantiable())
172               {
173                 $generators = Doctrine_Core::getTable($modelName)->getGenerators();
174                 foreach ($generators as $generator)
175                 {
176                   $this->pluginModels[$generator->getOption('className')] = $pluginName;
177                 } 
178               }
179             }
180           }
181         }
182       }
183     }
184
185     return $this->pluginModels;
186   }
187
188   /**
189    * Check to see if a model is part of a plugin
190    *
191    * @param string $modelName
192    * @return boolean $bool
193    */
194   public function isPluginModel($modelName)
195   {
196     return isset($this->pluginModels[$modelName]) ? true:false;
197   }
198
199   /**
200    * Get the name of the plugin a model belongs to
201    *
202    * @param string $modelName
203    * @return string $pluginName
204    */
205   public function getPluginNameForModel($modelName)
206   {
207     if ($this->isPluginModel($modelName))
208     {
209       return $this->pluginModels[$modelName];
210     } else {
211       return false;
212     }
213   }
214
215   /**
216    * Returns an array of relations that represents a many to many relationship.
217    *
218    * @return array An array of relations
219    */
220   public function getManyToManyRelations()
221   {
222     $relations = array();
223     foreach ($this->table->getRelations() as $relation)
224     {
225       if (
226         Doctrine_Relation::MANY == $relation->getType()
227         &&
228         isset($relation['refTable'])
229         &&
230         (null === $this->getParentModel() || !Doctrine_Core::getTable($this->getParentModel())->hasRelation($relation->getAlias()))
231       )
232       {
233         $relations[] = $relation;
234       }
235     }
236
237     return $relations;
238   }
239
240   /**
241    * Returns PHP names for all foreign keys of the current table.
242    *
243    * This method does not returns foreign keys that are also primary keys.
244    *
245    * @return array An array composed of:
246    *                 * The foreign table PHP name
247    *                 * The foreign key PHP name
248    *                 * A Boolean to indicate whether the column is required or not
249    *                 * A Boolean to indicate whether the column is a many to many relationship or not
250    */
251   public function getForeignKeyNames()
252   {
253     $names = array();
254     foreach ($this->table->getRelations() as $relation)
255     {
256       if ($relation->getType() === Doctrine_Relation::ONE)
257       {
258         $foreignDef = $relation->getTable()->getDefinitionOf($relation->getForeignFieldName());
259         $names[] = array($relation['table']->getOption('name'), $relation->getForeignFieldName(), $this->isColumnNotNull($relation->getForeignFieldName(), $foreignDef), false);
260       }
261     }
262
263     foreach ($this->getManyToManyRelations() as $relation)
264     {
265       $names[] = array($relation['table']->getOption('name'), $relation['alias'], false, true);
266     }
267
268     return $names;
269   }
270
271   /**
272    * Returns the first primary key column of the current table.
273    *
274    * @param ColumnMap A ColumnMap object
275    */
276   public function getPrimaryKey()
277   {
278     foreach ($this->getColumns() as $column)
279     {
280       if ($column->isPrimaryKey())
281       {
282         return $column;
283       }
284     }
285   }
286
287   /**
288    * Returns a sfWidgetForm class name for a given column.
289    *
290    * @param  sfDoctrineColumn $column
291    * @return string    The name of a subclass of sfWidgetForm
292    */
293   public function getWidgetClassForColumn($column)
294   {
295     switch ($column->getDoctrineType())
296     {
297       case 'string':
298         $widgetSubclass = null === $column->getLength() || $column->getLength() > 255 ? 'Textarea' : 'InputText';
299         break;
300       case 'boolean':
301         $widgetSubclass = 'InputCheckbox';
302         break;
303       case 'blob':
304       case 'clob':
305         $widgetSubclass = 'Textarea';
306         break;
307       case 'date':
308         $widgetSubclass = 'Date';
309         break;
310       case 'time':
311         $widgetSubclass = 'Time';
312         break;
313       case 'timestamp':
314         $widgetSubclass = 'DateTime';
315         break;
316       case 'enum':
317         $widgetSubclass = 'Choice';
318         break;
319       default:
320         $widgetSubclass = 'InputText';
321     }
322
323     if ($column->isPrimaryKey())
324     {
325       $widgetSubclass = 'InputHidden';
326     }
327     else if ($column->isForeignKey())
328     {
329       $widgetSubclass = 'DoctrineChoice';
330     }
331
332     return sprintf('sfWidgetForm%s', $widgetSubclass);
333   }
334
335   /**
336    * Returns a PHP string representing options to pass to a widget for a given column.
337    *
338    * @param sfDoctrineColumn $column
339    *
340    * @return string The options to pass to the widget as a PHP string
341    */
342   public function getWidgetOptionsForColumn($column)
343   {
344     $options = array();
345
346     if ($column->isForeignKey())
347     {
348       $options[] = sprintf('\'model\' => $this->getRelatedModelName(\'%s\'), \'add_empty\' => %s', $column->getRelationKey('alias'), $column->isNotNull() ? 'false' : 'true');
349     }
350     else if ('enum' == $column->getDoctrineType() && is_subclass_of($this->getWidgetClassForColumn($column), 'sfWidgetFormChoiceBase'))
351     {
352       $options[] = '\'choices\' => '.$this->arrayExport(array_combine($column['values'], $column['values']));
353     }
354
355     return count($options) ? sprintf('array(%s)', implode(', ', $options)) : '';
356   }
357
358   /**
359    * Returns a sfValidator class name for a given column.
360    *
361    * @param sfDoctrineColumn $column
362    * @return string    The name of a subclass of sfValidator
363    */
364   public function getValidatorClassForColumn($column)
365   {
366     switch ($column->getDoctrineType())
367     {
368       case 'boolean':
369         $validatorSubclass = 'Boolean';
370         break;
371       case 'string':
372             if ($column->getDefinitionKey('email'))
373             {
374               $validatorSubclass = 'Email';
375             }
376             else if ($column->getDefinitionKey('regexp'))
377             {
378               $validatorSubclass = 'Regex';
379             }
380             else
381             {
382               $validatorSubclass = 'String';
383             }
384         break;
385       case 'clob':
386       case 'blob':
387         $validatorSubclass = 'String';
388         break;
389       case 'float':
390       case 'decimal':
391         $validatorSubclass = 'Number';
392         break;
393       case 'integer':
394         $validatorSubclass = 'Integer';
395         break;
396       case 'date':
397         $validatorSubclass = 'Date';
398         break;
399       case 'time':
400         $validatorSubclass = 'Time';
401         break;
402       case 'timestamp':
403         $validatorSubclass = 'DateTime';
404         break;
405       case 'enum':
406         $validatorSubclass = 'Choice';
407         break;
408       default:
409         $validatorSubclass = 'Pass';
410     }
411
412     if ($column->isForeignKey())
413     {
414       $validatorSubclass = 'DoctrineChoice';
415     }
416     else if ($column->isPrimaryKey())
417     {
418       $validatorSubclass = 'Choice';
419     }
420
421     return sprintf('sfValidator%s', $validatorSubclass);
422   }
423
424   /**
425    * Returns a PHP string representing options to pass to a validator for a given column.
426    *
427    * @param sfDoctrineColumn $column
428    * @return string    The options to pass to the validator as a PHP string
429    */
430   public function getValidatorOptionsForColumn($column)
431   {
432     $options = array();
433
434     if ($column->isForeignKey())
435     {
436       $options[] = sprintf('\'model\' => $this->getRelatedModelName(\'%s\')', $column->getRelationKey('alias'));
437     }
438     else if ($column->isPrimaryKey())
439     {
440       $options[] = sprintf('\'choices\' => array($this->getObject()->get(\'%s\')), \'empty_value\' => $this->getObject()->get(\'%1$s\')', $column->getFieldName());
441     }
442     else
443     {
444       switch ($column->getDoctrineType())
445       {
446         case 'string':
447           if ($column['length'])
448           {
449             $options[] = sprintf('\'max_length\' => %s', $column['length']);
450           }
451           if (isset($column['minlength']))
452           {
453             $options[] = sprintf('\'min_length\' => %s', $column['minlength']);
454           }
455           if (isset($column['regexp']))
456           {
457             $options[] = sprintf('\'pattern\' => \'%s\'', $column['regexp']);
458           }
459           break;
460         case 'enum':
461           $options[] = '\'choices\' => '.$this->arrayExport($column['values']);
462           break;
463       }
464     }
465
466     // If notnull = false, is a primary or the column has a default value then
467     // make the widget not required
468     if (!$column->isNotNull() || $column->isPrimaryKey() || $column->hasDefinitionKey('default'))
469     {
470       $options[] = '\'required\' => false';
471     }
472
473     return count($options) ? sprintf('array(%s)', implode(', ', $options)) : '';
474   }
475
476   /**
477    * Returns the maximum length for a column name.
478    *
479    * @return integer The length of the longer column name
480    */
481   public function getColumnNameMaxLength()
482   {
483     $max = 0;
484     foreach ($this->getColumns() as $column)
485     {
486       if (($m = strlen($column->getFieldName())) > $max)
487       {
488         $max = $m;
489       }
490     }
491
492     foreach ($this->getManyToManyRelations() as $relation)
493     {
494       if (($m = strlen($this->underscore($relation['alias']).'_list')) > $max)
495       {
496         $max = $m;
497       }
498     }
499
500     return $max;
501   }
502
503   /**
504    * Returns an array of primary key column names.
505    *
506    * @return array An array of primary key column names
507    */
508   public function getPrimaryKeyColumNames()
509   {
510     return $this->table->getIdentifierColumnNames();
511   }
512
513   /**
514    * Returns a PHP string representation for the array of all primary key column names.
515    *
516    * @return string A PHP string representation for the array of all primary key column names
517    *
518    * @see getPrimaryKeyColumNames()
519    */
520   public function getPrimaryKeyColumNamesAsString()
521   {
522     return sprintf('array(\'%s\')', implode('\', \'', $this->getPrimaryKeyColumNames()));
523   }
524
525   /**
526    * Returns true if the current table is internationalized.
527    *
528    * @return Boolean true if the current table is internationalized, false otherwise
529    */
530   public function isI18n()
531   {
532     return $this->table->hasRelation('Translation');
533   }
534
535   /**
536    * Returns the i18n model name for the current table.
537    *
538    * @return string The model class name
539    */
540   public function getI18nModel()
541   {
542     return $this->table->getRelation('Translation')->getTable()->create();
543   }
544
545   public function underscore($name)
546   {
547     return strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), '\\1_\\2', $name));
548   }
549
550   /**
551    * Get array of sfDoctrineColumn objects that exist on the current model but not its parent.
552    *
553    * @return array $columns
554    */
555   public function getColumns()
556   {
557     $parentModel = $this->getParentModel();
558     $parentColumns = $parentModel ? array_keys(Doctrine_Core::getTable($parentModel)->getColumns()) : array();
559
560     $columns = array();
561     foreach (array_diff(array_keys($this->table->getColumns()), $parentColumns) as $name)
562     {
563       $columns[] = new sfDoctrineColumn($name, $this->table);
564     }
565
566     return $columns;
567   }
568
569   public function getUniqueColumnNames()
570   {
571     $uniqueColumns = array();
572
573     foreach ($this->getColumns() as $column)
574     {
575       if ($column->getDefinitionKey('unique'))
576       {
577         $uniqueColumns[] = array($column->getFieldName());
578       }
579     }
580
581     $indexes = $this->table->getOption('indexes');
582     foreach ($indexes as $name => $index)
583     {
584       $index['fields'] = (array) $index['fields'];
585
586       if (isset($index['type']) && $index['type'] == 'unique')
587       {
588         $tmp = $index['fields'];
589         if (is_array(array_shift($tmp)))
590         {
591           $uniqueColumns[] = array_keys($index['fields']);
592         } else {
593           $uniqueColumns[] = $index['fields'];
594         }
595       }
596     }
597
598     return $uniqueColumns;
599   }
600
601   /**
602    * Loads all Doctrine builders.
603    */
604   protected function loadModels()
605   {
606     Doctrine_Core::loadModels($this->generatorManager->getConfiguration()->getModelDirs());
607     $models = Doctrine_Core::getLoadedModels();
608     $models Doctrine_Core::initializeModels($models);
609     $models = Doctrine_Core::filterInvalidModels($models);
610     $this->models = $this->filterModels($models);
611
612     return $this->models;
613   }
614
615   /**
616    * Filter out models that have disabled generation of form classes
617    *
618    * @return array $models Array of models to generate forms for
619    */
620   protected function filterModels($models)
621   {
622     foreach ($models as $key => $model)
623     {
624       $table = Doctrine_Core::getTable($model);
625       $symfonyOptions = (array) $table->getOption('symfony');
626
627       if ($table->isGenerator())
628       {
629         $symfonyOptions = array_merge((array) $table->getParentGenerator()->getOption('table')->getOption('symfony'), $symfonyOptions);
630       }
631
632       if (isset($symfonyOptions['form']) && !$symfonyOptions['form'])
633       {
634         unset($models[$key]);
635       }
636     }
637
638     return $models;
639   }
640
641   /**
642    * Array export. Export array to formatted php code
643    *
644    * @param array $values
645    * @return string $php
646    */
647   protected function arrayExport($values)
648   {
649     $php = var_export($values, true);
650     $php = str_replace("\n", '', $php);
651     $php = str_replace('array (  ', 'array(', $php);
652     $php = str_replace(',)', ')', $php);
653     $php = str_replace('  ', ' ', $php);
654     return $php;
655   }
656
657   /**
658    * Returns the name of the model class this model extends.
659    *
660    * @return string|null
661    */
662   public function getParentModel()
663   {
664     $baseClasses = array(
665       'Doctrine_Record',
666       'sfDoctrineRecord',
667     );
668
669     $builderOptions = sfConfig::get('doctrine_model_builder_options', array());
670     if (isset($builderOptions['baseClassName']))
671     {
672       $baseClasses[] = $builderOptions['baseClassName'];
673     }
674
675     // find the first non-abstract parent
676     $model = $this->modelName;
677     while ($model = get_parent_class($model))
678     {
679       if (in_array($model, $baseClasses))
680       {
681         break;
682       }
683
684       $r = new ReflectionClass($model);
685       if (!$r->isAbstract())
686       {
687         return $r->getName();
688       }
689     }
690   }
691
692   /**
693    * Get the name of the form class to extend based on the inheritance of the model
694    *
695    * @return string
696    */
697   public function getFormClassToExtend()
698   {
699     return null === ($model = $this->getParentModel()) ? 'BaseFormDoctrine' : sprintf('%sForm', $model);
700   }
701 }
702
Note: See TracBrowser for help on using the browser.