Installation:
composer require chub/search-bundle
(Note: The README mentions Git installation, but Composer is preferred for modern Symfony.)
Register the Bundle:
Add to config/bundles.php (Symfony 4+):
return [
// ...
ChubProduction\SearchBundle\SearchBundle::class => ['all' => true],
];
First Use Case:
Create a simple SearchProvider for a model (e.g., Post):
use ChubProduction\SearchBundle\Provider\SearchProviderInterface;
class PostSearchProvider implements SearchProviderInterface
{
public function getSearchableFields()
{
return ['title', 'content']; // Fields to search in
}
public function search($query, $page = 1, $perPage = 10)
{
// Implement logic to fetch posts matching $query
// Return a paginated result (e.g., Doctrine Paginator)
}
}
Service Configuration:
Register the provider in config/services.yaml:
services:
App\Search\PostSearchProvider:
tags: ['search.provider']
Trigger a Search:
Inject the SearchService (auto-registered) and call:
$results = $searchService->search('query', 'post'); // 'post' is the provider key
Provider-Based Architecture:
Post, User) has a dedicated SearchProvider.SearchProviderInterface with:
getSearchableFields(): Define which fields to index/search.search($query, $page, $perPage): Return paginated results.Integration with Controllers:
use ChubProduction\SearchBundle\Search\SearchService;
class SearchController extends AbstractController
{
public function index(SearchService $search, Request $request)
{
$query = $request->query->get('q');
$results = $search->search($query, 'post'); // 'post' = provider key
return $this->render('search/index.html.twig', ['results' => $results]);
}
}
Twig Integration: Pass results to Twig and loop:
{% for result in results %}
<h2>{{ result.title }}</h2>
<p>{{ result.content|truncate(100) }}</p>
{% endfor %}
Pagination:
The bundle expects providers to return objects supporting getCurrentPage(), setCurrentPage(), etc. (e.g., KnpPaginator, Doctrine Paginator).
Dynamic Provider Loading:
Use the SearchService to dynamically load providers by key:
$searchService->addProvider('dynamic_key', new DynamicSearchProvider());
Combining Results: Merge results from multiple providers:
$posts = $searchService->search('query', 'post');
$users = $searchService->search('query', 'user');
$combined = array_merge($posts->getResults(), $users->getResults());
Custom Search Logic:
Extend SearchService or override provider methods for complex queries (e.g., full-text search with Solr/Elasticsearch).
Caching:
Cache search results in the provider’s search() method:
$cacheKey = md5($query . $page);
return $cache->get($cacheKey, function() use ($query, $page) {
return $this->fetchFromDatabase($query, $page);
});
Provider Key Mismatch:
search() (e.g., 'post') matches the service ID in services.yaml.SearchService::getProviders() to list registered keys.Pagination Issues:
getResults(), getTotalItems()).$paginator = $this->getDoctrine()
->getManager()
->getRepository(Post::class)
->createQueryBuilder('p')
->where('p.title LIKE :query')
->setParameter('query', "%$query%")
->getQuery()
->getResult();
$paginator = new Paginator($paginator, $fetchJoinCollection = true);
Field Matching:
getSearchableFields() returns an array of field names, not database columns. Ensure these match your entity properties or query logic.Case Sensitivity:
LOWER() or ILIKE in your provider’s query:
->where('LOWER(p.title) LIKE LOWER(:query)')
Log Provider Calls:
Override SearchService to log queries:
public function search($query, $providerKey)
{
$this->logger->debug(sprintf('Searching "%s" in provider "%s"', $query, $providerKey));
return parent::search($query, $providerKey);
}
Validate Results: Check if results are empty due to:
getSearchableFields().Performance:
%query%) in large tables. Use prefix searches (query%) or full-text indexes.EXPLAIN (Doctrine) or Xdebug.Custom Search Service:
Extend SearchService to add features like:
title:query for field-specific searches).Event Listeners:
Attach listeners to search.before/search.after events (if the bundle supports them; check source for events).
Alternative Data Sources: Replace the default provider logic to integrate with:
Elasticsearch\Client in the provider).Configuration:
Override bundle parameters in config/packages/chub_search.yaml (if supported). Example:
chub_search:
default_provider: 'post' # Set a default provider key
timeout: 5 # Search timeout (seconds)
Use DTOs for Results: Transform raw entities into Data Transfer Objects (DTOs) in the provider to decouple search logic from your domain model.
Highlight Matches: Modify the provider to return highlighted snippets:
public function search($query, $page = 1, $perPage = 10)
{
$results = $this->fetchFromDatabase($query, $page, $perPage);
foreach ($results as $result) {
$result->setHighlightedContent($this->highlightMatches($result->content, $query));
}
return $results;
}
Autocomplete:
Add a suggest() method to providers for type-ahead:
public function suggest($query, $limit = 5)
{
return $this->getEntityManager()
->createQueryBuilder()
->select('p.title')
->from(Post::class, 'p')
->where('p.title LIKE :query')
->setParameter('query', "$query%")
->setMaxResults($limit)
->getQuery()
->getResult();
}
Symfony Messenger:
For async search (e.g., indexing), dispatch a SearchQueryMessage via Messenger and process it in a worker.
How can I help you explore Laravel packages today?