puntmig/search-bundle
PuntMig SearchBundle integrates full-text search into Symfony apps, letting you index records and run configurable queries through a simple service. It keeps search logic out of controllers, supports paginated results, and offers a lightweight adapter-based setup for fast, flexible discovery.
puntmig/search-bundle is the Symfony-Drift rewrite of the Apisearch Symfony bundle: it lets one bundle definition run across Symfony 3.4 → 5.x, delegating version-specific plumbing to the symfony-drift helper classes. It was archived in September 2021, so treat it as something to read before maintaining an existing install rather than a package to adopt for new work — for greenfield projects start from the official Apisearch PHP client and wire it yourself.
Minimum steps:
composer require puntmig/search-bundle
Puntmig\SearchBundle\PuntmigSearchBundle::class => ['all' => true] in config/bundles.php.app/AppKernel.php.DependencyInjection/Configuration.php; read that file first, the README is thin:
# config/packages/puntmig_search.yaml
puntmig_search:
app_id: "%env(APISEARCH_APP_ID)%"
api_key: "%env(APISEARCH_API_KEY)%"
endpoint: "%env(APISEARCH_ENDPOINT)%"
index: "default"
Resources/config/services.*.yml and DependencyInjection/PuntmigSearchExtension.php. The registered service ids and aliases (typically puntmig_search.repository, puntmig_search.client, plus an alias on Apisearch\Repository\Repository) are what you will actually autowire — there is no other reliable contract.use Apisearch\Query\Query;
public function __invoke(Repository $repository): void
{
$repository->addObject($myEntity); // runs the indexing pipeline
$result = $repository->query(Query::create('*')->paginate(1, 20));
}
Inject the repository, not the client. The bundle's job is service wiring; autowire Apisearch\Repository\Repository and let Apisearch's own APIs do the work. Reach for the raw client only for admin operations (creating indices, resetting, snapshots) that the repository abstraction doesn't expose.
Multiple indices/repositories. The bundle supports more than one named repository (each with its own app_id/index) so multi-tenant search works out of the box. In practice you either (a) inject the default and accept a single index per app, or (b) add a small factory service that pulls the named puntmig_search.repository.<name> per tenant. If you go with (b), pin the service ids in your own config rather than relying on stringly-typed Container::get() lookups — the ids are not considered public API.
Indexing pipeline / event subscribers. Apisearch ships an event-driven IndexBuilder. Subscribe to it (tag your subscriber as an Apisearch subscriber in the bundle's DI, usually apisearch.event_subscriber or the bundle-specific equivalent — check services.yml) to transform, normalize, or drop documents just before they hit the index. This is the pattern for adding soft-delete filters, denormalizing relations, or enriching documents with computed fields, and it keeps that logic out of your entities.
Query building. Use Query::create(...) for simple cases and QueryBuilder when you need filters, aggregations, sorting, or facet configuration. Keep search queries in a dedicated service behind an interface (ProductSearcher) rather than scattering query construction through controllers — the API is churny and you want a single seam to swap in tests.
Testing. Don't boot the bundle in unit tests. Type-hint Apisearch\Repository\Repository in your own services and inject an in-memory fake (ArrayRepository style) in services_test.yaml. For integration tests against a real Apisearch instance, override puntmig_search.endpoint via the test config and reset the index in setUp.
Symfony-Drift awareness. If you extend the bundle with your own commands or config, mirror the Drift pattern (extend the drift base classes rather than Command directly) so your additions stay 3.4/4/5-compatible. If you don't need that matrix, ignore Drift entirely and just use the modern Symfony APIs — nothing in the runtime enforces it.
--ignore-platform-reqs unless you understand the failure mode — it hides PHP-level incompatibilities that surface at runtime.Configuration.php before you touch YAML. The config keys are the contract, and the README has not kept up. Misnamed keys silently default rather than erroring — a common source of "it's connecting to localhost:8100 despite my config."app_id/api_key pair; some setups concatenate them as app_id:api_key. Don't do it twice — supply each value separately as the config schema expects.http://host:8100 and http://host:8100/ are not interchangeable for some client builds. Normalize in your env var.app/config/config.yml, not config/packages/. The bundle runs on both, but the docs and examples skew modern.Apisearch\Repository\Repository fails, add an alias in your app config rather than making the bundle's service public:
services:
Apisearch\Repository\Repository: '@puntmig_search.repository'
Apisearch\Exception\* exceptions. Catch and log at your repository wrapper with the request body included.puntmig_search.repository in your own bundle for retries, audit logging, or per-tenant routing — that's stable regardless of the underlying Apisearch version. Avoid subclassing the DI extension; you'll re-do it if Drift is ever updated.apisearch-io/search-bundle (upstream) or wire apisearch-io/php-client yourself. This fork's value is the 3.4→5.x compatibility matrix, which is exactly the thing that is no longer evolving.How can I help you explore Laravel packages today?