Skip to content

Elasticsearch

You should use this storage when you want a super fast search.

Setup

use Formal\ORM\{
    Manager,
    Adapter\Elasticsearch,
};
use Innmind\OperatingSystem\Factory;
use Innmind\Url\Url;

$os = Factory::build(); //(1)
$orm = Manager::of(
    Elasticsearch::of(
        $os->remote()->http(),
        Url::of('http://localhost:9200/'), //(2)
    ),
);
  1. See innmind/operating-system.
  2. If you use this exact url then you can omit this parameter.

Mapping

By default when you'll generate the mapping to create the Aggregate index (see below) Formal will adapt the field types for the PHP types it handles by default. For any other type it will use text.

To avoid that you can declare the mapping for your custom types. For the Name you could do:

NameType.php
use Formal\ORM\{
    Definition\Type,
    Adapter\Elasticsearch\ElasticsearchType,
};
use Formal\AccessLayer\Table\Column;

/**
 * @psalm-immutable
 * @implements Type<Name>
 */
final class NameType implements Type, ElasticsearchType
{
    public function elasticsearchType(): array
    {
        return [
            'type' => 'keyword', //(1)
            'index' => false,
        ];
    }

    public function normalize(mixed $value): null|string|int|bool
    {
        return $value->toString();
    }

    public function denormalize(null|string|int|bool $value): mixed
    {
        if (!\is_string($value)) {
            throw new \LogicException("'$value' is not a string");
        }

        return Name::of($value);
    }
}
  1. See the documentation for an exhaustive list of types you can use.

Creating indexes

To automatically create the index you can build a simple script like this:

show_create_tables.php
use Formal\ORM\{
    Definition\Aggregates,
    Definition\Types,
    Definition\Type\Support,
    Adapter\SQL\ShowCreateTable,
};
use Innmind\OperatingSystem\Factory;
use Innmind\Url\Url;

$os = Factory::build(); //(1)
$aggregates = Aggregates::of(Types::of(
    Support::class(Name::class, new NameType),
));
$url = Url::of('http://localhost:9200/');
$createIndex = CreateIndex::of(
    $os->remote()->http(),
    $aggregates,
    $url,
);
$dropIndex = DropIndex::of(
    $os->remote()->http(),
    $aggregates,
    $url,
);

$_ = $dropIndex(User::class)
    ->flatMap(static fn() => $createIndex(User::class))
    ->match(
        static fn() => null, // index available
        static fn() => throw new \RuntimeException('Unable to create User index'),
    );
  1. See innmind/operating-system.

Testing

If you want to test your app with integration tests against a real instance of Elasticsearch you should the Refresh HTTP transport. This decorator force Elasticsearch to apply changes directly to the index.

use Formal\ORM\{
    Manager,
    Adapter\Elasticsearch,
};
use Innmind\OperatingSystem\Factory;
use Innmind\Url\Url;

$os = Factory::build();
$orm = Manager::of(
    Elasticsearch::of(
        Elasticsearch\Refresh::of(
            $os->remote()->http(),
        ),
        Url::of('http://localhost:9200/'),
    ),
);

Limitations

While this storage has its usages don't forget about its limitations.