diff --git a/lib/Doctrine/Import/Builder.php b/lib/Doctrine/Import/Builder.php index 7dc64d4ab..856d0419f 100644 --- a/lib/Doctrine/Import/Builder.php +++ b/lib/Doctrine/Import/Builder.php @@ -396,12 +396,11 @@ public function buildTableDefinition(array $definition) /** * buildSetUp * - * @param array $options - * @param array $columns - * @param array $relations + * @param array $definition + * @param array $actAsColumns * @return string */ - public function buildSetUp(array $definition) + public function buildSetUp(array $definition, array &$actAsColumns = array()) { $ret = array(); $i = 0; @@ -483,7 +482,7 @@ public function buildSetUp(array $definition) } if (isset($definition['actAs']) && is_array($definition['actAs']) && !empty($definition['actAs'])) { - $ret[$i] = $this->buildActAs($definition['actAs']); + $ret[$i] = $this->buildActAs($definition['actAs'], $actAsColumns); $i++; } @@ -640,8 +639,9 @@ public function buildAccessors(array $definition) * Build the phpDoc for a class definition * * @param array $definition + * @param array $actAsColumns */ - public function buildPhpDocs(array $definition) + public function buildPhpDocs(array $definition, array $actAsColumns = array()) { $ret = array(); $ret[] = $definition['className']; @@ -654,7 +654,7 @@ public function buildPhpDocs(array $definition) $setters = array(); if ((isset($definition['is_base_class']) && $definition['is_base_class']) || ! $this->generateBaseClasses()) { - foreach ($definition['columns'] as $name => $column) { + foreach (array_merge($definition['columns'], $actAsColumns) as $name => $column) { $name = isset($column['name']) ? $column['name']:$name; // extract column name & field name if (stripos($name, ' as ')) @@ -865,7 +865,7 @@ public function buildPhpDocs(array $definition) * @param string $option * @return string assignation code */ - private function emitAssign($level, $name, $option) + private function emitAssign($level, $name, $option, &$classname) { // find class matching $name $classname = $name; @@ -904,11 +904,12 @@ private function emitActAs($level, $name) /** * buildActAs: builds a complete actAs code. It supports hierarchy of plugins * @param array $actAs array of plugin definitions and options + * @param array $actAsColumns contains on output an array of columns defined by actAs behaviors */ - public function buildActAs($actAs) + public function buildActAs($actAs, array &$actAsColumns = array()) { $emittedActAs = array(); - $build = $this->innerBuildActAs($actAs, 0, null, $emittedActAs); + $build = $this->innerBuildActAs($actAs, 0, null, $emittedActAs, $actAsColumns); foreach($emittedActAs as $str) { $build .= $str; } @@ -922,9 +923,10 @@ public function buildActAs($actAs) * @param int $level current indentation level * @param string $parent name of the parent template/plugin * @param array $emittedActAs contains on output an array of actAs command to be appended to output + * @param array $actAsColumns contains on output an array of columns defined by actAs behaviors * @return string actAs full definition */ - private function innerBuildActAs($actAs, $level = 0, $parent = null, array &$emittedActAs = array()) + private function innerBuildActAs($actAs, $level = 0, $parent = null, array &$emittedActAs = array(), array &$actAsColumns = array()) { // rewrite special case of actAs: [Behavior] which gave [0] => Behavior if (is_array($actAs) && isset($actAs[0]) && !is_array($actAs[0])) { @@ -945,7 +947,7 @@ private function innerBuildActAs($actAs, $level = 0, $parent = null, array &$emi foreach($actAs as $template => $options) { if ($template == 'actAs') { // found another actAs - $build .= $this->innerBuildActAs($options, $level + 1, $parent, $emittedActAs); + $build .= $this->innerBuildActAs($options, $level + 1, $parent, $emittedActAs, $actAsColumns); } else if (is_array($options)) { // remove actAs from options $realOptions = array(); @@ -959,7 +961,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->addActAsColumnsToDefinition($className, $realOptions, $actAsColumns); if ($level == 0) { $emittedActAs[] = $this->emitActAs($level, $template); } else { @@ -967,9 +970,10 @@ private function innerBuildActAs($actAs, $level = 0, $parent = null, array &$emi } // descend for the remainings actAs $parent = $template; - $build .= $this->innerBuildActAs($leftActAs, $level, $template, $emittedActAs); + $build .= $this->innerBuildActAs($leftActAs, $level, $template, $emittedActAs, $actAsColumns); } else { - $build .= $this->emitAssign($level, $template, null); + $build .= $this->emitAssign($level, $template, null, $className); + $this->addActAsColumnsToDefinition($className, array($options), $actAsColumns); if ($level == 0) { $emittedActAs[] = $this->emitActAs($level, $template); } else { @@ -979,7 +983,8 @@ private function innerBuildActAs($actAs, $level = 0, $parent = null, array &$emi } } } else { - $build .= $this->emitAssign($level, $actAs, null); + $build .= $this->emitAssign($level, $actAs, null, $className); + $this->addActAsColumnsToDefinition($className, array(), $actAsColumns); if ($level == 0) { $emittedActAs[] = $this->emitActAs($level, $actAs); } else { @@ -990,6 +995,56 @@ 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 string $className + * @param array $instanceOptions + * @param array $actAsColumns + * + * @throws Doctrine_Import_Builder_Exception + */ + private function addActAsColumnsToDefinition($className, $instanceOptions, &$actAsColumns) + { + // No class specified or class does not exist. + if (!$className || !class_exists($className)) { + return; + } + + $actAsInstance = new $className($instanceOptions); + $options = $actAsInstance->getOptions(); + + if (count($options) == 0) { + return; + } + + // Some behaviors do not contain an array of columns, e.g. SoftDelete. + if (!is_array(reset($options))) { + $options = [$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, $actAsColumns) + && !array_key_exists($column['name'], $actAsColumns) + ) { + $actAsColumns[$name] = $column; + } + } + } + + /** * Build php code for adding record listeners * @@ -1122,9 +1177,10 @@ public function buildDefinition(array $definition) $className = $definition['className']; $extends = isset($definition['inheritance']['extends']) ? $definition['inheritance']['extends']:$this->_baseClassName; + $actAsColumns = array(); if ( ! (isset($definition['no_definition']) && $definition['no_definition'] === true)) { $tableDefinitionCode = $this->buildTableDefinition($definition); - $setUpCode = $this->buildSetUp($definition); + $setUpCode = $this->buildSetUp($definition, $actAsColumns); } else { $tableDefinitionCode = null; $setUpCode = null; @@ -1136,7 +1192,7 @@ public function buildDefinition(array $definition) $setUpCode.= $this->buildToString($definition); - $docs = PHP_EOL . $this->buildPhpDocs($definition); + $docs = PHP_EOL . $this->buildPhpDocs($definition, $actAsColumns); $content = sprintf(self::$_tpl, $docs, $abstract, $className, diff --git a/tests/Ticket/gh110TestCase.php b/tests/Ticket/gh110TestCase.php new file mode 100644 index 000000000..baee0161c --- /dev/null +++ b/tests/Ticket/gh110TestCase.php @@ -0,0 +1,31 @@ +buildDefinition( + array( + 'className' => 'Ticket_gh110_TestRecord', + 'topLevelClassName' => 'Ticket_gh110_TestRecord', + 'is_base_class' => true, + 'columns' => array( + 'id' => array( + 'type' => 'integer', + 'length' => 4, + ) + ), + 'actAs' => array( + 'SoftDelete' => array(), + 'Timestampable' => 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 string\s*\$created_at/', $class)); + $this->assertTrue(preg_match('/@property string\s*\$updated_at/', $class)); + } +}