snowcap/elastica-bundle
A Symfony bundle that integrates the Elastica Elasticsearch client, offering configuration, service wiring, and tooling for indexing and searching documents. It simplifies setting up indexes, mappings, and queries in Elasticsearch-powered PHP applications.
SnowcapElasticaBundle is a Symfony 2/3-era integration between the Elastica PHP client and Symfony's DI container. It wires clients, indexes, types, and populating/search services into the container so you don't instantiate Elastica by hand.
Reality check first: the last tagged release is from June 2015, it has 1 star and no dependents. You are only here because you inherited a legacy project. Confirm your PHP (5.5–7.0) and Elasticsearch (1.x/2.x) versions before touching anything — modern Elasticsearch 7+ will reject this client entirely.
Minimal setup in a Symfony 2/3 app:
composer require snowcap/elastica-bundle
Register in AppKernel.php:
new Snowcap\ElasticaBundle\SnowcapElasticaBundle(),
Configure a client, an index, and a type in config.yml:
snowcap_elastica:
clients:
default:
host: 127.0.0.1
port: 9200
indexes:
app:
client: default
index_name: app
types:
product:
mappings:
name: { type: string }
price: { type: float }
persistence:
driver: orm
model: AppBundle\Entity\Product
provider: ~
listener: ~
First use case: index a Doctrine entity and query it. Run the populate command that ships with the bundle (snowcap:elastica:populate), then inject the generated snowcap_elastica.index.app.product service and call createQuery() / search().
Look first at the generated service IDs (snowcap_elastica.client.*, .index.*, .type.*) — they are the whole public API surface of the bundle. If the service is in debug:container, the wiring worked.
Query via the injected type service, not by rebuilding the client:
class ProductSearch
{
private $type;
public function __construct(\Elastica\Type $type)
{
$this->type = $type;
}
public function find($term)
{
$query = new \Elastica\Query();
$query->setQuery(new \Elastica\Query\Match('name', $term));
return $this->type->search($query);
}
}
Wire it in services.yml with @snowcap_elastica.index.app.product.
Persistence drivers for Doctrine. The persistence block has three drivers worth knowing:
orm — maps a Doctrine entity and indexes it on postPersist/postUpdate/postRemove when listener: ~ is set.propel — same idea for Propel.custom / provider: your.service — you implement ProviderInterface and control what gets indexed.For anything non-trivial (aggregated results, nested objects, computed fields) use the provider path and a custom transformer; the built-in ORM listener indexes only the entity's raw columns.
Populate and reindex workflow. The bundle exposes population commands. Typical day-to-day loop is: change mappings → drop and recreate the index → repopulate. Because mappings can't be changed in place on ES 1.x/2.x without reindexing, script it:
app/console snowcap:elastica:populate --index=app --type=product
If you need to alias between old/new indexes during a reindex, do it via Elastica\Index and Elastica\Client directly — the bundle's populate command doesn't manage aliases for you.
Pagination. The bundle ships a paginator for Elastica results; use it instead of hand-slicing ResultSet:
$paginator = new \Snowcap\ElasticaBundle\Paginator\Paginator($resultSet);
$paginator->setCurrentPage($page)->setMaxPerPage(20);
Search + hydration. For read models, use the type's search() and hydrate with your own normalizer keyed on the Doctrine entity — the bundle does not serialize results back to entities. Keep the hydration in a dedicated service so both the controller and CLI consumer agree on shape.
Testing. In tests, replace the type service with a mock/stub via the container (set('snowcap_elastica.index.app.product', $fakeType)), or point the client at an in-memory/fake Elasticsearch. Do not spin up a real cluster for unit tests — the Elastica client is not test-friendly with connection failures.
Version pinning is the trap. Elastica ~1.x/2.x lines map to specific ES server versions. If composer pulls a newer Elastica than the bundle was written against, mappings silently fail. Pin both: "snowcap/elastica-bundle": "~1.x" and a matching "ruflin/elastica". Check the bundle's own composer.json in vendor for the constraint it expects.
Config quirks. The YAML tree is strict: indexes.*.types.*.mappings must be the raw ES mapping DSL, but persistence is a separate sibling key. Mixing them (putting _source, properties, etc. at the wrong level) produces confusing "unknown option" DI errors rather than ES errors. Validate with app/console config:dump-reference snowcap_elastica before debugging further.
Listener ordering. The Doctrine postPersist/postUpdate listeners fire before your transaction commits. If indexing fails, you get partial index state with committed DB state. For bulk paths use the populate command with batching (--batch-size) rather than relying on per-entity listeners — it uses Type::addDocuments() and is far faster.
Debugging. Enable Elastica's logger in the client config (logging: true / logger: @logger) and watch the query body Symfony dumps. Nine times in ten an empty result set is a bad mapping (analyzed vs not_analyzed) or a mismatched field name foo vs foo.raw.
Index name collisions. The bundle prefixes index names with the configured index_name; if you set one without a suffix for env, dev and prod share the same index. Add an env suffix (app_%kernel.environment%) or you'll clobber production during local reindex.
Extension points are DI-level, not code-level. There are no events or extension classes worth relying on. To customize, either decorate the injected Elastica\Type service with your own wrapper, or bypass the bundle entirely for complex queries and inject Elastica\Client directly. The bundle gives you wiring, not an ORM.
Migration guidance. This bundle was effectively superseded by friendsofsymfony/elastica-bundle, which is actively maintained. Day-to-day, keep the Snowcap services you already use (index/type injection, populate command) and treat them as thin wrappers; when you must upgrade Elasticsearch, migrate to FOSElasticaBundle (or the official client) and move the custom provider/transformer code over — that is the only Snowcap-specific logic that isn't trivially recreated.
How can I help you explore Laravel packages today?