diff --git a/lib/Doctrine/Import/Builder.php b/lib/Doctrine/Import/Builder.php index 7dc64d4ab..913c50841 100644 --- a/lib/Doctrine/Import/Builder.php +++ b/lib/Doctrine/Import/Builder.php @@ -179,6 +179,14 @@ class Doctrine_Import_Builder extends Doctrine_Builder */ protected $_phpDocEmail = '##EMAIL##'; + + /** + * Contains the actAs columns after running buildSetUp + * + * @var array + */ + private $_actAsColumns = array(); + /** * _tpl * @@ -396,9 +404,7 @@ public function buildTableDefinition(array $definition) /** * buildSetUp * - * @param array $options - * @param array $columns - * @param array $relations + * @param array $definition * @return string */ public function buildSetUp(array $definition) @@ -857,21 +863,33 @@ public function buildPhpDocs(array $definition) return $ret; } + /** + * find class matching $name + * + * @param $name + * @return class-string + */ + private function findTemplateClassMatchingName($name) + { + $classname = $name; + if (class_exists("Doctrine_Template_$name", true)) { + $classname = "Doctrine_Template_$name"; + } + + return $classname; + } + /** * emit a behavior assign * * @param int $level * @param string $name * @param string $option + * @param class-string $classname * @return string assignation code */ - private function emitAssign($level, $name, $option) + private function emitAssign($level, $name, $option, $classname) { - // find class matching $name - $classname = $name; - if (class_exists("Doctrine_Template_$name", true)) { - $classname = "Doctrine_Template_$name"; - } return " \$" . strtolower($name) . "$level = new $classname($option);". PHP_EOL; } @@ -943,6 +961,7 @@ private function innerBuildActAs($actAs, $level = 0, $parent = null, array &$emi $currentParent = $parent; if (is_array($actAs)) { foreach($actAs as $template => $options) { + $className = $this->findTemplateClassMatchingName($template); if ($template == 'actAs') { // found another actAs $build .= $this->innerBuildActAs($options, $level + 1, $parent, $emittedActAs); @@ -959,7 +978,8 @@ private function innerBuildActAs($actAs, $level = 0, $parent = null, array &$emi } $optionPHP = $this->varExport($realOptions); - $build .= $this->emitAssign($level, $template, $optionPHP); + $build .= $this->emitAssign($level, $template, $optionPHP, $className); + $this->determineActAsColumns($className, $realOptions); if ($level == 0) { $emittedActAs[] = $this->emitActAs($level, $template); } else { @@ -969,7 +989,8 @@ private function innerBuildActAs($actAs, $level = 0, $parent = null, array &$emi $parent = $template; $build .= $this->innerBuildActAs($leftActAs, $level, $template, $emittedActAs); } else { - $build .= $this->emitAssign($level, $template, null); + $build .= $this->emitAssign($level, $template, null, $className); + $this->determineActAsColumns($className, array($options)); if ($level == 0) { $emittedActAs[] = $this->emitActAs($level, $template); } else { @@ -979,7 +1000,9 @@ private function innerBuildActAs($actAs, $level = 0, $parent = null, array &$emi } } } else { - $build .= $this->emitAssign($level, $actAs, null); + $className = $this->findTemplateClassMatchingName($actAs); + $build .= $this->emitAssign($level, $actAs, null, $className); + $this->determineActAsColumns($className, array()); if ($level == 0) { $emittedActAs[] = $this->emitActAs($level, $actAs); } else { @@ -990,6 +1013,70 @@ private function innerBuildActAs($actAs, $level = 0, $parent = null, array &$emi return $build; } + /** + * Adds the columns of the used actAs behaviors to the comment block. + * + * @param class-string $className + * @param array $instanceOptions + * + * @throws Doctrine_Import_Builder_Exception + */ + private function determineActAsColumns($className, $instanceOptions) + { + // No class specified or class does not exist. + if (!$className || !class_exists($className)) { + return; + } + + $actAsInstance = new $className($instanceOptions); + $options = $actAsInstance->getOptions(); + + // Some behaviors do not contain an array of columns, e.g. SoftDelete. + if (!is_array(reset($options))) { + $options = array($options); + } + + foreach ($options as $name => $column) { + if (!is_array($column) || !array_key_exists('name', $column) || !array_key_exists('type', $column)) { + // 'name' or 'type' not found. Unfortunately there is no logger. What is the best way to abort here? + continue; + } + + if (array_key_exists('disabled', $column) && $column['disabled']) { + // Column has been disabled. + continue; + } + + // Add field, if it does not exist already. + if (array_key_exists($name, $this->_actAsColumns)) { + continue; + } + + $this->_actAsColumns[$name] = $column; + } + } + + private function mergeDefinitionAndActAsColumns(array $definitionColumns, array $actAsColumns) + { + $result = $definitionColumns; + + foreach ($actAsColumns as $actAsOptionName => $actAsColumn) { + $actAsColumnName = isset($actAsColumn['name']) ? $actAsColumn['name'] : $actAsOptionName; + + foreach ($result as $optionName => $column) { + $name = isset($column['name']) ? $column['name'] : $optionName; + if ($name === $actAsColumnName) { + continue 2; + } + } + + $result[$actAsOptionName] = $actAsColumn; + } + + return $result; + } + + /** * Build php code for adding record listeners * @@ -1122,6 +1209,8 @@ public function buildDefinition(array $definition) $className = $definition['className']; $extends = isset($definition['inheritance']['extends']) ? $definition['inheritance']['extends']:$this->_baseClassName; + // Clear actAsColumns + $this->_actAsColumns = array(); if ( ! (isset($definition['no_definition']) && $definition['no_definition'] === true)) { $tableDefinitionCode = $this->buildTableDefinition($definition); $setUpCode = $this->buildSetUp($definition); @@ -1136,6 +1225,7 @@ public function buildDefinition(array $definition) $setUpCode.= $this->buildToString($definition); + $definition['columns'] = $this->mergeDefinitionAndActAsColumns($definition['columns'], $this->_actAsColumns); $docs = PHP_EOL . $this->buildPhpDocs($definition); $content = sprintf(self::$_tpl, $docs, $abstract, diff --git a/tests/Ticket/gh110TestCase.php b/tests/Ticket/gh110TestCase.php new file mode 100644 index 000000000..8eab1f5d3 --- /dev/null +++ b/tests/Ticket/gh110TestCase.php @@ -0,0 +1,76 @@ +buildDefinition( + array( + 'className' => 'Ticket_gh110_TestRecord', + 'topLevelClassName' => 'Ticket_gh110_TestRecord', + 'is_base_class' => true, + 'columns' => array( + 'id' => array( + 'type' => 'integer', + 'length' => 4, + ), + 'my_custom_created_at' => array( + 'name' => 'created_at', + 'type' => 'my_custom_type', + 'length' => '', + ) + ), + 'actAs' => array( + 'SoftDelete' => array(), + 'Timestampable' => array( + 'updated' => array( + 'disabled' => true, + ), + 'unknown_column' => array() + ), + 'UnknownActAs' => array(), + // This template brings an already defined column + 'gh110_Template' => array(), + ) + ) + ); + + $this->assertTrue(preg_match('/@property int\s*\$id/', $class)); + $this->assertTrue(preg_match('/@property string\s*\$deleted_at/', $class)); + $this->assertTrue(preg_match('/@property my_custom_type\s*\$created_at/', $class)); + $this->assertFalse(preg_match('/@property string\s*\$created_at/', $class)); + $this->assertFalse(preg_match('/@property string\s*\$updated_at/', $class)); + } +} + +class Doctrine_Template_gh110_Template extends Doctrine_Template +{ + protected $_options = array( + 'created' => array( + 'name' => 'created_at', + 'alias' => null, + 'type' => 'timestamp', + 'format' => 'Y-m-d H:i:s', + 'disabled' => false, + 'expression' => false, + 'options' => array('notnull' => true) + ) + ); + + /** + * Set table definition for Timestampable behavior + * + * @return void + */ + public function setTableDefinition() + { + if ( ! $this->_options['created']['disabled']) { + $name = $this->_options['created']['name']; + if ($this->_options['created']['alias']) { + $name .= ' as ' . $this->_options['created']['alias']; + } + $this->hasColumn($name, $this->_options['created']['type'], null, $this->_options['created']['options']); + } + } +}