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

Symfony Search Bundle Laravel Package

adriballa/symfony-search-bundle

Symfony bundle that abstracts Elasticsearch: define indexes with two PHP classes, get auto-generated routes for index/document CRUD, validation, and a powerful search API (full-text, filters, sorting, pagination, aggregations). Optional client interfaces for programmatic use.

View on GitHub
Deep Wiki
Context7

Getting Started

First Steps

  1. Installation: Add the package via Composer:

    composer require vendor/package-name
    

    Publish the package config (if needed) with:

    php artisan vendor:publish --provider="Vendor\PackageName\PackageServiceProvider"
    
  2. Define Indexes: Create two classes per index:

    • IndexDefinitionInterface (e.g., ProductIndexDefinition) to define schema and settings.
    • IndexMappingInterface (e.g., ProductIndexMapping) to specify field mappings (e.g., text, keyword, date).

    Example:

    class ProductIndexDefinition implements IndexDefinitionInterface {
        public function getName(): string { return 'products'; }
        public function getSettings(): array { return ['number_of_shards' => 1]; }
    }
    
  3. Register Indexes: Bind your index definitions to the container in a service provider:

    $this->app->bind(IndexDefinitionInterface::class, ProductIndexDefinition::class);
    
  4. Run Migrations: Use the built-in CLI command to create indexes:

    php artisan elastic:index:create ProductIndexDefinition
    
  5. First Query: Index a document and search:

    // Index a document
    $client = app(DocumentClientInterface::class);
    $client->index('products', $productData);
    
    // Search
    $searchClient = app(SearchClientInterface::class);
    $results = $searchClient->search('products', 'query', [
        'filters' => ['category' => 'electronics'],
        'sort' => ['price' => 'asc']
    ]);
    

Key First-Use Case

Bulk Indexing: Use the DocumentClientInterface to index multiple documents at once:

$client->bulkIndex('products', [$doc1, $doc2, $doc3]);

Implementation Patterns

Core Workflows

  1. CRUD Operations:

    • Indexing: Single or bulk documents via DocumentClientInterface.
    • Updates: Partial updates with validation:
      $client->update('products', $id, ['price' => 99.99]);
      
    • Deletion: By ID or query:
      $client->delete('products', $id);
      // or
      $client->deleteByQuery('products', ['category' => 'electronics']);
      
  2. Search Patterns:

    • Basic Search:
      $results = $searchClient->search('products', 'laptop', ['page' => 2, 'perPage' => 10]);
      
    • Filtered Search:
      $results = $searchClient->search('products', '', [
          'filters' => [
              new ExactMatch('category', 'electronics'),
              new Range('price', ['gte' => 500])
          ]
      ]);
      
    • Aggregations:
      $results = $searchClient->search('products', '', [
          'aggregations' => ['categories' => ['terms' => ['field' => 'category']]]
      ]);
      
  3. Lifecycle Management:

    • Index Creation/Deletion:
      $indexClient = app(IndexClientInterface::class);
      $indexClient->create('products'); // Uses your IndexDefinitionInterface
      $indexClient->delete('products');
      
    • Reindexing: Use the IndexClientInterface to rebuild an index from a data source (e.g., database).
  4. Validation-Driven Development:

    • Define strict mappings in IndexMappingInterface to enforce document structure:
      class ProductIndexMapping implements IndexMappingInterface {
          public function getMappings(): array {
              return [
                  'properties' => [
                      'name' => ['type' => 'text', 'analyzer' => 'english'],
                      'price' => ['type' => 'float'],
                      'category' => ['type' => 'keyword']
                  ]
              ];
          }
      }
      
    • Validation errors are returned in indexation responses (e.g., missing required fields).

