Install the Bundle
composer require ayaou/db-search-bundle
Ensure your project meets the requirements: PHP 8.0+, Symfony 6.4+, Doctrine ORM.
Generate and Run Migration
php bin/console make:migration
php bin/console doctrine:migrations:migrate
This creates the db_search_index table.
Configure Indexes
Edit config/packages/db_search.yaml to define your indexes and entities:
db_search:
indexes:
main:
entities:
App\Entity\Post:
fields:
- title
- content
Reindex Data
php bin/console db-search:index:regenerate
This populates the db_search_index table with searchable data.
Search in Code
Inject the Searcher service and call search():
use Ayaou\DbSearchBundle\Searcher\Searcher;
$results = $searcher->search('query');
Implement a basic search endpoint in a controller:
#[Route('/search', name: 'app_search')]
public function search(Request $request, Searcher $searcher): JsonResponse
{
$query = $request->query->get('q');
$results = $searcher->search($query);
return $this->json($results);
}
Test with GET /search?q=test.
Define Indexes
Configure indexes in db_search.yaml to specify which entity fields to index. Example:
db_search:
indexes:
products:
entities:
App\Entity\Product:
fields:
- name
- description
- tags
users:
entities:
App\Entity\User:
fields:
- username
- email
Index Management
db-search:index:regenerate after adding new entities/fields.postPersist, postUpdate) to update the index dynamically. Example:
use Ayaou\DbSearchBundle\Event\IndexerEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
#[AsEventListener(event: IndexerEvent::class, method: 'onIndexUpdate')]
public function onIndexUpdate(IndexerEvent $event): void
{
$entity = $event->getEntity();
if ($entity instanceof App\Entity\Post) {
$event->markForIndexing();
}
}
Search Patterns
$results = $searcher->search('query');
// Search in 'products' index
$results = $searcher->search('query', 'products');
// Search for 'App\Entity\Product' only
$results = $searcher->search('query', [], 'App\Entity\Product');
$rawResults = $searcher->searchRaw('query');
Result Handling Process results in a controller or service:
foreach ($results as $result) {
$entity = $result['entity'];
$score = $result['score']; // Relevance score (if available)
$index = $result['index'];
}
$form = $this->createFormBuilder()
->add('query', TextType::class, ['label' => 'Search'])
->getForm();
return $this->json([
'data' => array_map(fn($r) => [
'id' => $r['entity']->getId(),
'title' => $r['entity']->getTitle(),
], $results),
]);
$cacheKey = 'search:' . md5($query);
$results = $cache->get($cacheKey, function() use ($searcher, $query) {
return $searcher->search($query);
});
Full-Text Search Limitations
FULLTEXT index requires a minimum word length (default: 4 characters). Configure in db_search.yaml:
db_search:
fulltext_min_word_length: 2 # Override default
pg_trgm) for full-text search. The bundle does not support this out-of-the-box.Entity ID Requirements
getId(). Avoid using UUIDs or composite keys without proper configuration.// In your entity
public function getId(): string
{
return $this->uuid->toString(); // For UUIDs
}
Indexing Performance
db-search:index:regenerate. Split into batches:
php bin/console db-search:index:regenerate --batch-size=1000
TEXT or LONGTEXT fields directly; use summarized versions (e.g., title instead of content).Case Sensitivity
utf8mb4_unicode_ci collation is case-insensitive by default. For case-sensitive searches, use utf8mb4_bin (requires manual table alteration).Field Type Restrictions
string, int, float, and bool fields are indexed. Convert other types (e.g., DateTime) to strings:
fields:
- publishedAt # Assumes getPublishedAt() returns a DateTime
Workaround in entity:
public function getPublishedAt(): string
{
return $this->publishedAt->format('Y-m-d');
}
Check Index Contents
Inspect the db_search_index table directly:
SELECT * FROM db_search_index WHERE indexed_text LIKE '%query%';
Or use the searchRaw method:
$raw = $searcher->searchRaw('query');
dd($raw); // Debug raw results
Verify Indexing
Ensure entities are being indexed by checking the entity_class and entity_id columns. If missing, confirm:
db_search.yaml.getId() method exists.db-search:index:regenerate.Query Logs Enable Doctrine query logging to debug search queries:
// In config/services.yaml
Doctrine\Bundle\DoctrineBundle\DoctrineBundle:
resources:
- "%kernel.project_dir%/config/doctrine.yaml"
parameters:
doctrine.dbal.logger: true
Custom Indexers
Extend the indexer to support custom logic. Implement Ayaou\DbSearchBundle\Indexer\IndexerInterface:
use Ayaou\DbSearchBundle\Indexer\IndexerInterface;
class CustomIndexer implements IndexerInterface
{
public function indexEntity(object $entity, string $indexName): string
{
// Custom indexing logic
return "custom indexed text";
}
}
Register as a service:
services:
App\Indexer\CustomIndexer:
tags: ['db_search.indexer']
Search Result Transformers
Modify how results are formatted. Create a service tagged as db_search.result_transformer:
use Ayaou\DbSearchBundle\Transformer\ResultTransformerInterface;
class CustomTransformer implements ResultTransformerInterface
{
public function transform(array $result): array
{
$result['custom_field'] = 'value';
return $result;
}
}
Event Listeners Listen to indexing events to trigger side effects:
use Ayaou\DbSearchBundle\Event\IndexerEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
#[AsEventListener(IndexerEvent::POST_INDEX)]
public function onPostIndex(IndexerEvent $event): void
{
// Log or notify after indexing
}
db_search.yaml must match exactly when searching:
indexes:
main: # Must use 'main' in searcher->search($query,
How can I help you explore Laravel packages today?