Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding columns defined in actAs-templates to the docblock of the generated model class #110

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 130 additions & 12 deletions lib/Doctrine/Import/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,25 @@ class Doctrine_Import_Builder extends Doctrine_Builder
*/
protected $_phpDocEmail = '##EMAIL##';


/**
* Contains the actAs columns after running buildSetUp
*
* @var array<string, array{
* name: string,
* type: string,
* disabled?: bool,
* alias?: string,
* length?: int,
* unique?: bool,
* fixed?: bool,
* primary?: bool,
* notblank?: bool,
* default?: mixed,
* }>
*/
private $actAsColumns = array();

/**
* _tpl
*
Expand Down Expand Up @@ -396,9 +415,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)
Expand Down Expand Up @@ -857,21 +874,33 @@ public function buildPhpDocs(array $definition)
return $ret;
}

/**
* find class matching $name
*
* @param $name
* @return class-string<Doctrine_Template>
*/
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;
}

Expand Down Expand Up @@ -943,6 +972,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);
Expand All @@ -959,7 +989,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 {
Expand All @@ -969,7 +1000,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 {
Expand All @@ -979,7 +1011,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 {
Expand All @@ -990,6 +1024,87 @@ 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;
}

// PHP >= 7.4 is planned as a minimum version for the upcoming release of doctrine1,
// therefore we simply skip the generation of actAs columns if run below 7.0, as
// instantiation exceptions are not supported before PHP 7
if (PHP_VERSION_ID <= 70000) {
return;
}

try {
$actAsInstance = new $className($instanceOptions);
} catch (Error $e) {
// The class can't be instantiated, skipping it
return;
}

if (!$actAsInstance || !method_exists($actAsInstance, 'getOptions')) {
return;
}

$options = $actAsInstance->getOptions();
thirsch marked this conversation as resolved.
Show resolved Hide resolved

// 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.
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
*
Expand Down Expand Up @@ -1122,6 +1237,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);
Expand All @@ -1136,6 +1253,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,
Expand Down
66 changes: 66 additions & 0 deletions tests/Ticket/gh110/Ticket_gh110_TestRecord.snapshot
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* Ticket_gh110_TestRecord
*
* This class has been auto-generated by the Doctrine ORM Framework
*
* @property int $id Type: integer(4)
* @property my_custom_type $created_at Type: my_custom_type
* @property string $deleted_at Type: timestamp, Timestamp in ISO-8601 format (YYYY-MM-DD HH:MI:SS)
*
* @method int getId() Type: integer(4)
* @method my_custom_type getCreatedAt() Type: my_custom_type
* @method string getDeletedAt() Type: timestamp, Timestamp in ISO-8601 format (YYYY-MM-DD HH:MI:SS)
*
* @method Ticket_gh110_TestRecord setId(int $val) Type: integer(4)
* @method Ticket_gh110_TestRecord setCreatedAt(my_custom_type $val) Type: my_custom_type
* @method Ticket_gh110_TestRecord setDeletedAt(string $val) Type: timestamp, Timestamp in ISO-8601 format (YYYY-MM-DD HH:MI:SS)
*
* @package ##PACKAGE##
* @subpackage ##SUBPACKAGE##
* @author ##NAME## <##EMAIL##>
* @version SVN: $Id: Builder.php 7490 2010-03-29 19:53:27Z jwage $
*/
class Ticket_gh110_TestRecord extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('id', 'integer', 4, array(
'type' => 'integer',
'length' => 4,
));
$this->hasColumn('created_at', 'my_custom_type', null, array(
'type' => 'my_custom_type',
'length' => '',
));
}

public function setUp()
{
parent::setUp();
$softdelete0 = new Doctrine_Template_SoftDelete(array(
));
$timestampable0 = new Doctrine_Template_Timestampable(array(
'updated' =>
array(
'disabled' => true,
),
'unknown_column' =>
array(
),
));
$unknownactas0 = new UnknownActAs(array(
));
$gh110_template0 = new Doctrine_Template_gh110_Template(array(
));
$gh110_invalid_template0 = new gh110_Invalid_Template(array(
));
$gh110_abstract_template0 = new gh110_Abstract_Template(array(
));
$this->actAs($softdelete0);
$this->actAs($timestampable0);
$this->actAs($unknownactas0);
$this->actAs($gh110_template0);
$this->actAs($gh110_invalid_template0);
$this->actAs($gh110_abstract_template0);
}
}
81 changes: 81 additions & 0 deletions tests/Ticket/gh110TestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

class Doctrine_Ticket_gh110_TestCase extends Doctrine_UnitTestCase
{
public function testAddActAsColumnsToDocBlock()
{
$builder = new Doctrine_Import_Builder();
$class = $builder->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(),
'gh110_Invalid_Template' => array(),
'gh110_Abstract_Template' => array(),
)
)
);

// Can be used to update the snapshot.
//file_put_contents(dirname(__FILE__) . '/gh110/Ticket_gh110_TestRecord.snapshot', $class);
$this->assertEqual($class, file_get_contents(dirname(__FILE__) . '/gh110/Ticket_gh110_TestRecord.snapshot'));
}
}

abstract class gh110_Abstract_Template {}

/** This is just a simple class without the required getOptions()-Method */
class gh110_Invalid_Template {}

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']);
}
}
}
Loading