Skip to content

Commit

Permalink
Merge pull request propelorm#131 from jaugustin/fix-BC-for-many-to-ma…
Browse files Browse the repository at this point in the history
…ny-without-is-cross-ref

fix issue propelorm#129 and propelorm#130 with BC break of generated form for M2M without isCrossRef parameters
  • Loading branch information
willdurand committed Apr 10, 2012
2 parents 478e721 + dd8c08f commit 28dfbde
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public function updateDefaultsFromObject()
if (isset($this->widgetSchema['<?php echo $this->underscore($tables['middleTable']->getClassname()) ?>_list']))
{
$values = array();
foreach ($this->object->get<?php echo $tables['middleTable']->getPhpName() ?>s() as $obj)
foreach ($this->object-><?php echo $tables['relatedGetter'] ?>() as $obj)
{
$values[] = $obj->get<?php echo $tables['relatedColumn']->getPhpName() ?>();
}
Expand Down
67 changes: 54 additions & 13 deletions lib/generator/sfPropelFormGenerator.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ public function generate($params = array())
public function getManyToManyTables()
{
$tables = array();
$middleTables = array();
$foreignTables = array();
$relations = array();

Expand All @@ -133,7 +134,7 @@ public function getManyToManyTables()
//we have a many to many Relation
if (RelationMap::MANY_TO_MANY === $relation->getType())
{
$foreignTables[$relation->getLocalTable()->getClassname()] = $relation->getLocalTable();
$foreignTables[$relation->getLocalTable()->getClassname()] = $relation;
}
else if (RelationMap::ONE_TO_MANY === $relation->getType())
{
Expand All @@ -142,30 +143,70 @@ public function getManyToManyTables()
}

// find middleTable for Many to Many relation
foreach ($foreignTables as $tableName => $foreignTable)
foreach ($foreignTables as $tableName => $relation)
{
$foreignTable = $relation->getLocalTable();
foreach ($foreignTable->getRelations() as $foreignRelation)
{
$foreignTableName = $foreignRelation->getLocalTable()->getClassname();
$foreignTableClassname = $foreignRelation->getLocalTable()->getClassname();

// Test if the foreign table has a common relation with our table
// TODO: test if is CrossRef
if (RelationMap::ONE_TO_MANY === $foreignRelation->getType()
&& array_key_exists($foreignTableName, $relations))
&& array_key_exists($foreignTableClassname, $relations))
{
$columns = $relations[$foreignTableName]->getLocalColumns();
$columns = $relations[$foreignTableClassname]->getLocalColumns();
$relatedColumns = $foreignRelation->getLocalColumns();

$tables[] = array(
'middleTable' => $foreignRelation->getLocalTable(),
'relatedTable' => $foreignTable,
'column' => reset($columns),
'relatedColumn' => reset($relatedColumns),
);
continue 2;
$middleTable = $foreignRelation->getLocalTable();
if ($middleTable->isCrossRef() && !isset($middleTables[$middleTable->getClassname()]))
{
// Add this middleTable to table list to prevent using it twice
$middleTables[$middleTable->getClassname()] = $middleTable;
$tables[] = array(
'middleTable' => $middleTable,
'relatedGetter' => $foreignTable->getPhpname() == $relation->getName() ? 'get' . $middleTable->getPhpname() . 's' : 'get' . $relation->getPluralName(),
'relatedTable' => $foreignTable,
'column' => reset($columns),
'relatedColumn' => reset($relatedColumns),
);
continue 2;
}
}
}
}

//Keep BC with M2M without isCrossRef = true
foreach ($relations as $relation)
{
$middleTable = $relation->getLocalTable();
//check if $middleTable is a Many 2 Many :
// exclude already found middle table
// if there it has 2 PKs
// if there id only 2 columns in the table
// if PKs are also FKs
if (!isset($middleTables[$middleTable->getClassname()])
&& 2 === count($pks = $middleTable->getPrimaryKeyColumns())
&& 2 === count($middleTable->getColumns())
&& $pks[0]->isForeignKey()
&& $pks[1]->isForeignKey())
{
//We found a Many to Many middle table
$foreignTable = $pks[0]->getRelatedTableName() != $this->table->getName() ? $pks[0]->getRelatedTable() : $pks[1]->getRelatedTable();
$relatedColumn = $pks[0]->getRelatedTableName() != $this->table->getName() ? $pks[0] : $pks[1];
$columns = $relation->getLocalColumns();
// Add this middleTable to table list to prevent using it twice
$middleTables[$middleTable->getClassname()] = $middleTable;

$tables[] = array(
'middleTable' => $middleTable,
'relatedGetter' => $middleTable->getPhpname() == $relation->getName() ? 'get' . $middleTable->getPhpname() . 's' : 'get' . $relation->getPluralName(),
'relatedTable' => $foreignTable,
'column' => reset($columns),
'relatedColumn' => $relatedColumn,
);
}
}

return $tables;
}

