Upgrade Your Drupal Skills
We trained 1,000+ Drupal Developers over the last decade.
See Advanced Courses NAH, I know EnoughSolr Search with Drupal 8
Solr search with Solarium
Before we get too far ahead of ourselves, we should note that this wasn’t done with a contributable module in mind. That isn’t because we don’t like giving back the the community, we totally do, it was because it was created for a very specific client need. There will likely be a more generic version of this coming out down the road if demand is high enough. Also, we are under the impression that most use cases are covered by the modules mentioned above, so that would be where most would start. Enough with the disclaimers; let’s talk Solarium.
We went with Solarium as the Solr client to use for this. That is what most of the existing Drupal modules use and it seemed to be the most direct way to do this with PHP. Installing Solarium is pretty simple with Composer and Drupal 8. (If you aren’t using Composer yet, you really should be.) Using a client for communicating with a Solr instance isn’t specifically required. Ultimately, the requests are just simple HTTP calls, but the client saves you from having to memorize all of the admittedly confusing query language that comes with using Solr.
Installing Solarium can be done as simply as composer install "solarium/solarium". You could also do this by adding a line to your composer.json file in the require section for "solarium/solarium": “3.6.0”. Your approach on this part may vary, but this should be done from the root of your Drupal site so that this library goes into the global dependencies for the project. These instructions are also detailed a bit more in the official docs for Solarium, here. The official docs also have a bunch of example code that will help if you dive into this like we did.
For this implementation, we opted to create a Solr PHP class to do the heavy lifting and made use of a Drupal service for calls to it from the rest of the app.
namespace Drupal\my_module\Solr;
use Solarium\Core\Client\Client;
class SolrExample {
/**
* Connection to the solr server
*
* @var Client
*/
protected $solr;
}
The heart of the class is going to be the connection to Solr which is done through the Solarium client. We will make use of this client in our constructor by setting it up with the credentials and default settings for connection to our Solr instance. In our case, we used a config form to get the connection details and are passing those to the client. We wanted to use the configuration management system so that we could keep those settings consistent between environments. This allowed more accurate testing and fewer settings for developers to keep track of.
/**
* Solr constructor.
*/
public function __construct() {
$config = \Drupal::config(‘example.solr_config’); //Normally we’d inject this, but for this example we’ll ignore that
$settings = [
'endpoint' => [
'default' => [
'host' => $config->get('host'),
'port' => $config->get('port'),
'path' => $config->get('path'),
'scheme' => $config->get('protocol'),
'http_method' => 'AUTO',
'site_hash' => TRUE,
]
]
];
$this->solr = new Client($settings);
}
We are doing this in the constructor so that we don’t have to create a new client connection multiple times during a given call. In our case, we ended up using this as a Drupal service which allows us to only have the Client object created once per call and gives a simple way to use this class throughout the app.
The next part is the actual search method. This does a lot and may not be clear from the code below. In this method, we take parameters passed in and build a Solr query. We have a helper function in this that does some specific formatting of the search terms to put it in the right query syntax. For most sites, this code would serve fine for doing generic searching of the whole index or having multiple versions for searching with specific filters
/**
* General Search functionality
*
* @param array $params
*
* @return mixed
*/
public function search($params = []) {
$query = $this->solr->createSelect();
$default_params = [
'start' => 0,
'rows' => 20,
'sort' => 'score',
'sort_direction' => 'DESC',
'search' => '*:*',
'time' => '*'
];
$params = array_merge($default_params, $params);
// Building a proper solr search query with the search params
$search_string = $this->getTextSearchString($params['search'], $params['time']);
$query->setQuery($params['search']);
$query->setStart($params['start'])->setRows($params['rows']);
$query->addSort($params['sort'], $params['sort_direction'] == 'ASC' ? $query::SORT_ASC : $query::SORT_DESC);
try {
$results = $this->solr->select($query);
return ['status' => 1, 'docs' => $results->getData()['response']['docs']];
}
catch (HttpException $e) {
\Drupal::logger('custom_solr')->warning('Error connecting to solr while searching content. Message: @message',['@message' => $e->getMessage()]);
return ['status' => 0, 'docs' => [], 'message' => 'Unable to reach search at this time. Try again later.'];
}
}
The code we’ve presented so far isn’t breaking new ground and for the most part does a similar job to the existing search modules available from the Drupal community. What really made us do something custom was the more like this feature of Solr. At the time that we were implementing this, we found that piece to be not quite working in one module and impossible to figure out in another, so we put our own together.
Thankfully with Solarium, this was a pretty simple query to tackle and we were able to have related content on the site without much other setup. We can create a new more like this query and submit an id so Solr knows which content to compare against for similarity. The rest of it behaves very similar to the search method presented previously. The results are still returned the same and we are able to do some other filtering to change the minimum relevancy score or number of rows.
$query = $this->solr->createMoreLikeThis();
$helper = $query->getHelper();
$query->setQuery('id:' . $id);
$query->setRows($params['rows']);
$query->setMltFields($params['mltfields']);
$query->setMinimumDocumentFrequency(1);
$query->setMinimumTermFrequency(1);
$query->createFilterQuery('status')->setQuery($params['queryfields']);
We didn’t share all of the code used for this here, obviously. The point of this post isn’t to help others create an exact duplicate of this custom implementation of Solarium in Drupal 8. At the time of this writing, it seems that the existing Solr modules might be in great shape for most use cases. We wanted to point out that if you have to dip into code for something like this, it can certainly be done and without an insane amount of custom code.
MIKE OUT
About Drupal Sun
Drupal Sun is an Evolving Web project. It allows you to:
- Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
- Facet based on tags, author, or feed
- Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
- View the entire article text inline, or in the context of the site where it was created
See the blog post at Evolving Web