问题
I have an Object that extends Page ("Thing") which has a many_many relationship with a DataObject ("Tag").
class Thing extends Page
{
static $many_many = array(
'Tags' => 'Tag'
);
}
class Tag extends DataObject
{
static $belongs_many_many = array(
'Things' => 'Thing'
);
}
I have an array of Tag IDs and I want to get a list of Things that have all of these tags attached.
The following should be possible...
$tag_ids = array(1,2,3,4);
$things = Thing::get();
$things->filter('Tags.ID', array($tag_ids));
...but this just returns an unfiltered list. Apparently this hasn't been implemented for relationships yet. So how can I do it?
回答1:
For performance reasons you would probably be better off relying as much as possible on a sql query.
$tag_ids = array(1,2,3);
$objects = new ArrayList();
if (count($tag_ids)) {
$sql = "SELECT \"SiteTree\".*, \"Page\".*, \"Thing\".*,count(\"ThingID\") AS ThingIDCount FROM \"SiteTree\" ";
$sql.= "LEFT JOIN \"Page\" ON \"Page\".\"ID\" = \"SiteTree\".\"ID\" ";
$sql.= "LEFT JOIN \"Thing\" ON \"Thing\".\"ID\" = \"SiteTree\".\"ID\" ";
$sql.= "LEFT JOIN \"Thing_Tags\" ON \"Thing_Tags\".\"ThingID\" = \"SiteTree\".\"ID\" ";
$sql.= "LEFT JOIN \"Tag\" ON \"Thing_Tags\".\"TagID\" = \"Tag\".\"ID\" ";
$sql.= "WHERE \"TagID\" IN (" . implode(',', $tag_ids) . ") GROUP BY \"ThingID\" HAVING ThingIDCount >= " . count($tag_ids);
// Get records
$records = DB::query($sql);
foreach($records as $record) {
$objects->push(new Thing($record));
}
}
// Display the Things for demo purposes
foreach($objects as $thing){
Debug::Dump($thing);
}
NB I have added a left join to a Thing table, as I imagine you have some db fields on it, but drop the line (and the \"Thing\".* on the SELECT statement) if that's not the case
回答2:
I think if you are using older versions for SilverStripe 3, you need to use the ExactMatchMulti SearchFilter.
$tag_ids = array(1,2,3,4);
$things = Thing::get();
$things->filter('Tags.ID:ExactMatchMulti', $tag_ids);
回答3:
I don't see an easy solution do do this directly with the ORM. But you should be able to solve this with some loops and filtering.
$tag_ids = array(1,2,3,4);
$things = Thing::get();
$results = new ArrayList();
$tags_count = count($tag_ids);
$matches = 0;
foreach ($things as $thing)
{
foreach ($thing->Tags() as $tags)
{
$matches = 0;
foreach ($tags as $tag)
{
if ( in_array($tag->ID, $tag_ids) )
{
$matches++;
}
}
if ( $matches === $tags_count )
{
$results->push($thing);
}
}
}
Although not tested, this should leave you with $things that contain those with all tags and more. (Assuming a thing can only be tagged once with the same tag).
回答4:
You can try to do the following:
Tag::get()->byIds($tag_ids)->relation('Things')
which will return a ManyManyList that you can iterate over, ie
foreach(Tag::get()->byIds($tag_ids)->relation('Things') as $thing){
Debug::Dump($thing); // A 'Thing' object
}
Hope this helps
来源:https://stackoverflow.com/questions/17127486/silverstripe-filter-dataobject-list-by-many-many-relationship