Skip to main content

This site requires you to update your browser. Your browsing experience maybe affected by not having the most up to date version.

We've moved the forum!

Please use forum.silverstripe.org for any new questions (announcement).
The forum archive will stick around, but will be read only.

You can also use our Slack channel or StackOverflow to ask for help.
Check out our community overview for more options to contribute.

Form Questions /

Moderators: martimiz, Sean, Ed, biapar, Willr, Ingo, swaiba

[Resolved] Fulltext Search on Member Subclass


Go to End


5 Posts   7735 Views

Avatar
katja

Community Member, 46 Posts

25 November 2010 at 1:03am

Edited: 05/12/2010 4:37pm

Hello Silverstripers,

I have been using the SearchContext object to create a Custom Search on a subclass of Member and that has been working really well when I do ExactMatchFilter or PartialMatchFilter on separate fields.

I now wanted to add an additional Search form that consists of only one input field and does a fulltext search on a number of fields.

So, in my Member class I have added

  static $indexes = array(fulltext (field1,field2,..) )

and I can see in the database that the index has been added.

If I now create a new SearchContext, couldn't I match the input from the Search field against several fields in the Member class (I have checked they are all of Char, VarChar or Text type) [aah.. wrong question already. It's the other way round, matching the fields against the input! /edit 5 Dec 2010]

I tried an awful lot last night and could not get it to work. But I am not sure now if that is because it is too complex, or because I just got muddled up in my thinking. I also came across this thread started by UncleCheese http://www.silverstripe.org/all-other-modules/show/6641?start=24 and tried to implement the version that Aram posted, but did not get it to work.

What I had previously tried is putting something like this in my extended Member class:

function FulltextSearchContext(){
    return new SearchContext(
		   $this->class,
		   $fields = new FieldSet(array(
		      new MyTextField('FulltextSearch','Search','')
		   )),
		   $filters = array(
                  //tried various different things here 
                               'FulltextSearch'   => new  FulltextFilter('field1')
                                            [...]
                 ));

}

I think there is something wrong in the way I write the $filters array. Either I get error messages, or I get all Data sets returned. Is it maybe just not possible to do a search on more than one field at a time?

So, I have two questions really:
1. Is the type of Search I am aiming at just very difficult to achieve?
2. Is there something where I go wrong with the syntax of the SearchContext?

I'd really appreciate your help. I should maybe mention I have read about the Sphinx module, and I would like to try it out, but at some later stage. If it is at all possible to get it going without that module for now, that would be great.

Thanks,
Katja

[edited 5 Dec 2010]

Avatar
katja

Community Member, 46 Posts

5 December 2010 at 4:33pm

Edited: 05/12/2010 4:39pm

I finally managed to answer my own question. Please correct me if I'm wrong

1. It is not that difficult to achieve
2. If you use FulltextFilter in your SearchObject, you can match only one field against your search query (and in the corresponding database table, the Searchfields FULLTEXT index must contain only that one field!)

The solution I found in the end is to write your own filter for processing the textfield input. This can be done with DataObject::get()

It took me a while to get the syntax right, I also had difficulties because the search was across two tables (Member and subclass of Member). I found that I had to use two MATCH ... AGAINST although I am only searching on one object -- this has to do with the fact that using MATCH ... AGAINST, you have to match exactly the fields that you have in your SearchFields index, against the search query

My results function now looks like this:

function results($data, $form){
        	$data = $_REQUEST;
            $query = htmlspecialchars($data['Search'], ENT_QUOTES,'UTF-8');            
        	if($searchresult = DataObject::get("Therapist","MATCH (PracticeAddress,Accessibility,Fees,Membership,Style) AGAINST ('$query') OR MATCH (FirstName,Surname) AGAINST ('$query') ")){
        	    $data['Results'] = $searchresult;	        		
        	} else {
        		$data['Results'] = '';
        	}
        	$data['Title'] = 'Search Results';
        	return $this->customise($data)->renderWith(array('Page_results','Page'));
        }

Who knows, maybe this will be of use to somebody. Feel free to ask me if anything is unclear.

(I hope the code is not too bad, I am still quite a newbie programmer)

Katja

Avatar
Mo

Community Member, 541 Posts

3 June 2011 at 11:44pm

Do you know how you would go about searching a Page object and a DataObject? I have a searchbox that I want to use to search all pages on the site and Case Studies (which are dataobjects managed with ModelAdmin).

I guess you could use use your results method and run 2 queries:

1. DataObject::get('SiteTree')
2. DataObject::get('CaseStudy')

Then create a loop that merges the two into one DataObjectSet, but that seems a little heavy handed. Would be nice if there was a more elegant method?

Cheers for any responses.

Mo

Avatar
katja

Community Member, 46 Posts

4 June 2011 at 11:42pm

Edited: 05/06/2011 1:48am

Yes, you would not get round running two seperate queries I think, but for creating the combined DataObjectSet you can use DataObjectSet()->merge(). Not sure this is very elegant though ;) And it might be what you were thinking of doing anyway..:

[...]
$searchresult1 = DataObject::get("YourDataObject", "(MATCH(Field1,Field2...) AGAINST ('$query' IN BOOLEAN MODE))");
$searchresult2 = DataObject::get("SiteTree", " (MATCH (Title,MenuTitle,Content,MetaTitle,MetaDescription,MetaKeywords) AGAINST ('$query' IN BOOLEAN MODE))")
$ds = new DataObjectSet();
            if($searchresult1){
                $ds->merge($searchresult1);
            }
            if($searchresult2){
                $ds->merge($searchresult2);
            }
            $searchresult = ($ds->exists())? $ds : '';      
            $data['Results'] = $searchresult;
[...]

I am glad you asked this question. I had thought about this previously, but as I did not need it on my site, had never really looked into how to solve it. I might well need this in the future, so it will be useful to have!

Avatar
Mo

Community Member, 541 Posts

18 June 2011 at 2:16am

Ah Ha! I have got a result!

http://pastie.org/2082787

Thanks for the help. I didn't think DOS->Merge() would be able to handle two different objects, but amazingly it does!

Cheers,

Mo