Magento 2 Advanced Searching – SmartSearch

catalogsearchextensionsmagento2search

I'm trying to create a "SmartSearch" extension for Magento 2.0, but I really can't find out what the next step should be. What I have so far:

  • Working 2.0 Extension (registration.php, module is enabled and recognized)
  • I override the default autocomplete function by using this code:
    enter image description here

With this code I can manipulate the default autocomplete text. This is my SearchDataProvider:

class SearchDataProvider extends \Magento\CatalogSearch\Model\Autocomplete\DataProvider {
    /**
     * {@inheritdoc}
     */
    public function getItems()
    {
        $collection = $this->getSuggestCollection();

        $query = $this->queryFactory->get()->getQueryText();
        $result = [];
        foreach ($collection as $item) {

            $resultItem = $this->itemFactory->create([
                'title' => $item->getQueryText(),
                'data'  => $item->getData(),
                'num_results' => $item->getNumResults(),
            ]);
            if ($resultItem->getTitle() == $query) {
                array_unshift($result, $resultItem);
            } else {
                $result[] = $resultItem;
            }
        }
        return $result;
    }

    /**
     * Retrieve suggest collection for query
     *
     * @return Collection
     */
    private function getSuggestCollection()
    {
        return $this->queryFactory->get()->getSuggestCollection();
    }
}

If i add something after 'title', it's shown in the autocomplete dropdown.

What I want to do now is to load another provider that will return a collection of products that contain the search query text.

I already found a FullText search model, but I cannot figure out how to properly use this.

Does anyone have experience with creating a search collection in Magento 2.0?

Best Answer

To perform full text search Magento\Framework\Api\Search\SearchInterface should be used. Please note that Service Contracts concept was introduced in Magento 2, even though Service API is available not for all core functionality yet, it should be used whenever possible by 3-rd party modules instead of direct access to models and collections. To avoid breaking changes in the future (after 2.1, 2.2 etc releases), only classes/interfaces and methods marked with @api annotation should be used.

Below is fully working example which will display up to 5 matching products in suggest (title and price). To test just replace original \Magento\CatalogSearch\Model\Autocomplete\DataProvider content with this one:

<?php
namespace Magento\CatalogSearch\Model\Autocomplete;

use Magento\Search\Model\QueryFactory;
use Magento\Search\Model\Autocomplete\DataProviderInterface;
use Magento\Search\Model\Autocomplete\ItemFactory;
use Magento\Framework\Api\Search\SearchCriteriaFactory as FullTextSearchCriteriaFactory;
use Magento\Framework\Api\Search\SearchInterface as FullTextSearchApi;
use Magento\Framework\Api\Search\FilterGroupBuilder;
use Magento\Framework\Api\FilterBuilder;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Catalog\Api\ProductRepositoryInterface;

/**
 * Full text search implementation of autocomplete.
 */
class DataProvider implements DataProviderInterface
{
    const PRODUCTS_NUMBER_IN_SUGGEST = 5;

    /** @var QueryFactory */
    protected $queryFactory;

    /** @var ItemFactory */
    protected $itemFactory;

    /** @var \Magento\Framework\Api\Search\SearchInterface */
    protected $fullTextSearchApi;

    /** @var FullTextSearchCriteriaFactory */
    protected $fullTextSearchCriteriaFactory;

    /** @var FilterGroupBuilder */
    protected $searchFilterGroupBuilder;

    /** @var FilterBuilder */
    protected $filterBuilder;

    /** @var ProductRepositoryInterface */
    protected $productRepository;

    /** @var SearchCriteriaBuilder */
    protected $searchCriteriaBuilder;

    /**
     * Initialize dependencies.
     *
     * @param QueryFactory $queryFactory
     * @param ItemFactory $itemFactory
     * @param FullTextSearchApi $search
     * @param FullTextSearchCriteriaFactory $searchCriteriaFactory
     * @param FilterGroupBuilder $searchFilterGroupBuilder
     * @param FilterBuilder $filterBuilder
     * @param ProductRepositoryInterface $productRepository
     * @param SearchCriteriaBuilder $searchCriteriaBuilder
     */
    public function __construct(
        QueryFactory $queryFactory,
        ItemFactory $itemFactory,
        FullTextSearchApi $search,
        FullTextSearchCriteriaFactory $searchCriteriaFactory,
        FilterGroupBuilder $searchFilterGroupBuilder,
        FilterBuilder $filterBuilder,
        ProductRepositoryInterface $productRepository,
        SearchCriteriaBuilder $searchCriteriaBuilder
    ) {
        $this->queryFactory = $queryFactory;
        $this->itemFactory = $itemFactory;
        $this->fullTextSearchApi = $search;
        $this->fullTextSearchCriteriaFactory = $searchCriteriaFactory;
        $this->filterBuilder = $filterBuilder;
        $this->searchFilterGroupBuilder = $searchFilterGroupBuilder;
        $this->productRepository = $productRepository;
        $this->searchCriteriaBuilder = $searchCriteriaBuilder;
    }

    /**
     * {@inheritdoc}
     */
    public function getItems()
    {
        $result = [];
        $query = $this->queryFactory->get()->getQueryText();
        $productIds = $this->searchProductsFullText($query);
        if ($productIds) {
            $searchCriteria = $this->searchCriteriaBuilder->addFilter('entity_id', $productIds, 'in')->create();
            $products = $this->productRepository->getList($searchCriteria);
            foreach ($products->getItems() as $product) {
                $resultItem = $this->itemFactory->create([
                    /** Feel free to add here necessary product data and then render in template */
                    'title' => $product->getName() . " " . $product->getPrice()
                ]);
                $result[] = $resultItem;
            }
        }
        return $result;
    }

    /**
     * Perform full text search and find IDs of matching products.
     *
     * @param string
     * @return int[]
     */
    private function searchProductsFullText($query)
    {
        $searchCriteria = $this->fullTextSearchCriteriaFactory->create();
        /** To get list of available request names see Magento/CatalogSearch/etc/search_request.xml */
        $searchCriteria->setRequestName('quick_search_container');
        $filter = $this->filterBuilder->setField('search_term')->setValue($query)->setConditionType('like')->create();
        $filterGroup = $this->searchFilterGroupBuilder->addFilter($filter)->create();
        $currentPage = 1;
        $searchCriteria->setFilterGroups([$filterGroup])
            ->setCurrentPage($currentPage)
            ->setPageSize(self::PRODUCTS_NUMBER_IN_SUGGEST);
        $searchResults = $this->fullTextSearchApi->search($searchCriteria);
        $productIds = [];
        /**
         * Full text search returns document IDs (in this case product IDs),
         * so to get products information we need to load them using filtration by these IDs
         */
        foreach ($searchResults->getItems() as $searchDocument) {
            $productIds[] = $searchDocument->getId();
        }
        return $productIds;
    }
}
Related Topic