Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Meilisearch Search Bundle Laravel Package

aschaeffer/meilisearch-search-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the Bundle

    composer require aschaeffer/meilisearch-search-bundle
    

    Enable the bundle in config/bundles.php:

    return [
        // ...
        ArnaudSchaeffer\MeilisearchSearchBundle\MeilisearchSearchBundle::class => ['all' => true],
    ];
    
  2. Configure the Bundle Add MeiliSearch connection details to config/packages/meilisearch_search.yaml:

    meilisearch_search:
        clients:
            default:
                host: 'http://localhost:7700'
                api_key: 'your-api-key'
    
  3. First Use Case: Indexing a Doctrine Entity Annotate your entity with @Meilisearch\Index and define a searchable index:

    use ArnaudSchaeffer\MeilisearchSearchBundle\Annotation\Index;
    
    /**
     * @Index(indexName="products")
     */
    class Product {}
    

    Sync the index with your database:

    php bin/console meilisearch:sync
    
  4. Searching Inject the MeilisearchSearchClient and perform a search:

    use ArnaudSchaeffer\MeilisearchSearchBundle\Client\MeilisearchSearchClient;
    
    class ProductController
    {
        public function search(MeilisearchSearchClient $client)
        {
            $results = $client->search('products', 'query');
            return $this->json($results);
        }
    }
    

Implementation Patterns

Workflows

  1. Index Management

    • Dynamic Index Creation: Use the meilisearch:index:create command to generate indexes for annotated entities.
    • Partial Updates: Leverage Doctrine lifecycle events (postPersist, postUpdate, postRemove) to sync changes:
      // src/EventListener/MeilisearchSyncListener.php
      use ArnaudSchaeffer\MeilisearchSearchBundle\EventListener\MeilisearchSyncListener;
      
      class MeilisearchSyncListener extends MeilisearchSyncListener
      {
          protected function getIndexName(object $entity): string
          {
              return 'products'; // Custom logic if needed
          }
      }
      
  2. Search Integration

    • Reusable Search Services: Create a service to encapsulate search logic:
      # config/services.yaml
      services:
          App\Service\ProductSearchService:
              arguments:
                  $client: '@meilisearch_search.client.default'
      
      class ProductSearchService
      {
          public function __construct(private MeilisearchSearchClient $client) {}
      
          public function searchProducts(string $query, int $limit = 10): array
          {
              return $this->client->search('products', $query, [
                  'limit' => $limit,
                  'attributesToRetrieve' => ['id', 'name', 'price'],
              ]);
          }
      }
      
  3. Hybrid Search (Database + MeiliSearch)

    • Use MeiliSearch for full-text search and Doctrine for exact matches:
      // Fallback to DB if MeiliSearch returns no results
      $meiliResults = $searchService->searchProducts($query);
      if (empty($meiliResults)) {
          $products = $entityManager->getRepository(Product::class)
              ->createQueryBuilder('p')
              ->where('p.name LIKE :query')
              ->setParameter('query', "%$query%")
              ->getQuery()
              ->getResult();
      }
      
  4. Bulk Operations

    • Use the MeilisearchIndexManager for bulk indexing:
      $manager = $container->get('meilisearch_search.index_manager');
      $manager->addDocuments('products', $productsArray);
      $manager->updateIndex('products');
      

Integration Tips

  • Doctrine Events: Subscribe to MeilisearchSyncListener in services.yaml to auto-sync entities:
    services:
        ArnaudSchaeffer\MeilisearchSearchBundle\EventListener\MeilisearchSyncListener:
            tags:
                - { name: 'doctrine.event_listener', event: 'postPersist' }
                - { name: 'doctrine.event_listener', event: 'postUpdate' }
                - { name: 'doctrine.event_listener', event: 'postRemove' }
    
  • Custom Mappers: Override the default entity-to-array mapper for complex entities:
    use ArnaudSchaeffer\MeilisearchSearchBundle\Mapper\EntityMapperInterface;
    
    class CustomEntityMapper implements EntityMapperInterface
    {
        public function toArray(object $entity): array
        {
            return [
                'id' => $entity->getId(),
                'name' => $entity->getName(),
                'custom_field' => $this->mapCustomField($entity),
            ];
        }
    }
    
    Register it in services.yaml:
    services:
        App\Mapper\CustomEntityMapper:
            tags:
                - { name: 'meilisearch_search.entity_mapper', alias: 'product' }
    

