Drupal - Programatically use search api

$query = Index::load('my_index')->query();
$query->addCondition('search_api_language', $language);
$query->keys($search_string);
$query->range(0, 25);
$data = $query->execute();

Also I have a custom parsing function for the results:

function parse_results(array $results) {
$list = [];
  foreach ($results AS $item) {
    // The pattern is "entity:[entity_type]:[entity_id]:[language_code]".
    // For example "entity:node/1:en".
    $data = explode(':', $item->getId());
    $data = explode('/', $data[1]);
    $list[] = $data[1];
  }
  return $list;
}

Here is a gist of an idea I was working on a few months ago. Basically a custom module has a router (i.e. /search) and this handles it. I had not yet moved to the point of implementing a custom search form, but this got me to a starting point of a page with results, and a pager.

namespace Drupal\velirsearch\Controller;

use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Drupal\velirsearch\Service\PagerService;

class SearchResults extends ControllerBase {

  /**
   * @var \Drupal\velirsearch\Service\PagerService
   */
  private $pager_service;

  /**
   * SearchResults constructor.
   * @param \Drupal\velirsearch\Service\PagerService $pager_service
   */
  public function __construct(PagerService $pager_service) {
    $this->pager_service = $pager_service;
  }

  /**
   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
   * @return static
   */
  public static function create(ContainerInterface $container) {
    $pager_service = $container->get('velirsearch.pager_service');
    return new static($pager_service);
  }

  /**
   * @param \Symfony\Component\HttpFoundation\Request $request
   * @return array
   */
  public function build(Request $request) {
    /* @var $search_api_index \Drupal\search_api\IndexInterface */
    $index = $this->getSearchIndex('new_solr_test');

    // Create the query.
    $query = $index->query([
      'limit' => 10,
      'offset' => !is_null($request->get('page')) ? $request->get('page') * 10 : 0,
      'search id' => 'velirsearch:1',
    ]);

    if (!is_null($request->get('keyword'))) {
      $query->keys(['body' => $request->get('keyword'), '#conjunction' => 'AND']);
    }

    $results = $query->execute();

    $output = '';

    foreach ($results as $result) {
      $value = $result->getField('view_mode_teaser')->getValues();
      $output .= $value[0];
    }

    $render[] = [
      '#markup' => $output,
      '#type' => 'remote',
    ];

    $render[] = $this->attachPager($results->getResultCount(), 10);

    return $render;
  }

  /**
   * Title callback.
   *
   * @param Request $request
   *   The request.
   *
   * @return string $title
   *   The page title.
   */
  public function title(Request $request) {
    if (!is_null($request->get('keyword'))) {
      return $this->t('Search results for %keyword', ['%keyword' => $request->get('keyword')]);
    }

    return $this->t('Search results');
  }

  /**
   * Load and return a search index.
   * @param $id
   * @return \Drupal\Core\Entity\EntityInterface|null
   */
  protected function getSearchIndex($id) {
    return $this->entityTypeManager()->getStorage('search_api_index')->load($id);
  }

  /**
   * Convenient method to obtain a pager to attach from the pager service.
   * @param $totalRows
   * @param $limit
   * @return array
   */
  protected function attachPager($totalRows, $limit) {
    return $this->pager_service->getPager($totalRows, $limit);
  }
}

The attachPager method simply returns:

pager_default_initialize($totalRows, $limit);
return ['#type' => 'pager'];

I was separating it out in case I wanted to build more than 1 custom search without duplicating old pager code.

Also note that I am simply concatenating the value of a record in Solr as my result, I am storing view mode renderings in Solr. You would likely have to render them on the fly unless you did the same.

This was also done in a version of Search API that is not current, YMMV. But it did work for me.


The official document is here: Executing a search in code

The following shows how to execute a Search API search programmatically, with some example code:

$index = \Drupal\search_api\Entity\Index::load('INDEX_ID');
$query = $index->query();

// Change the parse mode for the search.
$parse_mode = \Drupal::service('plugin.manager.search_api.parse_mode')
  ->createInstance('direct');
$parse_mode->setConjunction('OR');
$query->setParseMode($parse_mode);

// Set fulltext search keywords and fields.
$query->keys('alea iacta');
$query->setFulltextFields(['title', 'name', 'body']);

// Set additional conditions.
$query->addCondition('status', 1)
  ->addCondition('author', 1, '<>');

// Add more complex conditions.
// (In this case, a condition for a specific datasource).
$time = \Drupal::service('datetime.time')->getRequestTime();
$conditions = $query->createConditionGroup('OR');
$conditions->addCondition('search_api_datasource', 'entity:node', '<>')
  ->addCondition('created', $time - 7 * 24 * 3600, '>=');

// Restrict the search to specific languages.
$query->setLanguages(['de', 'it']);

// Do paging.
$query->range(20, 10);

// Set additional options.
// (In this case, retrieve facets, if supported by the backend.)
$server = $index->getServerInstance();
if ($server->supportsFeature('search_api_facets')) {
  $query->setOption('search_api_facets', [
    'type' => [
      'field' => 'type',
      'limit' => 20,
      'operator' => 'AND',
      'min_count' => 1,
      'missing' => TRUE,
    ],
  ]);
}

// Set one or more tags for the query.
// @see hook_search_api_query_TAG_alter()
// @see hook_search_api_results_TAG_alter()
$query->addTag('custom_search');

// Execute the search.
$results = $query->execute();

echo "Result count: {$results->getResultCount()}\n";
$ids = implode(', ', array_keys($results->getResultItems()));
echo "Returned IDs: $ids.\n";
$facets = $results->getExtraData('search_api_facets', []);
echo 'Facets data: ' . var_export($facets, TRUE);

Tags:

Search

8