Expand Down
68 changes: 54 additions & 14 deletions lib/generator/sfPropelGenerator.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,18 @@ public function getTableMap()
public function getManyToManyTables()
{
$tables = array();
$middleTables = array();
$foreignTables = array();
$relations = array();

// go through all relations
foreach ($this->getTableMap()->getRelations() as $relation)
$table = $this->getTableMap();
foreach ($table->getRelations() as $relation)
{
//we have a many to many Relation
if (RelationMap::MANY_TO_MANY === $relation->getType())
{
$foreignTables[$relation->getLocalTable()->getClassname()] = $relation->getLocalTable();
$foreignTables[$relation->getLocalTable()->getClassname()] = $relation;
}
else if (RelationMap::ONE_TO_MANY === $relation->getType())
{
Expand All @@ -84,30 +86,68 @@ public function getManyToManyTables()
}

// find middleTable for Many to Many relation
foreach ($foreignTables as $tableName => $foreignTable)
foreach ($foreignTables as $tableName => $relation)
{
$foreignTable = $relation->getLocalTable();
foreach ($foreignTable->getRelations() as $foreignRelation)
{
$foreignTableName = $foreignRelation->getLocalTable()->getClassname();
$foreignTableClassname = $foreignRelation->getLocalTable()->getClassname();

// Test if the foreign table has a common relation with our table
// TODO: test if is CrossRef
if (RelationMap::ONE_TO_MANY === $foreignRelation->getType()
&& array_key_exists($foreignTableName, $relations))
&& array_key_exists($foreignTableClassname, $relations))
{
$columns = $relations[$foreignTableName]->getLocalColumns();
$columns = $relations[$foreignTableClassname]->getLocalColumns();
$relatedColumns = $foreignRelation->getLocalColumns();

$tables[] = array(
'middleTable' => $foreignRelation->getLocalTable(),
'relatedTable' => $foreignTable,
'column' => reset($columns),
'relatedColumn' => reset($relatedColumns),
);
continue 2;
$middleTable = $foreignRelation->getLocalTable();
if ($middleTable->isCrossRef() && !isset($middleTables[$middleTable->getClassname()]))
{
// Add this middleTable to table list to prevent using it twice
$middleTables[$middleTable->getClassname()] = $middleTable;
$tables[] = array(
'middleTable' => $middleTable,
'relatedTable' => $foreignTable,
'column' => reset($columns),
'relatedColumn' => reset($relatedColumns),
);
continue 2;
}
}
}
}

//Keep BC with M2M without isCrossRef = true
foreach ($relations as $relation)
{
$middleTable = $relation->getLocalTable();
//check if $middleTable is a Many 2 Many :
// exclude already found middle table
// if there it has 2 PKs
// if there id only 2 columns in the table
// if PKs are also FKs
if (!isset($middleTables[$middleTable->getClassname()])
&& 2 === count($pks = $middleTable->getPrimaryKeyColumns())
&& 2 === count($middleTable->getColumns())
&& $pks[0]->isForeignKey()
&& $pks[1]->isForeignKey())
{
//We found a Many to Many middle table
$foreignTable = $pks[0]->getRelatedTableName() != $table->getName() ? $pks[0]->getRelatedTable() : $pks[1]->getRelatedTable();
$relatedColumn = $pks[0]->getRelatedTableName() != $table->getName() ? $pks[0] : $pks[1];
$columns = $relation->getLocalColumns();
// Add this middleTable to table list to prevent using it twice
$middleTables[$middleTable->getClassname()] = $middleTable;

$tables[] = array(
'middleTable' => $middleTable,
'relatedTable' => $foreignTable,
'column' => reset($columns),
'relatedColumn' => $relatedColumn,
);
}
}

return $tables;
}