Gotchas and Tips

Pitfalls

  1. Index Naming Conflicts

    • Ensure @Index annotations use unique indexName values. Overlapping names will cause sync failures.
    • Fix: Use a naming convention (e.g., {entity_class}_index) or validate in a custom MeilisearchSyncListener.
  2. Circular References in Entities

    • MeiliSearch serializes entities to JSON, which fails on circular references (e.g., ProductCategory).
    • Fix: Use #[Groups] from Symfony Serializer or implement JsonSerializable:
      use JsonSerializable;
      
      class Product implements JsonSerializable
      {
          public function jsonSerialize(): array
          {
              return [
                  'id' => $this->id,
                  'name' => $this->name,
                  // Exclude circular references
              ];
          }
      }
      
  3. Rate Limiting

    • MeiliSearch has rate limits. Bulk operations may hit these.
    • Fix: Use MeilisearchIndexManager::addDocuments() with chunking:
      $manager->addDocuments('products', array_chunk($products, 1000));
      $manager->updateIndex('products');
      
  4. Doctrine Proxy Classes

    • Lazy-loaded Doctrine proxies may cause issues during indexing.
    • Fix: Initialize proxies before syncing or use getId() to trigger loading:
      $entity->getId(); // Force proxy initialization
      
  5. Case Sensitivity in Search

    • MeiliSearch is case-insensitive by default, but custom analyzers may change this.
    • Fix: Configure the index settings explicitly:
      $client->updateIndexSettings('products', [
          'searchableAttributes' => ['name', 'description'],
          'searchSettings' => [
              'typoTolerance' => 'lenient',
          ],
      ]);
      

Debugging

  • Enable Debug Mode Configure meilisearch_search.yaml to log requests:
    meilisearch_search:
        clients:
            default:
                host: 'http://localhost:7700'
                api_key: 'your-api-key'
                debug: true # Logs all API calls
    
  • Check Index Status Use the CLI to inspect indexes:
    php bin/console meilisearch:index:status
    
  • Test Locally Spin up MeiliSearch with Docker:
    docker run -p 7700:7700 -it meilisearch/meilisearch:v0.22.0
    

Extension Points

  1. Custom Search Parameters Extend the MeilisearchSearchClient to add domain-specific search logic:

    class ExtendedSearchClient extends MeilisearchSearchClient
    {
        public function advancedSearch(string $index, string $query, array $params = []): array
        {
            $params['filter'] = $this->buildCustomFilter($query);
            return parent::search($index, $query, $params);
        }
    }
    

    Override the service in services.yaml:

    services:
        meilisearch_search.client.default:
            class: App\Client\ExtendedSearchClient
    
  2. Event-Driven Indexing Dispatch events before/after index updates:

    use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
    
    class MeilisearchSyncListener
    {
        public function __construct(private EventDispatcherInterface $dispatcher) {}
    
        public function postPersist(object $entity)
        {
            $this->dispatcher->dispatch(new MeilisearchIndexEvent($entity, 'persist'));
            // Sync logic...
        }
    }
    
  3. Async Indexing Use Symfony Messenger to queue index updates:

    # config/packages/messenger.yaml
    framework:
        messenger:
            transports:
                meilisearch: '%env(MEILISE
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
comsave/common
alecsammon/php-raml-parser
chrome-php/wrench
lendable/composer-license-checker
typhoon/reflection
mesilov/moneyphp-percentage
mike42/gfx-php
bookdown/themes
aura/view
aura/html
aura/cli
povils/phpmnd
nayjest/manipulator
omnipay/tests
psr-mock/http-message-implementation
psr-mock/http-factory-implementation
psr-mock/http-client-implementation
voku/email-check
voku/urlify
rtheunissen/guzzle-log-middleware