bambi/bambi-postgres-text-search-bundle
Installation
composer require bambi/bambi-postgres-text-search-bundle
Ensure your project uses Symfony 5.4+ and PostgreSQL 12+.
Enable the Bundle
Add to config/bundles.php:
return [
// ...
Bambi\PostgresTextSearchBundle\BambiPostgresTextSearchBundle::class => ['all' => true],
];
Configure API-Platform Filter
Register the filter in config/packages/api_platform.yaml:
api_platform:
formats:
jsonld:
mime_types: ['application/ld+json']
filters:
text_search:
type: Bambi\PostgresTextSearchBundle\Filter\TextSearchMatchFilter
properties:
- { name: 'name', type: 'string' }
- { name: 'isbn', type: 'string' }
- { name: 'author.name', type: 'string' }
First Use Case
Query your API with a ts_query parameter (or your custom name):
GET /api/books?postgres_text_search=john%20&postgres_text_search=king
This searches for books where name, isbn, or author.name contains "john" and "king".
Basic Text Search
Use the default ts_query syntax for PostgreSQL full-text search:
GET /api/books?ts_query=('book' & 'fiction') | 'author'
& = AND, | = OR, ! = NOT.Nested Entity Search
Search across related entities (e.g., author.name):
# config/services.yaml
app.book.text_search_filter:
class: Bambi\PostgresTextSearchBundle\Filter\TextSearchMatchFilter
arguments:
$properties:
- name
- isbn
- author.name # Searches Author#name
Custom Parameter Names
Override the default ts_query parameter name:
# config/services.yaml
app.book.custom_search_filter:
class: Bambi\PostgresTextSearchBundle\Filter\TextSearchMatchFilter
arguments:
$textSearchParameterName: 'custom_search'
Query with:
GET /api/books?custom_search=postgres
Integration with API-Platform
Apply the filter to a specific operation in src/Entity/Book.php:
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\GetCollection;
#[GetCollection(
filters: ['text_search'],
uriTemplate: '/books{?postgres_text_search[]}'
)]
Dynamic Property Mapping Use a service to dynamically define searchable properties:
// src/Service/SearchPropertyProvider.php
class SearchPropertyProvider
{
public function getSearchableProperties(): array
{
return [
'name',
'isbn',
'author.name',
'publisher.city' // Supports multi-level relations
];
}
}
Inject into the filter:
app.book.dynamic_filter:
class: Bambi\PostgresTextSearchBundle\Filter\TextSearchMatchFilter
arguments:
$properties: '@=service("App\\Service\\SearchPropertyProvider").getSearchableProperties()'
PostgreSQL Version Mismatch
pg_config --version.Case Sensitivity
websearch_to_tsquery('english', 'café') for diacritic-insensitive searches.arguments:
$config: "'websearch'::regconfig" # Diacritic-insensitive
Query Syntax Errors
ts_query syntax (e.g., unbalanced parentheses) will throw a 500 error.// Dump the generated query in a filter
$this->logger->debug('Generated SQL: ' . $query->getSQL());
Performance with Large Datasets
CREATE INDEX idx_books_gin_search ON books USING GIN (
to_tsvector('english', name || ' ' || isbn || ' ' || author->>'name')
);
Nested Relations Limitation
author.publisher.country).API-Platform Caching Issues
cache: false in metadata.Log Generated Queries
Enable Doctrine logging in config/packages/dev/doctrine.yaml:
doctrine:
dbal:
logging: true
profiling: true
Check logs for the raw SQL query.
Test with pgAdmin
Manually test your ts_query in pgAdmin:
SELECT * FROM books
WHERE to_tsvector('english', name || ' ' || isbn) @@ to_tsquery('english', 'john & king');
Validate ts_query Syntax
Use PostgreSQL’s plainto_tsquery for debugging:
SELECT plainto_tsquery('english', 'john & king');
NULL if syntax is invalid.Custom Scoring
Extend the filter to include ranking (e.g., ts_rank):
// src/Filter/CustomTextSearchFilter.php
class CustomTextSearchFilter extends TextSearchMatchFilter
{
protected function modifyQueryBeforeDql(string $dql, QueryBuilder $qb): string
{
$dql = parent::modifyQueryBeforeDql($dql, $qb);
$dql = str_replace(
'WHERE',
'WHERE ts_rank(to_tsvector(''english'', ...), plainto_tsquery(''english'', :ts_query)) > 0',
$dql
);
return $dql;
}
}
Add Highlighting
Use PostgreSQL’s ts_headline for snippets:
$qb->addSelect("ts_headline(to_tsvector('english', b.name), plainto_tsquery('english', :ts_query)) as highlight");
Support for Multiple Languages Override the config per filter:
app.book.german_filter:
arguments:
$config: "'german'::regconfig"
Integration with Elasticsearch Use the bundle for hybrid search (PostgreSQL + Elasticsearch):
if ($postgresResults->count() < 10) {
$elasticsearchResults = $elasticsearchClient->search(...);
return $postgresResults->merge($elasticsearchResults);
}
How can I help you explore Laravel packages today?