Development

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

You must first sign up to be able to contribute.

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

Revision 29661, 18.4 kB (checked in by Kris.Wallsmith, 5 years ago)

[1.3, 1.4] changed generation of (non-foreign key) primary key form fields so validation will fail if the primary key is changed (closes #8639, refs #8704)

  • 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               $generators = Doctrine_Core::getTable($modelName)->getGenerators();
171               foreach ($generators as $generator)
172               {
173                 $this->pluginModels[$generator->getOption('className')] = $pluginName;
174               }
175             }
176           }
177         }
178       }
179     }
180
181     return $this->pluginModels;
182   }
183
184   /**
185    * Check to see if a model is part of a plugin
186    *
187    * @param string $modelName
188    * @return boolean $bool
189    */
190   public function isPluginModel($modelName)
191   {
192     return isset($this->pluginModels[$modelName]) ? true:false;
193   }
194
195   /**
196    * Get the name of the plugin a model belongs to
197    *
198    * @param string $modelName
199    * @return string $pluginName
200    */
201   public function getPluginNameForModel($modelName)
202   {
203     if ($this->isPluginModel($modelName))
204     {
205       return $this->pluginModels[$modelName];
206     } else {
207       return false;
208     }
209   }
210
211   /**
212    * Returns an array of relations that represents a many to many relationship.
213    *
214    * @return array An array of relations
215    */
216   public function getManyToManyRelations()
217   {
218     $relations = array();
219     foreach ($this->table->getRelations() as $relation)
220     {
221       if (
222         Doctrine_Relation::MANY == $relation->getType()
223         &&
224         isset($relation['refTable'])
225         &&
226         (null === $this->getParentModel() || !Doctrine_Core::getTable($this->getParentModel())->hasRelation($relation->getAlias()))
227       )
228       {
229         $relations[] = $relation;
230       }
231     }
232
233     return $relations;
234   }
235
236   /**
237    * Returns PHP names for all foreign keys of the current table.
238    *
239    * This method does not returns foreign keys that are also primary keys.
240    *
241    * @return array An array composed of:
242    *                 * The foreign table PHP name
243    *                 * The foreign key PHP name
244    *                 * A Boolean to indicate whether the column is required or not
245    *                 * A Boolean to indicate whether the column is a many to many relationship or not
246    */
247   public function getForeignKeyNames()
248   {
249     $names = array();
250     foreach ($this->table->getRelations() as $relation)
251     {
252       if ($relation->getType() === Doctrine_Relation::ONE)
253       {
254         $foreignDef = $relation->getTable()->getDefinitionOf($relation->getForeignFieldName());
255         $names[] = array($relation['table']->getOption('name'), $relation->getForeignFieldName(), $this->isColumnNotNull($relation->getForeignFieldName(), $foreignDef), false);
256       }
257     }
258
259     foreach ($this->getManyToManyRelations() as $relation)
260     {
261       $names[] = array($relation['table']->getOption('name'), $relation['alias'], false, true);
262     }
263
264     return $names;
265   }
266
267   /**
268    * Returns the first primary key column of the current table.
269    *
270    * @param ColumnMap A ColumnMap object
271    */
272   public function getPrimaryKey()
273   {
274     foreach ($this->getColumns() as $column)
275     {
276       if ($column->isPrimaryKey())
277       {
278         return $column;
279       }
280     }
281   }
282
283   /**
284    * Returns a sfWidgetForm class name for a given column.
285    *
286    * @param  sfDoctrineColumn $column
287    * @return string    The name of a subclass of sfWidgetForm
288    */
289   public function getWidgetClassForColumn($column)
290   {
291     switch ($column->getDoctrineType())
292     {
293       case 'string':
294         $widgetSubclass = null === $column->getLength() || $column->getLength() > 255 ? 'Textarea' : 'InputText';
295         break;
296       case 'boolean':
297         $widgetSubclass = 'InputCheckbox';
298         break;
299       case 'blob':
300       case 'clob':
301         $widgetSubclass = 'Textarea';
302         break;
303       case 'date':
304         $widgetSubclass = 'Date';
305         break;
306       case 'time':
307         $widgetSubclass = 'Time';
308         break;
309       case 'timestamp':
310         $widgetSubclass = 'DateTime';
311         break;
312       case 'enum':
313         $widgetSubclass = 'Choice';
314         break;
315       default:
316         $widgetSubclass = 'InputText';
317     }
318
319     if ($column->isPrimaryKey())
320     {
321       $widgetSubclass = 'InputHidden';
322     }
323     else if ($column->isForeignKey())
324     {
325       $widgetSubclass = 'DoctrineChoice';
326     }
327
328     return sprintf('sfWidgetForm%s', $widgetSubclass);
329   }
330
331   /**
332    * Returns a PHP string representing options to pass to a widget for a given column.
333    *
334    * @param sfDoctrineColumn $column
335    *
336    * @return string The options to pass to the widget as a PHP string
337    */
338   public function getWidgetOptionsForColumn($column)
339   {
340     $options = array();
341
342     if ($column->isForeignKey())
343     {
344       $options[] = sprintf('\'model\' => $this->getRelatedModelName(\'%s\'), \'add_empty\' => %s', $column->getRelationKey('alias'), $column->isNotNull() ? 'false' : 'true');
345     }
346     else if ('enum' == $column->getDoctrineType() && is_subclass_of($this->getWidgetClassForColumn($column), 'sfWidgetFormChoiceBase'))
347     {
348       $options[] = '\'choices\' => '.$this->arrayExport(array_combine($column['values'], $column['values']));
349     }
350
351     return count($options) ? sprintf('array(%s)', implode(', ', $options)) : '';
352   }
353
354   /**
355    * Returns a sfValidator class name for a given column.
356    *
357    * @param sfDoctrineColumn $column
358    * @return string    The name of a subclass of sfValidator
359    */
360   public function getValidatorClassForColumn($column)
361   {
362     switch ($column->getDoctrineType())
363     {
364       case 'boolean':
365         $validatorSubclass = 'Boolean';
366         break;
367       case 'string':
368             if ($column->getDefinitionKey('email'))
369             {
370               $validatorSubclass = 'Email';
371             }
372             else if ($column->getDefinitionKey('regexp'))
373             {
374               $validatorSubclass = 'Regex';
375             }
376             else
377             {
378               $validatorSubclass = 'String';
379             }
380         break;
381       case 'clob':
382       case 'blob':
383         $validatorSubclass = 'String';
384         break;
385       case 'float':
386       case 'decimal':
387         $validatorSubclass = 'Number';
388         break;
389       case 'integer':
390         $validatorSubclass = 'Integer';
391         break;
392       case 'date':
393         $validatorSubclass = 'Date';
394         break;
395       case 'time':
396         $validatorSubclass = 'Time';
397         break;
398       case 'timestamp':
399         $validatorSubclass = 'DateTime';
400         break;
401       case 'enum':
402         $validatorSubclass = 'Choice';
403         break;
404       default:
405         $validatorSubclass = 'Pass';
406     }
407
408     if ($column->isForeignKey())
409     {
410       $validatorSubclass = 'DoctrineChoice';
411     }
412     else if ($column->isPrimaryKey())
413     {
414       $validatorSubclass = 'Choice';
415     }
416
417     return sprintf('sfValidator%s', $validatorSubclass);
418   }
419
420   /**
421    * Returns a PHP string representing options to pass to a validator for a given column.
422    *
423    * @param sfDoctrineColumn $column
424    * @return string    The options to pass to the validator as a PHP string
425    */
426   public function getValidatorOptionsForColumn($column)
427   {
428     $options = array();
429
430     if ($column->isForeignKey())
431     {
432       $options[] = sprintf('\'model\' => $this->getRelatedModelName(\'%s\')', $column->getRelationKey('alias'));
433     }
434     else if ($column->isPrimaryKey())
435     {
436       $options[] = sprintf('\'choices\' => array($this->getObject()->get(\'%s\')), \'empty_value\' => $this->getObject()->get(\'%1$s\')', $column->getFieldName());
437     }
438     else
439     {
440       switch ($column->getDoctrineType())
441       {
442         case 'string':
443           if ($column['length'])
444           {
445             $options[] = sprintf('\'max_length\' => %s', $column['length']);
446           }
447           if (isset($column['minlength']))
448           {
449             $options[] = sprintf('\'min_length\' => %s', $column['minlength']);
450           }
451           if (isset($column['regexp']))
452           {
453             $options[] = sprintf('\'pattern\' => \'%s\'', $column['regexp']);
454           }
455           break;
456         case 'enum':
457           $options[] = '\'choices\' => '.$this->arrayExport($column['values']);
458           break;
459       }
460     }
461
462     // If notnull = false, is a primary or the column has a default value then
463     // make the widget not required
464     if (!$column->isNotNull() || $column->isPrimaryKey() || $column->hasDefinitionKey('default'))
465     {
466       $options[] = '\'required\' => false';
467     }
468
469     return count($options) ? sprintf('array(%s)', implode(', ', $options)) : '';
470   }
471
472   /**
473    * Returns the maximum length for a column name.
474    *
475    * @return integer The length of the longer column name
476    */
477   public function getColumnNameMaxLength()
478   {
479     $max = 0;
480     foreach ($this->getColumns() as $column)
481     {
482       if (($m = strlen($column->getFieldName())) > $max)
483       {
484         $max = $m;
485       }
486     }
487
488     foreach ($this->getManyToManyRelations() as $relation)
489     {
490       if (($m = strlen($this->underscore($relation['alias']).'_list')) > $max)
491       {
492         $max = $m;
493       }
494     }
495
496     return $max;
497   }
498
499   /**
500    * Returns an array of primary key column names.
501    *
502    * @return array An array of primary key column names
503    */
504   public function getPrimaryKeyColumNames()
505   {
506     return $this->table->getIdentifierColumnNames();
507   }
508
509   /**
510    * Returns a PHP string representation for the array of all primary key column names.
511    *
512    * @return string A PHP string representation for the array of all primary key column names
513    *
514    * @see getPrimaryKeyColumNames()
515    */
516   public function getPrimaryKeyColumNamesAsString()
517   {
518     return sprintf('array(\'%s\')', implode('\', \'', $this->getPrimaryKeyColumNames()));
519   }
520
521   /**
522    * Returns true if the current table is internationalized.
523    *
524    * @return Boolean true if the current table is internationalized, false otherwise
525    */
526   public function isI18n()
527   {
528     return $this->table->hasRelation('Translation');
529   }
530
531   /**
532    * Returns the i18n model name for the current table.
533    *
534    * @return string The model class name
535    */
536   public function getI18nModel()
537   {
538     return $this->table->getRelation('Translation')->getTable()->create();
539   }
540
541   public function underscore($name)
542   {
543     return strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), '\\1_\\2', $name));
544   }
545
546   /**
547    * Get array of sfDoctrineColumn objects that exist on the current model but not its parent.
548    *
549    * @return array $columns
550    */
551   public function getColumns()
552   {
553     $parentModel = $this->getParentModel();
554     $parentColumns = $parentModel ? array_keys(Doctrine_Core::getTable($parentModel)->getColumns()) : array();
555
556     $columns = array();
557     foreach (array_diff(array_keys($this->table->getColumns()), $parentColumns) as $name)
558     {
559       $columns[] = new sfDoctrineColumn($name, $this->table);
560     }
561
562     return $columns;
563   }
564
565   public function getUniqueColumnNames()
566   {
567     $uniqueColumns = array();
568
569     foreach ($this->getColumns() as $column)
570     {
571       if ($column->getDefinitionKey('unique'))
572       {
573         $uniqueColumns[] = array($column->getFieldName());
574       }
575     }
576
577     $indexes = $this->table->getOption('indexes');
578     foreach ($indexes as $name => $index)
579     {
580       $index['fields'] = (array) $index['fields'];
581
582       if (isset($index['type']) && $index['type'] == 'unique')
583       {
584         $tmp = $index['fields'];
585         if (is_array(array_shift($tmp)))
586         {
587           $uniqueColumns[] = array_keys($index['fields']);
588         } else {
589           $uniqueColumns[] = $index['fields'];
590         }
591       }
592     }
593
594     return $uniqueColumns;
595   }
596
597   /**
598    * Loads all Doctrine builders.
599    */
600   protected function loadModels()
601   {
602     Doctrine_Core::loadModels($this->generatorManager->getConfiguration()->getModelDirs());
603     $models = Doctrine_Core::getLoadedModels();
604     $models Doctrine_Core::initializeModels($models);
605     $models = Doctrine_Core::filterInvalidModels($models);
606     $this->models = $this->filterModels($models);
607
608     return $this->models;
609   }
610
611   /**
612    * Filter out models that have disabled generation of form classes
613    *
614    * @return array $models Array of models to generate forms for
615    */
616   protected function filterModels($models)
617   {
618     foreach ($models as $key => $model)
619     {
620       $table = Doctrine_Core::getTable($model);
621       $symfonyOptions = (array) $table->getOption('symfony');
622
623       if ($table->isGenerator())
624       {
625         $symfonyOptions = array_merge((array) $table->getParentGenerator()->getOption('table')->getOption('symfony'), $symfonyOptions);
626       }
627
628       if (isset($symfonyOptions['form']) && !$symfonyOptions['form'])
629       {
630         unset($models[$key]);
631       }
632     }
633
634     return $models;
635   }
636
637   /**
638    * Array export. Export array to formatted php code
639    *
640    * @param array $values
641    * @return string $php
642    */
643   protected function arrayExport($values)
644   {
645     $php = var_export($values, true);
646     $php = str_replace("\n", '', $php);
647     $php = str_replace('array (  ', 'array(', $php);
648     $php = str_replace(',)', ')', $php);
649     $php = str_replace('  ', ' ', $php);
650     return $php;
651   }
652
653   /**
654    * Returns the name of the model class this model extends.
655    *
656    * @return string|null
657    */
658   public function getParentModel()
659   {
660     $baseClasses = array(
661       'Doctrine_Record',
662       'sfDoctrineRecord',
663     );
664
665     $builderOptions = sfConfig::get('doctrine_model_builder_options', array());
666     if (isset($builderOptions['baseClassName']))
667     {
668       $baseClasses[] = $builderOptions['baseClassName'];
669     }
670
671     // find the first non-abstract parent
672     $model = $this->modelName;
673     while ($model = get_parent_class($model))
674     {
675       if (in_array($model, $baseClasses))
676       {
677         break;
678       }
679
680       $r = new ReflectionClass($model);
681       if (!$r->isAbstract())
682       {
683         return $r->getName();
684       }
685     }
686   }
687
688   /**
689    * Get the name of the form class to extend based on the inheritance of the model
690    *
691    * @return string
692    */
693   public function getFormClassToExtend()
694   {
695     return null === ($model = $this->getParentModel()) ? 'BaseFormDoctrine' : sprintf('%sForm', $model);
696   }
697 }
698
Note: See TracBrowser for help on using the browser.