mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
ORM Column now supports related table lookup
This commit is contained in:
parent
26e3b6f4e3
commit
99786dda22
@ -501,6 +501,16 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li
|
||||
* Unlike getRelationName, this is immutable and will fallback to the quoted field
|
||||
* name if not a relation.
|
||||
*
|
||||
* Example use (simple WHERE condition on data sitting in a related table):
|
||||
*
|
||||
* <code>
|
||||
* $columnName = null;
|
||||
* $list = Page::get()
|
||||
* ->applyRelation('TaxonomyTerms.ID', $columnName)
|
||||
* ->where([$columnName => 'my value']);
|
||||
* </code>
|
||||
*
|
||||
*
|
||||
* @param string $field Name of field or relation to apply
|
||||
* @param string &$columnName Quoted column name
|
||||
* @param bool $linearOnly Set to true to restrict to linear relations only. Set this
|
||||
|
@ -4,6 +4,7 @@ namespace SilverStripe\ORM;
|
||||
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Core\Extensible;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\ORM\Connect\Query;
|
||||
use SilverStripe\ORM\Queries\SQLConditionGroup;
|
||||
@ -21,6 +22,8 @@ use InvalidArgumentException;
|
||||
class DataQuery
|
||||
{
|
||||
|
||||
use Extensible;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
@ -930,7 +933,7 @@ class DataQuery
|
||||
$joinExpression = "{$foreignKeyIDColumn} = {$localIDColumn}";
|
||||
}
|
||||
$this->query->addLeftJoin(
|
||||
$foreignTable,
|
||||
$this->augmentTable($foreignClass, $foreignTable),
|
||||
$joinExpression,
|
||||
$foreignTableAliased
|
||||
);
|
||||
@ -944,7 +947,7 @@ class DataQuery
|
||||
if ($ancestorTable !== $foreignTable) {
|
||||
$ancestorTableAliased = $foreignPrefix . $ancestorTable;
|
||||
$this->query->addLeftJoin(
|
||||
$ancestorTable,
|
||||
$this->augmentTable($ancestor, $ancestorTable),
|
||||
"\"{$foreignTableAliased}\".\"ID\" = \"{$ancestorTableAliased}\".\"ID\"",
|
||||
$ancestorTableAliased
|
||||
);
|
||||
@ -990,7 +993,7 @@ class DataQuery
|
||||
$foreignIDColumn = $schema->sqlColumnForField($foreignBaseClass, 'ID', $foreignPrefix);
|
||||
$localColumn = $schema->sqlColumnForField($localClass, "{$localField}ID", $localPrefix);
|
||||
$this->query->addLeftJoin(
|
||||
$foreignBaseTable,
|
||||
$this->augmentTable($foreignClass, $foreignBaseTable),
|
||||
"{$foreignIDColumn} = {$localColumn}",
|
||||
$foreignPrefix . $foreignBaseTable
|
||||
);
|
||||
@ -1005,7 +1008,7 @@ class DataQuery
|
||||
if ($ancestorTable !== $foreignBaseTable) {
|
||||
$ancestorTableAliased = $foreignPrefix . $ancestorTable;
|
||||
$this->query->addLeftJoin(
|
||||
$ancestorTable,
|
||||
$this->augmentTable($ancestor, $ancestorTable),
|
||||
"{$foreignIDColumn} = \"{$ancestorTableAliased}\".\"ID\"",
|
||||
$ancestorTableAliased
|
||||
);
|
||||
@ -1039,7 +1042,13 @@ class DataQuery
|
||||
$schema = DataObject::getSchema();
|
||||
|
||||
if (class_exists($relationClassOrTable)) {
|
||||
$relationClassOrTable = $schema->tableName($relationClassOrTable);
|
||||
// class is provided
|
||||
$relationTable = $schema->tableName($relationClassOrTable);
|
||||
$relationTableAugmented = $this->augmentTable($relationClassOrTable, $relationTable);
|
||||
} else {
|
||||
// table is provided
|
||||
$relationTable = $relationClassOrTable;
|
||||
$relationTableAugmented = $relationClassOrTable;
|
||||
}
|
||||
|
||||
// Check if already joined to component alias (skip join table for the check)
|
||||
@ -1051,10 +1060,10 @@ class DataQuery
|
||||
}
|
||||
|
||||
// Join parent class to join table
|
||||
$relationAliasedTable = $componentPrefix . $relationClassOrTable;
|
||||
$relationAliasedTable = $componentPrefix . $relationTable;
|
||||
$parentIDColumn = $schema->sqlColumnForField($parentClass, 'ID', $parentPrefix);
|
||||
$this->query->addLeftJoin(
|
||||
$relationClassOrTable,
|
||||
$relationTableAugmented,
|
||||
"\"{$relationAliasedTable}\".\"{$parentField}\" = {$parentIDColumn}",
|
||||
$relationAliasedTable
|
||||
);
|
||||
@ -1062,7 +1071,7 @@ class DataQuery
|
||||
// Join on base table of component class
|
||||
$componentIDColumn = $schema->sqlColumnForField($componentBaseClass, 'ID', $componentPrefix);
|
||||
$this->query->addLeftJoin(
|
||||
$componentBaseTable,
|
||||
$this->augmentTable($componentBaseClass, $componentBaseTable),
|
||||
"\"{$relationAliasedTable}\".\"{$componentField}\" = {$componentIDColumn}",
|
||||
$componentAliasedTable
|
||||
);
|
||||
@ -1076,7 +1085,7 @@ class DataQuery
|
||||
if ($ancestorTable !== $componentBaseTable) {
|
||||
$ancestorTableAliased = $componentPrefix . $ancestorTable;
|
||||
$this->query->addLeftJoin(
|
||||
$ancestorTable,
|
||||
$this->augmentTable($ancestor, $ancestorTable),
|
||||
"{$componentIDColumn} = \"{$ancestorTableAliased}\".\"ID\"",
|
||||
$ancestorTableAliased
|
||||
);
|
||||
@ -1084,6 +1093,22 @@ class DataQuery
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this extension point to alter the table name
|
||||
* useful for versioning for example
|
||||
*
|
||||
* @param $class
|
||||
* @param $table
|
||||
* @return mixed
|
||||
*/
|
||||
protected function augmentTable($class, $table)
|
||||
{
|
||||
$augmented = $table;
|
||||
$this->invokeWithExtensions('augmentJoinTable', $class, $table, $augmented);
|
||||
|
||||
return $augmented;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the result of query from this query.
|
||||
*
|
||||
@ -1142,16 +1167,47 @@ class DataQuery
|
||||
|
||||
/**
|
||||
* Query the given field column from the database and return as an array.
|
||||
* querying DB columns of related tables is supported but you need to make sure that the related table
|
||||
* is already available in join
|
||||
*
|
||||
* @see DataList::applyRelation()
|
||||
*
|
||||
* example use:
|
||||
*
|
||||
* <code>
|
||||
* column("MyTable"."Title")
|
||||
*
|
||||
* or
|
||||
*
|
||||
* $columnName = null;
|
||||
* Category::get()
|
||||
* ->applyRelation('Products.Title', $columnName)
|
||||
* ->column($columnName);
|
||||
* </code>
|
||||
*
|
||||
* @param string $field See {@link expressionForField()}.
|
||||
* @return array List of column values for the specified column
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function column($field = 'ID')
|
||||
{
|
||||
$fieldExpression = $this->expressionForField($field);
|
||||
$query = $this->getFinalisedQuery(array($field));
|
||||
$query = $this->getFinalisedQuery([$field]);
|
||||
$originalSelect = $query->getSelect();
|
||||
$query->setSelect(array());
|
||||
$query->setSelect([]);
|
||||
|
||||
// field wasn't recognised as a valid field from the table class hierarchy
|
||||
// check if the field is in format "<table_name>"."<column_name>"
|
||||
// if that's the case we may want to query related table
|
||||
if (!$fieldExpression) {
|
||||
if (!$this->validateColumnField($field, $query)) {
|
||||
throw new InvalidArgumentException('Invalid column name ' . $field);
|
||||
}
|
||||
|
||||
$fieldExpression = $field;
|
||||
$field = null;
|
||||
}
|
||||
|
||||
$query->selectField($fieldExpression, $field);
|
||||
$this->ensureSelectContainsOrderbyColumns($query, $originalSelect);
|
||||
|
||||
@ -1257,4 +1313,22 @@ class DataQuery
|
||||
$this->dataQueryManipulators[] = $manipulator;
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function validateColumnField($field, SQLSelect $query)
|
||||
{
|
||||
// standard column - nothing to process here
|
||||
if (strpos($field, '.') === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fieldData = explode('.', $field);
|
||||
$tablePrefix = str_replace('"', '', $fieldData[0]);
|
||||
|
||||
// check if related table is available
|
||||
if (!$query->isJoinedTo($tablePrefix)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -4,24 +4,24 @@ namespace SilverStripe\ORM\Tests;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Core\Injector\InjectorNotFoundException;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\ORM\DataList;
|
||||
use SilverStripe\ORM\DataQuery;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\ORM\Filterable;
|
||||
use SilverStripe\ORM\Filters\ExactMatchFilter;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\ORM\Tests\DataObjectTest\Fixture;
|
||||
use SilverStripe\ORM\Tests\DataObjectTest\Bracket;
|
||||
use SilverStripe\ORM\Tests\DataObjectTest\EquipmentCompany;
|
||||
use SilverStripe\ORM\Tests\DataObjectTest\Fan;
|
||||
use SilverStripe\ORM\Tests\DataObjectTest\Fixture;
|
||||
use SilverStripe\ORM\Tests\DataObjectTest\Player;
|
||||
use SilverStripe\ORM\Tests\DataObjectTest\Sortable;
|
||||
use SilverStripe\ORM\Tests\DataObjectTest\Staff;
|
||||
use SilverStripe\ORM\Tests\DataObjectTest\SubTeam;
|
||||
use SilverStripe\ORM\Tests\DataObjectTest\Team;
|
||||
use SilverStripe\ORM\Tests\DataObjectTest\TeamComment;
|
||||
use SilverStripe\ORM\Tests\DataObjectTest\ValidatedObject;
|
||||
use SilverStripe\ORM\Tests\DataObjectTest\Staff;
|
||||
use SilverStripe\ORM\Tests\ManyManyListTest\Category;
|
||||
|
||||
/**
|
||||
* @skipUpgrade
|
||||
@ -1835,4 +1835,37 @@ class DataListTest extends SapphireTest
|
||||
$list->column("Title")
|
||||
);
|
||||
}
|
||||
|
||||
public function testColumnFailureInvalidColumn()
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
|
||||
Category::get()->column('ObviouslyInvalidColumn');
|
||||
}
|
||||
|
||||
public function testColumnFailureInvalidTable()
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
|
||||
$columnName = null;
|
||||
Category::get()
|
||||
->applyRelation('Products.ID', $columnName)
|
||||
->column('"ObviouslyInvalidTable"."ID"');
|
||||
}
|
||||
|
||||
public function testColumnFromRelatedTable()
|
||||
{
|
||||
$columnName = null;
|
||||
$productTitles = Category::get()
|
||||
->applyRelation('Products.Title', $columnName)
|
||||
->column($columnName);
|
||||
|
||||
$productTitles = array_diff($productTitles, [null]);
|
||||
sort($productTitles);
|
||||
|
||||
$this->assertEquals([
|
||||
'Product A',
|
||||
'Product B',
|
||||
], $productTitles);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user