kunstmaan/search-bundle
KunstmaanSearchBundle brings search functionality to Symfony applications, offering indexing, querying, and configurable integrations for Kunstmaan CMS content. It helps developers add flexible, maintainable site search and manage results through a structured bundle.
Read this first: it is a Symfony bundle, not a Laravel package. kunstmaan/search-bundle was built for Symfony 2.x (KunstmaanSearchBundle extends Symfony\Component\HttpKernel\Bundle\Bundle) and its last release was April 2014. You cannot composer require it into a Laravel app — it will pull in symfony/framework-bundle 2.x, symfony/dependency-injection 2.x and an ancient Elastica, and none of the Symfony DI machinery (service tags, compiler passes, bundle bootstrapping) exists in Laravel's container.
So the day-to-day value for a Laravel developer is as a reference implementation of a provider abstraction over Elasticsearch, not as a dependency.
Where to look, in order:
SearchConfigurationInterface — the whole bundle is built around this contract.search() wrapper around Elastica queries.The first use case the bundle was designed for: you have a Doctrine entity you want searchable. You write a service implementing SearchConfigurationInterface that declares the index name, the document type, the fields to index and how to populate them; you tag that service; the bundle aggregates all tagged configurations into one provider and exposes search across them.
In Laravel, the equivalent first use case is Scout:
composer require laravel/scout
composer require matchish/laravel-scout-elasticsearch # or babenkoivan/elastic-scout-driver
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
// config/scout.php
'driver' => env('SCOUT_DRIVER', 'elasticsearch'),
class Product extends Model
{
use Searchable;
public function toSearchableArray(): array
{
return ['id' => $this->id, 'name' => $this->name, 'description' => $this->description];
}
}
That gets you the same shape of thing this bundle offered in 2014, on a stack that still runs.
The bundle's model is worth copying even if the code isn't.
One configuration object per indexable domain type. Each implementation answers: what index, what type, what fields, what's the document id, and how do I populate it. The bundle then registers the provider as a single service and you call search() against it.
The provider is a thin wrapper, not a query builder. Queries are Elastica Query objects — you write the Elasticsearch DSL yourself. There is no fluent abstraction hiding the ES API. Expect to hand-write bool/multi_match/filtered bodies.
In Laravel the mirror is:
interface SearchConfiguration
{
public function index(): string;
public function type(): string;
public function documentId($model): string;
public function toDocument($model): array;
public function modelClass(): string;
}
class SearchRegistry
{
/** @var SearchConfiguration[] */
public function __construct(private array $configurations) {}
public function for(string $modelClass): SearchConfiguration { /* ... */ }
}
Bind it in a service provider from a config array or by scanning a directory — Laravel's answer to "tagged services" is explicit registration in a ServiceProvider::register().
Index lifecycle as commands/jobs, not request-time work.
php artisan search:reindex {model} → delete index, create with mapping, bulk-index in chunks.IndexModel / RemoveFromIndex) rather than hitting Elasticsearch synchronously. The bundle's populateIndex() was typically run from a console command precisely because it's slow.products_staging, products_production) so a local reindex never touches production.Bulk is the only sane populate path. If you copy one thing from the bundle's provider, copy its use of Elastica's bulk API: ~500–1000 documents per request, refresh: false during the load, a single refresh at the end.
getIndexType() method is a direct artifact of ES 1.x mapping types, which were removed in ES 7 and are gone entirely in 8. Don't try to retrofit it; the concepts survive, the code doesn't.composer.json.php artisan scout:flush then scout:import.curl localhost:9200/_cat/indices?v, curl localhost:9200/products/_mapping?pretty, and check the document count matches your table before blaming the query. Elastica's exceptions are thin wrappers around ES error bodies; log the full response.SearchProviderInterface with a search(string $query, array $options): Result method, so you can swap Elastica for the official elasticsearch/elasticsearch client (8.x) later without touching controllers. The bundle did this; do it too.text fields with analyzer/search_analyzer and keyword sub-fields in your mapping from day one; retrofitting analyzers means another full reindex.How can I help you explore Laravel packages today?