Installation
composer require friendsofsymfony/elastica-bundle
Add to config/bundles.php:
return [
// ...
FriendsOfSymfony\ElasticaBundle\FriendsOfSymfonyElasticaBundle::class => ['all' => true],
];
Configure Elasticsearch
Edit config/packages/fos_elastica.yaml:
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
app:
types:
product:
properties:
name: ~
price: ~
persistence:
driver: orm
model: App\Entity\Product
provider: ~
listener: ~
First Use Case
Create an entity (e.g., Product) and annotate it with @Elastica\Document:
use Elastica\Annotation as Elastica;
/**
* @Elastica\Document(repositoryClass="App\Repository\ProductElasticaRepository")
*/
class Product {}
Run:
php bin/console fos:elastica:populate
Indexing Entities Use Doctrine listeners for automatic indexing:
# config/packages/fos_elastica.yaml
fos_elastica:
indexes:
app:
types:
product:
persistence:
listener: true # Auto-index on CRUD
Custom Mappings Override default mappings via YAML or PHP:
properties:
name:
type: text
analyzer: custom_analyzer
Searching Inject the repository and query:
use FriendsOfSymfony\ElasticaBundle\Finder\PaginatedFinderInterface;
class ProductSearchService {
public function __construct(private PaginatedFinderInterface $finder) {}
public function search(string $query): array {
return $this->finder->find('app', Product::class, ['query_string' => ['query' => $query]]);
}
}
Bulk Operations
Use the fos:elastica:populate command with --batch-size for large datasets:
php bin/console fos:elastica:populate --batch-size=100
@Serializer\SerializedName for field mapping.ElasticaIndexEvent for pre/post-indexing logic.Elastica\Provider\ProviderInterface for non-Doctrine data.Mapping Conflicts
type after initial index creation.php bin/console fos:elastica:remove-index
php bin/console fos:elastica:create-index
Listener Overhead
listener: true) trigger on every Doctrine event (save/remove/flush).@Elastica\Index annotations selectively.Propel Limitation
Pagination Quirks
PaginatedFinder uses Elasticsearch’s from/size. For large datasets, prefer search_after:
$finder->find('app', Product::class, ['query' => $query, 'sort' => ['price']]);
php bin/console fos:elastica:status
fos_elastica.yaml:
client: { debug: true }
NoHandlerFoundException: Verify model and repositoryClass in YAML match your entity.ConnectionRefused: Confirm Elasticsearch is running (http://localhost:9200).Custom Indexers
Extend Elastica\Indexer\IndexerInterface for custom logic:
class CustomIndexer implements IndexerInterface {
public function indexDocument(DocumentInterface $document, $data) { ... }
}
Register in services.yaml:
services:
App\Indexer\CustomIndexer:
tags: [fos_elastica.indexer]
Dynamic Mappings
Use fos_elastica.dynamic_mapping event to modify mappings at runtime:
$event->setMapping(['dynamic' => 'strict']);
Async Indexing Offload indexing with Symfony Messenger:
use FriendsOfSymfony\ElasticaBundle\Indexer\IndexerInterface;
class IndexMessage implements MessageInterface {
public function __construct(private IndexerInterface $indexer, private $entity) {}
public function __invoke() { $this->indexer->index($this->entity); }
}
How can I help you explore Laravel packages today?