This guide explains how to create a custom adapter for the UX Search Bundle. Before starting, consider opening an issue to discuss if your adapter might be valuable for the project - we can work together to include it!
Create a custom adapter when you need to integrate with:
- External REST APIs
- Search engines not yet supported (Elasticsearch, Solr, etc.)
- Custom data sources (CSV, XML, external databases)
- Third-party search services
An adapter consists of two parts:
- Adapter (implements
AdapterInterface) - Performs the actual search - Factory (implements
AdapterFactoryInterface) - Creates adapter instances from DSN strings
- User submits search query
- Bundle calls
factory->createAdapter($dsn)to get adapter instance - Bundle calls
adapter->search($query, $search)with search parameters - Adapter returns
ResultSetwith hits, facets, and pagination data - Bundle renders results in Twig components
The adapter implements AdapterInterface with two methods:
<?php
declare(strict_types=1);
namespace App\Search\Adapter;
use Mezcalito\UxSearchBundle\Adapter\AdapterInterface;
use Mezcalito\UxSearchBundle\Search\Query;
use Mezcalito\UxSearchBundle\Search\ResultSet\ResultSet;
use Mezcalito\UxSearchBundle\Search\SearchInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
final readonly class MyAdapter implements AdapterInterface
{
public function search(Query $query, SearchInterface $search): ResultSet
{
// Your logic need to return a ResultSet
}
public function configureParameters(OptionsResolver $resolver): void
{
// Define available parameters that users can set via setAdapterParameters()
$resolver->setDefaults([
'my_param' => 'my_value',
]);
$resolver->setAllowedTypes('my_param', 'string');
}
}The factory creates adapter instances from DSN strings:
<?php
declare(strict_types=1);
namespace App\Search\Adapter;
use Mezcalito\UxSearchBundle\Adapter\AdapterFactoryInterface;
use Mezcalito\UxSearchBundle\Adapter\AdapterInterface;
final readonly class MyAdapterFactory implements AdapterFactoryInterface
{
public function support(string $dsn): bool
{
return str_starts_with($dsn, 'myAdapter'); // Or your own logic
}
public function createAdapter(string $dsn): AdapterInterface
{
// Your own logic
return new MyAdapter();
}
}Add your adapter to the configuration:
# config/packages/mezcalito_ux_search.yaml
mezcalito_ux_search:
default_adapter: 'myAdapter'
adapters:
myAdapter: 'myAdapter://what_you_need'<?php
namespace App\Search;
use App\Search\Adapter\RestApiAdapter;
use Mezcalito\UxSearchBundle\Attribute\AsSearch;
use Mezcalito\UxSearchBundle\Search\AbstractSearch;
#[AsSearch('products', adapter: 'my_api')]
class ProductSearch extends AbstractSearch
{
public function build(array $options = []): void
{
// Your logic
}
}Represents a single search result:
use Mezcalito\UxSearchBundle\Search\ResultSet\Hit;
$hit = new Hit(
data: ['id' => 1, 'name' => 'Product', 'price' => 29.99],
score: 0.95
);For facets with discrete values (brands, categories, etc.):
use Mezcalito\UxSearchBundle\Search\ResultSet\FacetTermDistribution;
$facet = new FacetTermDistribution();
$facet->setProperty('brand');
$facet->setValues([
'Nike' => 45, // 45 products
'Adidas' => 32, // 32 products
'Puma' => 18, // 18 products
]);
$facet->setCheckedValues(['Nike']); // User selected NikeFor numeric range facets (price, rating, etc.):
use Mezcalito\UxSearchBundle\Search\ResultSet\FacetStat;
$stat = new FacetStat(
property: 'price',
min: 0, // Minimum value in dataset
max: 999, // Maximum value in dataset
userMin: 10, // User selected minimum (optional)
userMax: 500, // User selected maximum (optional)
);The complete search result:
use Mezcalito\UxSearchBundle\Search\ResultSet\ResultSet;
$resultSet = new ResultSet();
$resultSet->setTotalResults(150);
$resultSet->setHits($hits);
$resultSet->setFacetDistributions($facetDistributions);
$resultSet->setFacetStats($facetStats);Use a clear, consistent DSN format:
scheme://credentials@host:port/path?options
Examples:
elasticsearch://user:pass@localhost:9200/myindexsolr://admin:secret@solr.example.com:8983/solr/productscsv://path/to/file.csvapi://apikey@api.example.com/v1
- Check existing adapter implementations in
src/Adapter/ - Ask questions in GitHub Issues
- Consider contributing your adapter to the bundle!