Expand Down
78 changes: 76 additions & 2 deletions test/functional/fixtures/config/schema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<column name="hobbies" type="array" required="false" />
</table>

<table name="author_article" isCrossref="true">
<table name="author_article">
<behavior name="symfony">
<parameter name="form" value="false"/>
<parameter name="filter" value="false"/>
Expand Down Expand Up @@ -151,11 +151,12 @@
<column name="enum_values" type="enum" valueSet="one, two, three space" />
</table>

<!-- model and data for testing many to many -->
<!-- model and data for testing many to many without isCrossRef -->
<table name="seller">
<column name="id" type="integer" required="true" primaryKey="true" autoincrement="true" />
<column name="name" type="varchar" size="255" />
</table>

<table name="sale">
<column name="seller_id" type="integer" primaryKey="true" />
<column name="book_id" type="integer" primaryKey="true" />
Expand All @@ -167,4 +168,77 @@
<reference local="book_id" foreign="id" />
</foreign-key>
</table>

<table name="extra_seller" isCrossRef="true">
<column name="seller_id" type="integer" primaryKey="true" />
<column name="extra_id" type="integer" primaryKey="true" />
<column name="comment" type="varchar" size="255" required="false" />
<foreign-key foreignTable="seller">
<reference local="seller_id" foreign="id" />
</foreign-key>
<foreign-key foreignTable="extra">
<reference local="extra_id" foreign="id" />
</foreign-key>
</table>

<table name="extra_seller_without_crossref">
<column name="seller_id" type="integer" primaryKey="true" />
<column name="extra_id" type="integer" primaryKey="true" />
<foreign-key foreignTable="seller">
<reference local="seller_id" foreign="id" />
</foreign-key>
<foreign-key foreignTable="extra">
<reference local="extra_id" foreign="id" />
</foreign-key>
</table>

<table name="extra_seller_without_crossref2">
<column name="seller_id" type="integer" primaryKey="true" />
<column name="extra_id" type="integer" primaryKey="true" />
<foreign-key foreignTable="seller">
<reference local="seller_id" foreign="id" />
</foreign-key>
<foreign-key foreignTable="extra">
<reference local="extra_id" foreign="id" />
</foreign-key>
</table>

<table name="book_seller_without_crossref">
<column name="seller_id" type="integer" primaryKey="true" />
<column name="book_id" type="integer" primaryKey="true" />
<foreign-key foreignTable="seller">
<reference local="seller_id" foreign="id" />
</foreign-key>
<foreign-key foreignTable="book">
<reference local="book_id" foreign="id" />
</foreign-key>
</table>

<table name="seller_seller" isCrossRef="true">
<column name="seller_id" type="integer" primaryKey="true" />
<column name="seller_cross_id" type="integer" primaryKey="true" />
<foreign-key foreignTable="seller">
<reference local="seller_id" foreign="id" />
</foreign-key>
<foreign-key foreignTable="seller">
<reference local="seller_cross_id" foreign="id" />
</foreign-key>
</table>

<table name="seller_seller2">
<column name="seller_id" type="integer" primaryKey="true" />
<column name="seller_cross_id" type="integer" primaryKey="true" />
<foreign-key foreignTable="seller">
<reference local="seller_id" foreign="id" />
</foreign-key>
<foreign-key foreignTable="seller">
<reference local="seller_cross_id" foreign="id" />
</foreign-key>
</table>

<table name="extra">
<column name="id" type="integer" required="true" primaryKey="true" autoincrement="true" />
<column name="name" type="varchar" size="255" />
<column name="useless" type="varchar" size="255" />
</table>
</database>
3 changes: 3 additions & 0 deletions test/functional/formTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,6 @@
} catch (InvalidArgumentException $e) {
$b->test()->pass('The book form shoud not has sale_list field because it is not a many to many relation');
}

$form = new SellerForm();
$b->test()->isa_ok($form->getWidget('extra_seller_list'), 'sfWidgetFormPropelChoice', 'The Seller form should have a sfWidgetFormPropelChoice of extras when there is a isCrossRef on a middle table');

0 comments on commit 28dfbde

Please sign in to comment.