Search
Baander uses PGroonga (a PostgreSQL extension for full-text search) to power search across catalog entities (albums, artists, songs, movies, videos). Search is integrated at the repository level via the PgroongaSearchTrait.
How It Works
- A controller receives a search query and constructs
SearchOptions - The repository method uses
PgroongaSearchTraitto build a PGroonga query - PGroonga performs full-text search with scoring (
pgroonga_score) - Results are returned as domain models, sorted by relevance
PGroonga indexes text columns using a GIN-like index. The &@~ operator performs full-text matching, and pgroonga_score() returns a relevance score for each result row.
Making a Context Searchable
1. Implement the Searchable interface
Add extends Searchable to the repository interface:
// src/Catalog/Domain/Repository/AlbumRepositoryInterface.php
interface AlbumRepositoryInterface extends Searchable
{
// ... existing methods
}
The Searchable interface requires a search(SearchOptions $options): SearchResult method.
2. Use PgroongaSearchTrait in the repository implementation
The trait provides two query builders:
buildScoredQuery()— returns entities with relevance scores (for search results)buildFilterQuery()— applies text matching as a filter within a cursor-paginated listing
// src/Catalog/Infrastructure/Doctrine/Repository/AlbumRepository.php
final class AlbumRepository implements AlbumRepositoryInterface
{
use PgroongaSearchTrait;
public function search(SearchOptions $options): SearchResult
{
$result = $this->buildScoredQuery(
options: $options,
em: $this->entityManager,
entityClass: AlbumEntity::class,
tableName: 'catalog_album',
searchColumn: 's.title',
);
$models = array_map(
fn(AlbumEntity $entity) => $this->toDomain($entity),
$result['entities'],
);
return new SearchResult(
items: $models,
total: $result['total'],
);
}
}
3. Add a PGroonga index to the migration
CREATE INDEX idx_album_title ON catalog_album USING pgroonga (title);
SearchOptions
The SearchOptions value object carries the search parameters:
| Property | Type | Description |
|---|---|---|
query |
string |
The search text |
limit |
int |
Maximum results to return |
offset |
int |
Number of results to skip |
filters |
array |
Additional filter criteria |
When query is empty, buildFilterQuery() returns all items (no text filter applied). This makes the same repository method work for both search and listing.
See Also
- Architecture — bounded context overview
- Coding Conventions — repository patterns
- API Reference — cursor and offset pagination for search results