PHPackages                             jolicode/elastically - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Search &amp; Filtering](/categories/search)
4. /
5. jolicode/elastically

ActiveLibrary[Search &amp; Filtering](/categories/search)

jolicode/elastically
====================

Opinionated Elastica based framework to bootstrap PHP and Elasticsearch implementations.

v2.2.0(4mo ago)2571.7M↓15.5%43[12 issues](https://github.com/jolicode/elastically/issues)[2 PRs](https://github.com/jolicode/elastically/pulls)1MITPHPPHP &gt;=8.0CI passing

Since Jun 3Pushed 2mo ago15 watchersCompare

[ Source](https://github.com/jolicode/elastically)[ Packagist](https://packagist.org/packages/jolicode/elastically)[ RSS](/packages/jolicode-elastically/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (10)Dependencies (19)Versions (33)Used By (1)

 [![Elastically](https://camo.githubusercontent.com/330c2edba85537f3599bc2bc52303606852252c86a6f6e1343bb57e49b0b039f/68747470733a2f2f6a6f6c69636f64652e636f6d2f6d656469612f6f726967696e616c2f6f73732f686561646572732f656c6173746963616c6c792e706e67)](https://github.com/jolicode/elastically)
 Elastically, your PHP Elasticsearch companion
 *###### Opinionated Elastica based framework to bootstrap PHP and Elasticsearch implementations.*
==================================================================================================================================================================================================================================================================================================================================================================================================================================================

[](#------elastically-your-php-elasticsearch-companion--opinionated-elastica-based-framework-to-bootstrap-php-and-elasticsearch-implementations)

[![PHP Version Require](https://camo.githubusercontent.com/a4f9e1436984a24278a0b4c5313141cf2ead249b3c895bd26901e5afa7e79aac/687474703a2f2f706f7365722e707567782e6f72672f6a6f6c69636f64652f656c6173746963616c6c792f726571756972652f706870)](https://packagist.org/packages/jolicode/elastically)[![Monthly Downloads](https://camo.githubusercontent.com/54eada81dab99d7bb5c0cea395ab9a7f0f181e8047db55521db2eabd0a8969a6/687474703a2f2f706f7365722e707567782e6f72672f6a6f6c69636f64652f656c6173746963616c6c792f642f6d6f6e74686c79)](https://packagist.org/packages/jolicode/elastically)

Opinionated [Elastica](https://github.com/ruflin/Elastica) based framework to bootstrap PHP and Elasticsearch implementations.

Main features:

- DTO are **first-class citizen**, you send PHP object as documents, and get objects back on search results, **like an ODM**;
- All indexes are versioned and aliased automatically;
- Mappings are done via YAML files, PHP or custom via `MappingProviderInterface`;
- Analysis is separated from mappings to ease reuse;
- 100% compatibility with [ruflin/elastica](https://github.com/ruflin/Elastica);
- Mapping migration capabilities with ReIndex;
- Symfony HttpClient compatible transport (**optional**);
- Symfony JsonStreamer support for faster serialization (**optional**);
- Tested with Elasticsearch 7, 8 and 9;
- Symfony support (**optional**):
    - See dedicated [chapter](#usage-in-symfony);
    - Tested with Symfony 5.4 to 7;
    - Symfony Messenger Handler support (with or without spool);

Important

Require PHP 8.0+ and Elasticsearch &gt;= 8

Works with **Elasticsearch 7** as well but is not officially supported by Elastica 8. Use with caution.

Version 2+ does not work with **OpenSearch** anymore due to restrictions added by Elastic on their client.

You can check the [changelog](CHANGELOG.md) and the [upgrade](UPGRADE.md) documents.

Installation
------------

[](#installation)

```
composer require jolicode/elastically

```

Demo
----

[](#demo)

Tip

If you are using Symfony, you can move to the Symfony [chapter](#usage-in-symfony)

Quick example of what the library do on top of Elastica:

```
// Your own DTO, or one generated by Jane (see below)
class Beer
{
    public string $foo;
    public string $bar;
}

use JoliCode\Elastically\Factory;
use JoliCode\Elastically\Model\Document;

// Factory object with Elastica options + new Elastically options in the same array
$factory = new Factory([
    // Where to find the mappings
    Factory::CONFIG_MAPPINGS_DIRECTORY => __DIR__.'/mappings',
    // What objects to find in each index
    Factory::CONFIG_INDEX_CLASS_MAPPING => [
        'beers' => Beer::class,
    ],
]);

// Class to perform request, same as the Elastica Client
$client = $factory->buildClient();

// Class to build Indexes
$indexBuilder = $factory->buildIndexBuilder();

// Create the Index in Elasticsearch
$index = $indexBuilder->createIndex('beers');

// Set the proper aliases
$indexBuilder->markAsLive($index, 'beers');

// Class to index DTO(s) in an Index
$indexer = $factory->buildIndexer();

$dto = new Beer();
$dto->bar = 'American Pale Ale';
$dto->foo = 'Hops from Alsace, France';

// Add a document to the queue
$indexer->scheduleIndex('beers', new Document('123', $dto));
$indexer->flush();

// Set parameters on the Bulk
$indexer->setBulkRequestParams([
    'pipeline' => 'covfefe',
    'refresh' => 'wait_for'
]);

// Force index refresh if needed
$indexer->refresh('beers');

// Get the Document (new!)
$results = $client->getIndex('beers')->getDocument('123');

// Get the DTO (new!)
$results = $client->getIndex('beers')->getModel('123');

// Perform a search
$results = $client->getIndex('beers')->search('alsace');

// Get the Elastic Document
$results->getDocuments()[0];

// Get the Elastica compatible Result
$results->getResults()[0];

// Get the DTO 🎉 (new!)
$results->getResults()[0]->getModel();

// Create a new version of the Index "beers"
$index = $indexBuilder->createIndex('beers');

// Slow down the Refresh Interval of the new Index to speed up indexation
$indexBuilder->slowDownRefresh($index);
$indexBuilder->speedUpRefresh($index);

// Set proper aliases
$indexBuilder->markAsLive($index, 'beers');

// Clean the old indices (close the previous one and delete the older)
$indexBuilder->purgeOldIndices('beers');

// Mapping change? Just call migrate and enjoy a full reindex (use the Task API internally to avoid timeout)
$newIndex = $indexBuilder->migrate($index);
$indexBuilder->speedUpRefresh($newIndex);
$indexBuilder->markAsLive($newIndex, 'beers');
```

Note

`scheduleIndex` is here called with `"beers"` index because the index was already created before. If you are creating a new index and want to index documents into it, you should pass the `Index` object directly.

*mappings/beers\_mapping.yaml*

```
# Anything you want, no validation
settings:
    number_of_replicas: 1
    number_of_shards: 1
    refresh_interval: 60s
mappings:
    dynamic: false
    properties:
        foo:
            type: text
            analyzer: english
            fields:
                keyword:
                    type: keyword
```

Configuration
-------------

[](#configuration)

This library add custom configurations on top of Elastica's:

### `Factory::CONFIG_MAPPINGS_DIRECTORY` (required with default configuration)

[](#factoryconfig_mappings_directory-required-with-default-configuration)

The directory Elastically is going to look for YAML.

When creating a `foobar` index, a `foobar_mapping.yaml` file is expected.

If an `analyzers.yaml` file is present, **all** the indices will get it.

### `Factory::CONFIG_INDEX_CLASS_MAPPING` (required)

[](#factoryconfig_index_class_mapping-required)

An array of index name to class FQN.

```
[
  'indexName' => My\AwesomeDTO::class,
]
```

### `Factory::CONFIG_MAPPINGS_PROVIDER`

[](#factoryconfig_mappings_provider)

An instance of `MappingProviderInterface`.

If this option is not defined, the factory will fall back to `YamlProvider` and will use `Factory::CONFIG_MAPPINGS_DIRECTORY` option.

There are two providers available in Elastically: `YamlProvider` and `PhpProvider`.

### `Factory::CONFIG_SERIALIZER` (optional)

[](#factoryconfig_serializer-optional)

A `SerializerInterface` compatible object that will be used on indexation.

*Default to Symfony Serializer with Object Normalizer.*

A faster alternative is to use Jane to generate plain PHP Normalizer, see below. Also, we recommend [customization to handle things like Date](https://symfony.com/doc/current/components/serializer.html#normalizers).

### `Factory::CONFIG_DENORMALIZER` (optional)

[](#factoryconfig_denormalizer-optional)

A `DenormalizerInterface` compatible object that will be used on search results to build your objects back.

If this option is not defined, the factory will fall back to `Factory::CONFIG_SERIALIZER` option.

### `Factory::CONFIG_SERIALIZER_CONTEXT_BUILDER` (optional)

[](#factoryconfig_serializer_context_builder-optional)

An instance of `ContextBuilderInterface` that build a serializer context from a class name.

If it is not defined, Elastically, will use a `StaticContextBuilder` with the configuration from `Factory::CONFIG_SERIALIZER_CONTEXT_PER_CLASS`.

### `Factory::CONFIG_SERIALIZER_CONTEXT_PER_CLASS` (optional)

[](#factoryconfig_serializer_context_per_class-optional)

Allow to specify the Serializer context for normalization and denormalization.

```
[
    Beer::class => ['attributes' => ['title']],
];
```

*Default to `[]`.*

### `Factory::CONFIG_BULK_SIZE` (optional)

[](#factoryconfig_bulk_size-optional)

When running indexation of lots of documents, this setting allow you to fine-tune the number of document threshold.

*Default to 100.*

### Using JsonStreamer for faster serialization (optional)

[](#using-jsonstreamer-for-faster-serialization-optional)

Elastically supports [Symfony JsonStreamer](https://symfony.com/doc/current/components/json_streamer.html) for faster serialization during indexation. JsonStreamer can significantly speed up the serialization process by streaming JSON directly without building intermediate data structures.

To use JsonStreamer:

1. Install the package:

```
composer require symfony/json-streamer

```

2. Add the `#[JsonStreamable]` attribute to your DTO classes:

```
use Symfony\Component\JsonStreamer\Attribute\JsonStreamable;

#[JsonStreamable]
class Beer
{
    public string $foo;
    public string $bar;
}
```

That's it! Elastically will automatically detect the JsonStreamer package and use it for any DTO that has the `#[JsonStreamable]` attribute. DTOs without the attribute will continue to use the standard Symfony Serializer.

Note

JsonStreamer is only used for serialization during indexation. Deserialization of search results still uses the standard Symfony Serializer denormalizer.

### `Factory::CONFIG_INDEX_PREFIX` (optional)

[](#factoryconfig_index_prefix-optional)

Add a prefix to all indexes and aliases created via Elastically.

*Default to `null`.*

Usage in Symfony
----------------

[](#usage-in-symfony)

### Configuration

[](#configuration-1)

You'll need to add the bundle in `bundles.php`:

```
// config/bundles.php
return [
    // ...
    JoliCode\Elastically\Bridge\Symfony\ElasticallyBundle::class => ['all' => true],
];
```

Then configure the bundle:

```
# config/packages/elastically.yaml
elastically:
    connections:
        # You can create multiple clients
        default:
            # Any Elastica option works here
            client:
                hosts:
                    - '127.0.0.1:9200'
                # Use HttpClient component
                transport_config:
                    http_client: 'Psr\Http\Client\ClientInterface'

            # Path to the mapping directory (in YAML)
            mapping_directory:       '%kernel.project_dir%/config/elasticsearch'

            # Size of the bulk sent to Elasticsearch (default to 100)
            bulk_size:               100

            # Mapping between an index name and a FQCN
            index_class_mapping:
                my-foobar-index:     App\Dto\Foobar

            # Configuration for the serializer
            serializer:
                # Fill a static context
                context_mapping:
                    foo:                 bar

            # If you want to add a prefix for your index in elasticsearch (you can still call it by its base name everywhere!)
            # prefix: '%kernel.environment%'
```

Finally, inject one of those service (autowirable) in you code where you need it:

```
JoliCode\Elastically\Client (elastically.default.client)
JoliCode\Elastically\IndexBuilder (elastically.default.index_builder)
JoliCode\Elastically\Indexer (elastically.default.indexer)

```

#### Advanced Configuration

[](#advanced-configuration)

##### Multiple Connections and Autowiring

[](#multiple-connections-and-autowiring)

If you define multiple connections, you can define a default one. This will be useful for autowiring:

```
elastically:
    default_connection: default
    connections:
        default: # ...
        another: # ...
```

To use class for other connection, you can use *Autowirable Types*. To discover them, run:

```
bin/console debug:autowiring elastically

```

##### Use a Custom Serializer Context Builder

[](#use-a-custom-serializer-context-builder)

```
elastically:
    default_connection: default
    connections:
        default:
            serializer:
                context_builder_service: App\Elastically\Serializer\ContextBuilder
                # Do not define "context_mapping" option anymore
```

##### Use a Custom Mapping provider

[](#use-a-custom-mapping-provider)

```
elastically:
    default_connection: default
    connections:
        default:
            mapping_provider_service: App\Elastically\MappingProvider
            # Do not define Factory::CONFIG_MAPPINGS_DIRECTORY option anymore
```

##### Using HttpClient as Transport

[](#using-httpclient-as-transport)

You can also use the Symfony HttpClient for all Elastica communications:

```
JoliCode\Elastically\Transport\HttpClientTransport: ~

JoliCode\Elastically\Client:
    arguments:
        $config:
            hosts:
                - '127.0.0.1:9200'
            transport_config:
                http_client: 'Psr\Http\Client\ClientInterface'
```

See the [official documentation on how to get a PSR-18 client](https://symfony.com/doc/current/http_client.html#psr-18-and-psr-17).

#### Reference

[](#reference)

You can run the following command to get the default configuration reference:

```
bin/console config:dump elastically

```

### Using Messenger for async indexing

[](#using-messenger-for-async-indexing)

Elastically ships with a default Message and Handler for Symfony Messenger.

Register the message in your configuration:

```
framework:
    messenger:
        transports:
            async: "%env(MESSENGER_TRANSPORT_DSN)%"

        routing:
            # async is whatever name you gave your transport above
            'JoliCode\Elastically\Messenger\IndexationRequest':  async

services:
    JoliCode\Elastically\Messenger\IndexationRequestHandler: ~
```

The `IndexationRequestHandler` service depends on an implementation of `JoliCode\Elastically\Messenger\DocumentExchangerInterface`, which isn't provided by this library. You must provide a service that implements this interface, so you can plug your database or any other source of truth.

Then from your code you have to call:

```
use JoliCode\Elastically\Messenger\IndexationRequest;
use JoliCode\Elastically\Messenger\IndexationRequestHandler;

$bus->dispatch(new IndexationRequest(Product::class, '1234567890'));

// Third argument is the operation, so for a "delete" add this argument:
// new IndexationRequest(Product::class, 'ref9999', IndexationRequestHandler::OP_DELETE);
```

#### Specifying index name

[](#specifying-index-name)

If you want to target multiples indexes, or create, populate then mark as live an index, you can specify a custom index name with the fourth argument (example uses named arguments):

```
use JoliCode\Elastically\Messenger\IndexationRequest;
use JoliCode\Elastically\Messenger\MultipleIndexationRequest;
use JoliCode\Elastically\Messenger\IndexationRequestHandler;

$bus->dispatch(new IndexationRequest(Product::class, '1234567890', targetIndex: 'products_future_index'));

// Or

$bus->dispatch(new MultipleIndexationRequest([
    new IndexationRequest(Product::class, '123', targetIndex: 'custom_index_1'),
    new IndexationRequest(Product::class, '123', targetIndex: 'custom_index_2'),
]));
```

And then consume the messages:

```
php bin/console messenger:consume async
```

### Grouping IndexationRequest in a spool

[](#grouping-indexationrequest-in-a-spool)

Sending multiple `IndexationRequest` during the same Symfony Request is not always appropriate, it will trigger multiple Bulk operations. Elastically provides a Kernel listener to group all the `IndexationRequest` in a single `MultipleIndexationRequest` message.

To use this mechanism, we send the `IndexationRequest` in a memory transport to be consumed and grouped in a really async transport:

```
messenger:
    transports:
        async: "%env(MESSENGER_TRANSPORT_DSN)%"
        queuing: 'in-memory:///'

    routing:
        'JoliCode\Elastically\Messenger\MultipleIndexationRequest': async
        'JoliCode\Elastically\Messenger\IndexationRequest': queuing
```

You also need to register the subscriber:

```
services:
    JoliCode\Elastically\Messenger\IndexationRequestSpoolSubscriber:
        arguments:
            - '@messenger.transport.queuing' # should be the name of the memory transport
            - '@messenger.default_bus'
        tags:
            - { name: kernel.event_subscriber }
```

Using Jane to build PHP DTO and fast Normalizers
------------------------------------------------

[](#using-jane-to-build-php-dto-and-fast-normalizers)

Install [JanePHP](https://jane.readthedocs.io/) json-schema tools to build your own DTO and Normalizers. All you have to do is setting the Jane-completed Serializer on the Factory:

```
$factory = new Factory([
    Factory::CONFIG_SERIALIZER => $serializer,
]);
```

Caution

Elastically is [not compatible with Jane &lt; 6](https://github.com/jolicode/elastically/issues/12).

To be done
----------

[](#to-be-done)

- some "todo" in the code
- optional Doctrine connector
- extra commands to monitor, update mapping, reindex... Commonly implemented tasks
- optional Symfony integration:
    - web debug toolbar!
- scripts / commands for common tasks:
    - auto-reindex when the mapping change, handle the aliases and everything
    - micro monitoring for cluster / indexes
    - health-check method

[![JoliCode is sponsoring this project](https://camo.githubusercontent.com/5dd64f06b500f261fcaac4e3594ca2d4efa01177f217c45a88835dc62140aaa2/68747470733a2f2f6a6f6c69636f64652e636f6d2f6d656469612f6f726967696e616c2f6f73732f666f6f7465722d6769746875622e706e673f7633)](https://jolicode.com/)

###  Health Score

65

—

FairBetter than 99% of packages

Maintenance81

Actively maintained with recent releases

Popularity59

Moderate usage in the ecosystem

Community33

Small or concentrated contributor base

Maturity75

Established project with proven stability

 Bus Factor2

2 contributors hold 50%+ of commits

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~86 days

Recently: every ~137 days

Total

29

Last Release

127d ago

Major Versions

v0.1-beta.2 → v1.02020-06-23

v1.9.2 → v2.0.02024-11-21

PHP version history (4 changes)v0.1-beta.1PHP ^7.2

v1.2.0PHP ^7.2 || ^8.0

v1.4.0PHP &gt;=7.4

v1.6.0PHP &gt;=8.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/ebbe48a3e30540f24739602c197ad1212aeb81af96077aed5eb2a300267306bd?d=identicon)[damienalexandre](/maintainers/damienalexandre)

![](https://www.gravatar.com/avatar/c6547f10fb51d1e84f1d5371fb44ee3593c7791432471f0b0adabfb29a18c507?d=identicon)[xavierlacot](/maintainers/xavierlacot)

---

Top Contributors

[![damienalexandre](https://avatars.githubusercontent.com/u/225704?v=4)](https://github.com/damienalexandre "damienalexandre (67 commits)")[![lyrixx](https://avatars.githubusercontent.com/u/408368?v=4)](https://github.com/lyrixx "lyrixx (63 commits)")[![jmsche](https://avatars.githubusercontent.com/u/3929498?v=4)](https://github.com/jmsche "jmsche (15 commits)")[![Korbeil](https://avatars.githubusercontent.com/u/944409?v=4)](https://github.com/Korbeil "Korbeil (9 commits)")[![VincentLanglet](https://avatars.githubusercontent.com/u/9052536?v=4)](https://github.com/VincentLanglet "VincentLanglet (8 commits)")[![jeremyFreeAgent](https://avatars.githubusercontent.com/u/176363?v=4)](https://github.com/jeremyFreeAgent "jeremyFreeAgent (3 commits)")[![GaryPEGEOT](https://avatars.githubusercontent.com/u/8696117?v=4)](https://github.com/GaryPEGEOT "GaryPEGEOT (3 commits)")[![MrYamous](https://avatars.githubusercontent.com/u/32437818?v=4)](https://github.com/MrYamous "MrYamous (3 commits)")[![tacman](https://avatars.githubusercontent.com/u/619585?v=4)](https://github.com/tacman "tacman (3 commits)")[![atapatel](https://avatars.githubusercontent.com/u/11732316?v=4)](https://github.com/atapatel "atapatel (2 commits)")[![jdreesen](https://avatars.githubusercontent.com/u/424602?v=4)](https://github.com/jdreesen "jdreesen (2 commits)")[![NicolasDievart](https://avatars.githubusercontent.com/u/4649817?v=4)](https://github.com/NicolasDievart "NicolasDievart (2 commits)")[![rogamoore](https://avatars.githubusercontent.com/u/5249624?v=4)](https://github.com/rogamoore "rogamoore (2 commits)")[![ternel](https://avatars.githubusercontent.com/u/359558?v=4)](https://github.com/ternel "ternel (2 commits)")[![benji07](https://avatars.githubusercontent.com/u/166890?v=4)](https://github.com/benji07 "benji07 (1 commits)")[![IssamRaouf](https://avatars.githubusercontent.com/u/24827501?v=4)](https://github.com/IssamRaouf "IssamRaouf (1 commits)")[![Nek-](https://avatars.githubusercontent.com/u/972456?v=4)](https://github.com/Nek- "Nek- (1 commits)")[![GuillaumeRoques](https://avatars.githubusercontent.com/u/9786870?v=4)](https://github.com/GuillaumeRoques "GuillaumeRoques (1 commits)")[![nicolassing](https://avatars.githubusercontent.com/u/619752?v=4)](https://github.com/nicolassing "nicolassing (1 commits)")[![plandolt](https://avatars.githubusercontent.com/u/922919?v=4)](https://github.com/plandolt "plandolt (1 commits)")

---

Tags

elasticaelasticsearchhacktoberfestphpsymfonysearchsymfonyelasticsearchelastica

###  Code Quality

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/jolicode-elastically/health.svg)

```
[![Health](https://phpackages.com/badges/jolicode-elastically/health.svg)](https://phpackages.com/packages/jolicode-elastically)
```

###  Alternatives

[shopware/platform

The Shopware e-commerce core

3.3k1.5M3](/packages/shopware-platform)[api-platform/core

Build a fully-featured hypermedia or GraphQL API in minutes!

2.6k48.1M236](/packages/api-platform-core)[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.3M152](/packages/sulu-sulu)[prestashop/prestashop

PrestaShop is an Open Source e-commerce platform, committed to providing the best shopping cart experience for both merchants and customers.

9.0k15.4k](/packages/prestashop-prestashop)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

595.2M386](/packages/shopware-core)[web-auth/webauthn-framework

FIDO2/Webauthn library for PHP and Symfony Bundle.

50570.7k1](/packages/web-auth-webauthn-framework)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
