Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 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: sfDoctrineFormGenerator.class.php 29661 2010-05-28 16:56:42Z Kris.Wallsmith $
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
}