Replacing Drupal Static Navigation with Apache Solr Facets
I suggested in another blog post (which I'm still writing and will link here when I'm done) that Solr could be used to drive site-wide navigation. Robert Douglas was recently quoted to me (second hand, so I can't verify the source) that the main reason to use Solr is for facets. I would add relevancy scores to that list, but that's for another post. What occurred to me one day was that facets look a lot like “traditional” navigation, only they are smarter (and better looking) than menu items or Views exposed filters. But getting solr facets on every page of your site, they way static menus are, is a bit out of the box. First, you need a Solr response available on every page load and second, you need to be able to cache and reuse that response from one request to the next. So here's what I do to accomplish this:
- Try to load a cached query. If none exists then create, execute, and cache a default one.
- Whenever the user builds a new query, cache it so that it becomes the default query for the next page load.
Here's the code, in two hooks:
hook_init()
function mymodule_init(){ ctools_include('object-cache'); /** * apachesolr_drupal_query() knows about the base query class. By running it first, we make sure that * cached query objects get unserialized properly */ $query = apachesolr_drupal_query($keys, $filterstring, $solrsort, 'search/apachesolr_search'); $cached_query = ctools_object_cache_get('mymodule', 'apachesolr_query'); $cached_response = ctools_object_cache_get('mymodule', 'apachesolr_response'); /** * If no cached query exists then execute an empty search. Our implementation of * hook_apachesolr_process_results() will take care of caching the query and response * for the next time around */ if (!$cached_query && !$cached_response) { apachesolr_search_execute(NULL, NULL, NULL, 'search/apachesolr_search', $page=0); return; } /** * TODO: If we bother to check query and response independently of one another then we should also * be able to respond intelligently when one is absent but the other present. Why this would be the * case I don't know. */ if ($cached_query) { $query = $cached_query; apachesolr_current_query($query); } if ($cached_response) { apachesolr_has_searched(TRUE); apachesolr_static_response_cache($cached_response); } }
hook_apachesolr_process_results()
/** * Implementation of hook_apachesolr_process_results(). * This is the only hook that runs after all the query altering hooks have been * run. Also, it only runs if the query is good (has results). So this is where * we choose to cache our query and response. */ function mymodule_apachesolr_process_results() { ctools_include('object-cache'); $response = apachesolr_static_response_cache(); $query = apachesolr_current_query(); ctools_object_cache_set('mymodule', 'apachesolr_response', $response); ctools_object_cache_set('mymodule', 'apachesolr_query', $query); }
hook_init() runs early in the bootstrap phase, on every request, and before any search or block hooks get invoked. So this is a good time to check for a cached query and create one if it doesn't exist. We are using ctools object cache to store the query. But we don't do the caching up front in hook_init(). Notice that we call apachesolr_search_execute() to do the query and get the response. This function will cause hook_apachesolr_process_results(), to be invoked, just the same as if this were being done as part of a normal search. By hooking into the response processing we ensure that no matter when, how, or by whom the query is getting executed, the last query sent to Solr will be in cache on the next page load. Since we are executing in hook_init(), other hooks have ample opportunity to execute new queries, which will overwrite our default one.
The code above should be made more robust before you use it in a serious deployment. We should be checking for ctools and apachesolr before we try to call their functions, for example. And no provision is made for catching GET params, in case one should want them. Also note that your default query can have filters, sorts, and keywords passed through apachesolr_search_execute(). So this doesn't have to be a bare query.






Very interesting post, Chris.
Very interesting post, Chris. I was thinking about the possibilities of using solr as a site-wide navigation recently. Have you looked at Exhibit (SIMILE widget) as an alternative? It's astonishingly fast and plays nicely with caching.In other words, once your initial JSON data is cached, that's it.
While I'm on...one of the issues I came across with site-wide nav. using solr/faceted style browsing was getting drupal to "remember" the selected facet (or in the case of site-wide navigatio - menu option) for each (anonymous) visitor without using session cookies. your approach of using using ctools object cache is very clever. must dig deeper into that.
thanks for posting...and looking foward to the completed module
dub
Thanks! I'll be the first to
Thanks! I'll be the first to admit that the code in this post is just a start. It guarantees that some query has been executed and that some response is there for generating facets. But if a user links directly to a node without having gotten there via a search then the facets will not reflect the filter selections that might have gotten them there. The solution would be to construct the initial query by providing default filters based on the node one is viewing. E.g., taxonomy terms.
I have indeed played with Exhibit, pretty extensively. However, I don't find it trivial to set up. The integration modules for it have not always worked cleanly for me, and so I often find myself writing my own implementations. Also, Exhibit is a javascript solution. So I wouldn't consider it for replacing site navigation.
Thanks again for the comment. It's nice to know someone's reading ;-)
good points about exhibit
good points about exhibit being a javascript solution and tricky to setup. I did manage to get the exhibit drupal add on for views working, but, I do remember having to try a few approaches before getting it to work properly.
on the topic of javascript, I'm finding it difficult to implement a neat UI for sitewide filtering without using some jquery/js at some point. Not just in the actual filtering...e.g. in our case two dropdown filters in the main navigation based on taxonomy terms.....but in the tagging of the content itself - we're using a large controlled vocabulary (i.e. not using freetagging).
like I said earlier..your approach is interesting. would be great if the guys could switch on notifications for this site so I can get an email if there's an update to the article.
dub
The important thing for
The important thing for JavaScript based features is that they be degradeable. As I recall (I could be wrong), the Exhibit stuff won't degrade because it's entirely Ajax. That is, there's no markup on the page until Exhibit loads. Which means there's nothing there at all on a screen reader.
I'm definitely tracking with the dropdown challenge, though. I've done this several different ways, depending on needs:
1. Write javascript to pick up the facets as Apache Solr generates them and rewrite them into a jump menu style selector. This has the advantage of degradeability. It has the disadvantage that you can implement it only where you have the vertical space. So not in an horizontal nav that stretches across the page, but in a side bar.
2. If you want the facet to be a dropdown everywhere it's used then use theme functions to rewrite the markup. I believe the last time I tried this I ran into some roadblocks. So mileage may vary.
3. Write your own module to provide filter code amenable to your purpose. Would have to implement some more Apache Solr hooks, etc. But you can model it off of the existing block related code in Apache Solr.
The most sensible option is to simply upgrade to D7 and implment a display plugin for the facet. I've done this a couple of times now and may get around to writing a blog post for it. But it really isn't hard. And speaking of D7, you really do need to check out Search API. I've not had a chance to play deeply with it, but it's looking like it may have a lot of the flexibility that even the new Apache Solr + Facet API solution still lacks.
Post new comment