Integration Tips

  1. Laravel Eloquent:

    • Use model observers to sync Eloquent models with Elasticsearch:
      class ProductObserver {
          public function saved(Product $product) {
              $client = app(DocumentClientInterface::class);
              $client->index('products', $product->toArray());
          }
      }
      
    • Register the observer in a service provider:
      Product::observe(ProductObserver::class);
      
  2. API Resources:

    • Extend Laravel’s JsonResource to include Elasticsearch metadata:
      public function toArray($request) {
          return [
              'id' => $this->id,
              'elasticsearch_id' => $this->elasticsearch_id,
              'name' => $this->name,
          ];
      }
      
  3. Caching:

    • Cache frequent search queries or aggregations using Laravel’s cache:
      $cacheKey = 'products_search_' . md5($query);
      return Cache::remember($cacheKey, now()->addMinutes(5), function() use ($searchClient, $query) {
          return $searchClient->search('products', $query);
      });
      
  4. Testing:

    • Use the package’s IndexClientInterface to reset indexes in tests:
      public function tearDown(): void {
          $indexClient = app(IndexClientInterface::class);
          $indexClient->delete('products_test');
          parent::tearDown();
      }
      

Gotchas and Tips

Common Pitfalls

  1. Validation Errors:

    • If a document fails validation during indexing, the response includes an errors array with field-specific issues.
    • Fix: Ensure your IndexMappingInterface matches your data structure. Use getMappings() to define required fields:
      'name' => ['type' => 'text', 'required' => true]
      
  2. Index Naming Conflicts:

    • Avoid dynamic index names (e.g., products_${date}) unless explicitly managed, as the package assumes static names for lifecycle operations.
    • Fix: Use a naming convention (e.g., products_v1) and update mappings manually if the schema evolves.
  3. Bulk Operations Timeouts:

    • Large bulk operations may timeout if Elasticsearch’s bulk API limits are hit.
    • Fix: Split bulk operations into chunks or increase Elasticsearch’s http.max_content_length in elasticsearch.yml.
  4. Case Sensitivity in Keyword Fields:

    • Keyword fields (e.g., category) are case-sensitive by default. Use normalizer or lowercase in mappings for case-insensitive searches:
      'category' => [
          'type' => 'keyword',
          'normalizer' => 'lowercase'
      ]
      
  5. Pagination Offsets:

    • Avoid deep pagination (e.g., page=1000) with from/size queries, as Elasticsearch may return incomplete or slow results.
    • Fix: Use search_after for deep pagination or implement cursor-based pagination in your application.

Debugging Tips

  1. Enable Logging:

    • Configure the package to log raw Elasticsearch queries:
      'logging' => [
          'enabled' => true,
          'channel' => 'single',
      ],
      
    • Check logs in storage/logs/laravel.log for query details.
  2. Inspect Index Mappings:

    • Use the IndexClientInterface to fetch mappings for debugging:
      $mappings = $indexClient->getMapping('products');
      
  3. Test Locally with Docker:

    • Spin up Elasticsearch locally for development:
      docker run -p 9200:9200 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.15.0
      
    • Configure the package to use http://localhost:9200.
  4. Handle Rate Limiting:

    • Elasticsearch may throttle requests. Implement exponential backoff in your client:
      try {
          $client->index('products', $data);
      } catch (RateLimitExceededException $e) {
          sleep(1);
          retry();
      }
      

Extension Points

  1. Custom Filters:

    • Extend the FilterInterface to create reusable filters (e.g., IpRangeFilter):
      class IpRangeFilter implements FilterInterface {
          public function __construct(private string $field, private string $ipRange) {}
          public function toArray(): array {
              return ['range' => [$this->field => ['gte' => $this->ipRange]]];
          }
      }
      
  2. Field Types:

    • Add custom field types by implementing FieldTypeInterface and registering them in the service provider:
      $this->app->bind(FieldTypeInterface::class, CustomFieldType::class);
      
  3. Search Highlighting:

    • Enable highlighting
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.
anousss007/vigilance
supportpal/eloquent-model
ardenexal/fhir-models
laravel-at/laravel-image-sanitize
romalytar/yammi-audit-log-laravel
ardenexal/fhir-validation
arshaviras/weather-widget
laravel-chronicle/core
sunchayn/nimbus
daikazu/eloquent-salesforce-objects
unseen-codes/chat
romalytar/yammi-jobs-monitoring-laravel
kisame76/filament-db-table-state
nqxcode/laravel-lucene-search
dpfx/laravel-livewire-wizards
workos/workos-php-laravel
sofa/laravel-global-scope
nawasara/auth-primitives
adhocrat-io/arkhe-main
make-dev/orca-harpoon