*/ class CU_ModelForm extends Zend_Form { /** * PluginLoader for loading many relation forms */ const FORM = 'form'; /** * Reference to the model's table class * @var Doctrine_Table */ protected $_table; /** * Instance of the Zend_Form based form used * @var Zend_Form */ protected $_form; /** * Which Zend_Form element types are associated with which doctrine type? * @var array */ protected $_columnTypes = array( 'integer' => 'text', 'decimal' => 'text', 'float' => 'text', 'string' => 'text', 'varchar' => 'text', 'boolean' => 'checkbox', 'timestamp' => 'text', 'time' => 'text', 'date' => 'text', 'enum' => 'select' ); /** * Array of hooks that are called before saving the column * @var array */ protected $_columnHooks = array(); /** * Default validators for doctrine column types * @var array */ protected $_columnValidators = array( 'integer' => 'int', 'float' => 'float', 'double' => 'float' ); /** * Prefix fields with this * @var string */ protected $_fieldPrefix = 'f_'; /** * Column names listed in this array will not be shown in the form * @var array */ protected $_ignoreColumns = array(); /** * Whether or not to generate fields for many parts of m2o and m2m relations * @var bool */ protected $_generateManyFields = false; /** * Use this to override field types for columns. key = column, value = field type * @var array */ protected $_fieldTypes = array(); /** * Field labels. key = column name, value = label * @var array */ protected $_fieldLabels = array(); /** * Labels to use with many to many relations. * key = related class name, value = label * @var array */ protected $_relationLabels = array(); /** * Name of the model class * @var string */ protected $_model = ''; /** * Model instance for editing existing models * @var Doctrine_Record */ protected $_instance = null; /** * Form PluginLoader * @var Zend_Loader_PluginLoader */ protected $_formLoader = null; /** * Stores form class names for many-relations * @var array */ protected $_relationForms = array(); /** * @param array $options Options to pass to the Zend_Form constructor */ public function __construct($options = null) { if($this->_model == '') throw new Exception('No model defined'); $this->_table = Doctrine::getTable($this->_model); parent::__construct($options); $this->_formLoader = new Zend_Loader_PluginLoader(array( 'App_Form_Model' => 'App/Form/Model/' )); $this->_preGenerate(); $this->_generateForm(); $this->_postGenerate(); } public static function create(array $options = array()) { $form = new CU_ModelForm($options); } /** * Override to provide custom pre-form generation logic */ protected function _preGenerate() { } /** * Override to provide custom post-form generation logic */ protected function _postGenerate() { } /** * Override to provide custom post-save logic */ protected function _postSave($persist) { } public function getPluginLoader($type = null) { if($type == self::FORM) return $this->_formLoader; return parent::getPluginLoader($type); } /** * Set the model instance for editing existing rows * @param Doctrine_Record $instance */ public function setRecord($instance) { $this->_instance = $instance; foreach($this->_getColumns() as $name => $definition) { $this->setDefault($this->_fieldPrefix . $name, $this->_instance->$name); } foreach($this->_getRelations() as $name => $relation) { switch($relation->getType()) { case Doctrine_Relation::ONE_AGGREGATE: $idColumn = $relation->getTable()->getIdentifier(); $this->setDefault($this->_fieldPrefix . $relation->getLocal(), $this->_instance->$name->$idColumn); break; case Doctrine_Relation::MANY_AGGREGATE: $formClass = $this->_relationForms[$relation->getClass()]; foreach($this->_instance->$name as $num => $rec) { $form = new $formClass; $form->setRecord($rec); $form->setIsArray(true); $form->removeDecorator('Form'); $form->addElement('submit', $this->_getDeleteButtonName($name, $rec), array( 'label' => 'Delete' )); $label = $relation->getClass(); if(isset($this->_relationLabels[$relation->getClass()])) $label = $this->_relationLabels[$relation->getClass()]; $form->setLegend($label . ' ' . ($num + 1)) ->addDecorator('Fieldset'); $this->addSubForm($form, $this->_getFormName($name, $rec)); } break; } } } public function getRecord() { return ($this->_instance != null) ? $this->_instance : new $this->_model; } /** * Generates the form */ protected function _generateForm() { $this->_columnsToFields(); $this->_relationsToFields(); } /** * Parses columns to fields */ protected function _columnsToFields() { foreach($this->_getColumns() as $name => $definition) { $type = $this->_columnTypes[$definition['type']]; if(isset($this->_fieldTypes[$name])) $type = $this->_fieldTypes[$name]; $field = $this->createElement($type, $this->_fieldPrefix . $name); $label = $name; if(isset($this->_fieldLabels[$name])) $label = $this->_fieldLabels[$name]; if(isset($this->_columnValidators[$definition['type']])) $field->addValidator($this->_columnValidators[$definition['type']]); if(isset($definition['notnull']) && $definition['notnull'] == true) $field->setRequired(true); $field->setLabel($label); if($type == 'select' && $definition['type'] == 'enum') { foreach($definition['values'] as $text) { $field->addMultiOption($text, ucwords($text)); } } $this->addElement($field); } } /** * Parses relations to fields */ protected function _relationsToFields() { foreach($this->_getRelations() as $alias => $relation) { $field = null; switch($relation->getType()) { case Doctrine_Relation::ONE_AGGREGATE: $table = $relation->getTable(); $idColumn = $table->getIdentifier(); $options = array('------'); foreach($table->findAll() as $row) { $options[$row->$idColumn] = (string)$row; } $field = $this->createElement('select', $this->_fieldPrefix . $relation->getLocal()); $label = (string)Doctrine_Manager::connection()->getTable($alias); if(isset($this->_fieldLabels[$alias])) $label = $this->_fieldLabels[$alias]; $field->setLabel($label); $definition = $this->_table->getColumnDefinition($relation->getLocal()); if(isset($definition['notnull']) && $definition['notnull'] == true) $field->addValidator(new CU_Validate_DbRowExists($table)); $field->setMultiOptions($options); break; case Doctrine_Relation::MANY_AGGREGATE: $class = $this->getPluginLoader(self::FORM)->load($relation->getClass()); $this->_relationForms[$relation->getClass()] = $class; $label = $relation->getClass(); if(isset($this->_relationLabels[$relation->getClass()])) $label = $this->_relationLabels[$relation->getClass()]; $field = $this->createElement('submit', $this->_getNewButtonName($alias), array( 'label' => 'Add new '. $label )); break; } if($field != null) $this->addElement($field); } } /** * Returns the name of the new button field for relation alias * @param string $relationAlias alias of the relation * @return string name of the new button */ protected function _getNewButtonName($relationAlias) { return $relationAlias . '_new_button'; } /** * Returns the name of the delete button field for relation alias * @param string $relationAlias alias of the relation * @param Doctrine_Record $record if deleting existing records * @return string name of the new button */ protected function _getDeleteButtonName($relationAlias, Doctrine_Record $record = null) { $val = 'new'; $idColumn = $record->getTable()->getIdentifier(); if($record != null) $val = $record->$idColumn; return $relationAlias . '_' . $val . '_delete'; } /** * Returns the new form name for relation alias * @param string $relationAlias alias of the relation * @param Doctrine_Record $record if editing existing records * @return string name of the new form */ protected function _getFormName($relationAlias, Doctrine_Record $record = null) { if($record != null) { $idColumn = $record->getTable()->getIdentifier(); return $relationAlias . '_' . $record->$idColumn; } return $relationAlias . '_new_form'; } public function isValid($data) { $ndata = $data; if ($this->isArray()) { $key = $this->_getArrayName($this->getElementsBelongTo()); if (isset($data[$key])) { $ndata = $data[$key]; } } foreach($this->_getRelations() as $name => $relation) { if($relation->getType() != Doctrine_Relation::MANY_AGGREGATE) continue; if(isset($ndata[$this->_getNewButtonName($name)]) || isset($ndata[$this->_getFormName($name)])) { if(isset($ndata[$this->_getFormName($name)]) && isset($ndata[$this->_getFormName($name)][$this->_getDeleteButtonName($name)])) { return false; } $cls = $this->_relationForms[$relation->getClass()]; $form = new $cls; $form->setIsArray(true); $form->removeDecorator('Form'); $form->addElement('submit',$this->_getDeleteButtonName($name), array( 'label' => 'Delete' )); $this->addSubForm($form, $this->_getFormName($name)); if(isset($ndata[$this->_getNewButtonName($name)])) return false; } foreach($this->getRecord()->$name as $rec) { $formName = $this->_getFormName($name, $rec); if(isset($ndata[$formName]) && isset($ndata[$formName][$this->_getDeleteButtonName($name, $rec)])) { $this->removeSubForm($formName); $rec->delete(); return false; } } } return parent::isValid($data); } /** * Get unignored columns * @return array */ protected function _getColumns() { $columns = array(); foreach($this->_table->getColumns() as $name => $definition) { if((isset($definition['primary']) && $definition['primary']) || !isset($this->_columnTypes[$definition['type']]) || in_array($name, $this->_ignoreColumns)) continue; $columns[$name] = $definition; } return $columns; } /** * Returns all un-ignored relations * @return array */ protected function _getRelations() { $relations = array(); foreach($this->_table->getRelations() as $name => $definition) { if(in_array($definition->getLocal(), $this->_ignoreColumns) || ($this->_generateManyFields == false && $definition->getType() == Doctrine_Relation::MANY_AGGREGATE)) continue; $relations[$name] = $definition; } return $relations; } /** * Save the form data * @param bool $persist Save to DB or not * @return Doctrine_Record */ public function save($persist = true) { $inst = $this->getRecord(); foreach($this->_getColumns() as $name => $definition) { $inst->$name = $this->_doctrineizeValue($this->getUnfilteredValue($this->_fieldPrefix . $name), $definition['type']); } foreach($this->_getRelations() as $name => $relation) { $colName = $relation->getLocal(); switch($relation->getType()) { case Doctrine_Relation::ONE_AGGREGATE: //Must use null if value=0 so integrity actions won't fail $val = $this->getUnfilteredValue($this->_fieldPrefix . $colName); if($val == 0) $val = null; if(isset($this->_columnHooks[$colName])) $val = call_user_func($this->_columnHooks[$colName], $val); $inst->set($colName, $val); break; case Doctrine_Relation::MANY_AGGREGATE: $idColumn = $relation->getTable()->getIdentifier(); foreach($inst->$name as $rec) { $subForm = $this->getSubForm($name . '_' . $rec->$idColumn); //Should get saved along with the main instance later $subForm->save(false); } $subForm = $this->getSubForm($name . '_new_form'); if($subForm) { $newRec = $subForm->save(false); $inst->{$name}[] = $newRec; } break; } } if($persist) $inst->save(); foreach($this->getSubForms() as $subForm) $subForm->save($persist); $this->_postSave($persist); return $inst; } /** * Correct form value types for Doctrine * @param string $value value * @param string $type column type * @return mixed */ protected function _doctrineizeValue($value, $type) { switch($type) { case 'boolean': return (boolean)$value; break; default: return $value; break; } trigger_error('This line should never run', E_USER_ERROR); } }