printdeal/pandosearch-bundle
Symfony bundle that integrates the PandoSearch search service into your application, providing configuration and services to index content and run fast, relevant searches with minimal setup.
This is a Symfony bundle, not a Laravel package — there is no service provider, facade, or config/pandosearch.php. A Laravel developer picking it up should treat it as a reference implementation and port the thin client logic rather than installing it.
Minimal steps for a Symfony app:
Install (expect version friction — last tagged release is Nov 2018):
composer require printdeal/pandosearch-bundle
You will likely need --with-all-dependencies and may have to pin symfony/http-kernel or Guzzle to versions contemporary with PHP 7.1–7.3.
Register the bundle — in legacy apps, AppKernel::registerBundles(); in Symfony 4/5 Flex apps, config/bundles.php:
Printdeal\PandosearchBundle\PrintdealPandosearchBundle::class => ['all' => true],
Configure it — create config/packages/pandosearch.yaml (or config.yml in Symfony 3). Because the bundle is archived and docs are sparse, open DependencyInjection/Configuration.php first to confirm the exact root key and option names before trusting any snippet:
pandosearch:
host: https://api.pandosearch.com
api_key: '%env(PANDO_API_KEY)%'
# index / default search options as defined in Configuration.php
Where to look first, in order:
DependencyInjection/Configuration.php — the authoritative list of config keys.Resources/config/services.yml (or .xml) — the service ID you will inject.src/ — this is what you actually call.First use case — inject the client and run a search in a controller:
public function search(Request $request, PandosearchClient $client): Response
{
$results = $client->search($request->query->get('q', ''), ['limit' => 10]);
return $this->render('search/results.html.twig', ['results' => $results]);
}
Verify the method name against the source; the bundle wraps Pandosearch's REST API, so search, suggest, and index/update operations are the expected surface.
Budget half a day for the first integration, most of it spent on dependency resolution and confirming the API contract in source — not on writing application code.
Isolate the vendor client behind your own service. Never let controllers or Twig talk to the bundle's client directly. Create App\Search\SearchGateway that wraps it with typed inputs/outputs. This is the single highest-value pattern here, because the package is archived and you will eventually swap it for direct Guzzle/PSR-18 calls or a maintained SDK. With a gateway in place that swap is a one-file change.
final class SearchGateway
{
public function __construct(private PandosearchClient $client) {}
public function find(string $term, int $limit = 10): array
{
try {
return $this->client->search($term, ['limit' => $limit])['results'] ?? [];
} catch (\Throwable $e) {
$this->logger->error('pandosearch search failed', ['term' => $term, 'e' => $e]);
return []; // degrade gracefully
}
}
}
Controller → Twig flow. Keep pagination, sorting, and filter mapping in the controller or a form/model object; pass a normalized array to the template. Do not pass the raw vendor response into Twig — the shape is not under your control.
Autocomplete endpoint. Wire a small JSON action that calls the suggestion/suggest method and returns a trimmed array. Cache aggressively (see below) — type-ahead traffic is the biggest volume driver against a metered search API.
Indexing. The idiomatic Symfony approach is an event subscriber or postPersist/postUpdate listener on the entities you index, pushing documents to Pandosearch. Better: dispatch a domain event and consume it from a queued listener (Symfony Messenger) so a slow external API never blocks a write. Also provide an app:search:reindex console command that iterates your content in batches — you will need it after every schema/mapping change.
Testing. Because the client is a concrete service, define an interface in your app (e.g. SearchGatewayInterface) and mock that in tests. If you must test the bundle client itself, mock its underlying HTTP transport (Guzzle handler stack / MockHandler) rather than hitting the network. Add a single integration test gated behind an env var that hits a staging index.
Environment separation. Point host/index name at different values per environment via .env. Never share an index name between dev and prod — you will pollute production results.
composer update breaks it, you fix it or fork it.composer.json, and require it via a VCS repositories entry.Configuration.php node tree is the source of truth. Common failure mode is a silently-ignored key that only surfaces as a runtime 401/404 from the API..env (PANDO_API_KEY) and reference via %env()%. Never commit it; the bundle will not mask it in exception traces, so avoid dumping the resolved config in dev.services.yaml.find/suggest in Symfony Cache (cache.app or a Redis pool) keyed by normalized query + filters, TTL of 60–300s. This alone can cut API spend dramatically.SearchClient using Laravel's Http facade or Symfony HttpClient, 50–100 lines, fully typed and under your control. That is a better long-term bet than reviving a dead bundle.services.yaml override) or forking. Don't plan an architecture that assumes you can extend it cleanly.How can I help you explore Laravel packages today?