Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ngstack 843 document mapper elastic search #12

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bundle/NetgenIbexaSearchExtraBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ public function build(ContainerBuilder $container): void
$container->addCompilerPass(new Compiler\FieldType\RichTextIndexablePass());
$container->addCompilerPass(new Compiler\SearchResultExtractorPass());
$container->addCompilerPass(new Compiler\RawFacetBuilderDomainVisitorPass());
$container->addCompilerPass(new Compiler\ElasticsearchExtensibleDocumentFactoryPass());
}
}
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
},
"suggest": {
"netgen/ibexa-site-api": "Boost your site-building productivity with Ibexa CMS",
"ibexa/solr": "Supports advanced capabilities with Ibexa search API"
"ibexa/solr": "Supports advanced capabilities with Ibexa search API",
"ibexa/elasticsearch": "Supports advanced capabilities with Ibexa search API"
},
"autoload": {
"psr-4": {
Expand Down
32 changes: 32 additions & 0 deletions docs/reference/document_mapper.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
DocumentFactory
===============
DocumentFactory is an implementation of field mappers for Elasticsearch modeled after the Solr implementation using the
template method pattern. It implements the Elasticsearch ``DocumentFactoryInterface`` and its methods ``fromContent()``
and ``fromLocation()`` add fields to document. These methods index the fields from the suitable field mappers.

``DocumentFactory`` depends on the elasticsearch service ``DocumentFactoryInterface`` , so it is registered in a
Compiler Pass since elasticsearch is not available directly from the bundle.

To use this feature, ensure you have elasticsearch bundle added to your project.

The ``DocumentFactory`` service uses all base field mapper services to index content into the correct document
(content, location, or translation-dependent document)::

ContentFieldMapper
LocationFieldMapper
ContentTranslationFieldMapper
LocationTranslationFieldMapper
BlockFieldMapper
BlockTranslationFieldMapper

These services are abstract classes containing methods ``accept()`` and ``mapFields()`` which are implemented by new
field mappers as needed.

To add a new field mapper, create a class that extends one of the base field mappers above, implements its methods, and
registers the service with one of the following tags, depending on the base field mapper::

netgen.ibexa_search_extra.elasticsearch.field_mapper.content
netgen.ibexa_search_extra.elasticsearch.field_mapper.location
netgen.ibexa_search_extra.elasticsearch.field_mapper.content_translation
netgen.ibexa_search_extra.elasticsearch.field_mapper.location_translation
netgen.ibexa_search_extra.elasticsearch.field_mapper.block_translation
2 changes: 2 additions & 0 deletions docs/reference/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ Reference
subdocuments
spellcheck_suggestions
extra_fields
asynchronous_indexing
document_mapper

.. include:: /reference/map.rst.inc
1 change: 1 addition & 0 deletions docs/reference/map.rst.inc
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
* :doc:`/reference/subdocuments`
* :doc:`/reference/spellcheck_suggestions`
* :doc:`/reference/extra_fields`
* :doc:`/reference/document_mapper`
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

namespace Netgen\IbexaSearchExtra\Container\Compiler;

use Ibexa\Contracts\Core\Persistence\Content\Handler;
use Ibexa\Elasticsearch\DocumentMapper\DocumentFactoryInterface;
use Netgen\IbexaSearchExtra\Core\Search\Elasticsearch\DocumentMapper\DocumentFactory;
use Netgen\IbexaSearchExtra\Core\Search\Elasticsearch\Query\CriterionVisitor\Content\VisibilityVisitor as ContentVisibilityVisitor;
use Netgen\IbexaSearchExtra\Core\Search\Elasticsearch\Query\CriterionVisitor\Location\VisibilityVisitor as LocationVisibilityVisitor;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

use function array_keys;
use function sprintf;

/**
* This compiler pass will register elastic search field mappers.
*/
final class ElasticsearchExtensibleDocumentFactoryPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
$this->processVisitors($container, 'block_translation');
$this->processVisitors($container, 'block');
$this->processVisitors($container, 'content');
$this->processVisitors($container, 'content_translation');
$this->processVisitors($container, 'location');
$this->processVisitors($container, 'location_translation');

$this->processDocumentFactory($container);

$container
->register(ContentVisibilityVisitor::class, ContentVisibilityVisitor::class)
->addTag('ibexa.search.elasticsearch.query.content.criterion.visitor');

$container
->register(LocationVisibilityVisitor::class, LocationVisibilityVisitor::class)
->addTag('ibexa.search.elasticsearch.query.location.criterion.visitor');
}

public function processDocumentFactory(ContainerBuilder $container): void
{
$container
->register(DocumentFactory::class, DocumentFactory::class)
->setDecoratedService(DocumentFactoryInterface::class)
->setArguments([
new Reference('.inner'),
new Reference(Handler::class),
new Reference('netgen.ibexa_search_extra.elasticsearch.field_mapper.content.aggregate'),
new Reference('netgen.ibexa_search_extra.elasticsearch.field_mapper.location.aggregate'),
new Reference('netgen.ibexa_search_extra.elasticsearch.field_mapper.content_translation.aggregate'),
new Reference('netgen.ibexa_search_extra.elasticsearch.field_mapper.location_translation.aggregate'),
new Reference('netgen.ibexa_search_extra.elasticsearch.field_mapper.block.aggregate'),
new Reference('netgen.ibexa_search_extra.elasticsearch.field_mapper.block_translation.aggregate'),
]);
}

private function processVisitors(ContainerBuilder $container, string $name): void
{
if (!$container->hasDefinition(sprintf('netgen.ibexa_search_extra.elasticsearch.field_mapper.%s.aggregate', $name))) {
return;
}

$aggregateDefinition = $container->getDefinition(
sprintf('netgen.ibexa_search_extra.elasticsearch.field_mapper.%s.aggregate', $name),
);

$this->registerMappers($aggregateDefinition, $container->findTaggedServiceIds(sprintf('netgen.ibexa_search_extra.elasticsearch.field_mapper.%s', $name)));
}

private function registerMappers(Definition $definition, array $mapperIds): void
{
foreach (array_keys($mapperIds) as $id) {
$definition->addMethodCall('addMapper', [new Reference($id)]);
}
}
}
22 changes: 22 additions & 0 deletions lib/Core/Search/Elasticsearch/DocumentMapper/BlockFieldMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Netgen\IbexaSearchExtra\Core\Search\Elasticsearch\DocumentMapper;

use Ibexa\Contracts\Core\Persistence\Content as SPIContent;

abstract class BlockFieldMapper
{
/**
* Indicates if the mapper accepts the given $content for mapping.
*/
abstract public function accept(SPIContent $content): bool;

/**
* Maps given $content to an array of search fields.
*
* @return \Ibexa\Contracts\Core\Search\Field[]
*/
abstract public function mapFields(SPIContent $content): array;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace Netgen\IbexaSearchExtra\Core\Search\Elasticsearch\DocumentMapper\BlockFieldMapper;

use Ibexa\Contracts\Core\Persistence\Content as SPIContent;
use Netgen\IbexaSearchExtra\Core\Search\Elasticsearch\DocumentMapper\BlockFieldMapper;

class Aggregate extends BlockFieldMapper
{
/**
* An array of aggregated field mappers.
*
* @var BlockFieldMapper[]
*/
protected $mappers = [];

/**
* @param blockFieldMapper[] $mappers
* An array of mappers
*/
public function __construct(array $mappers = [])
{
foreach ($mappers as $mapper) {
$this->addMapper($mapper);
}
}

/**
* Adds given $mapper to the internal array.
*/
public function addMapper(BlockFieldMapper $mapper): void
{
$this->mappers[] = $mapper;
}

public function accept(SPIContent $content): bool
{
return true;
}

public function mapFields(SPIContent $content): array
{
$fields = [];

foreach ($this->mappers as $mapper) {
if ($mapper->accept($content)) {
$fields = [...$fields, ...$mapper->mapFields($content)];
}
}

return $fields;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Netgen\IbexaSearchExtra\Core\Search\Elasticsearch\DocumentMapper;

use Ibexa\Contracts\Core\Persistence\Content as SPIContent;

abstract class BlockTranslationFieldMapper
{
/**
* Indicates if the mapper accepts the given $content for mapping.
*/
abstract public function accept(SPIContent $content, string $languageCode): bool;

/**
* Maps given $content to an array of search fields.
*
* @return \Ibexa\Contracts\Core\Search\Field[]
*/
abstract public function mapFields(SPIContent $content, string $languageCode): array;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace Netgen\IbexaSearchExtra\Core\Search\Elasticsearch\DocumentMapper\BlockTranslationFieldMapper;

use Ibexa\Contracts\Core\Persistence\Content as SPIContent;
use Netgen\IbexaSearchExtra\Core\Search\Elasticsearch\DocumentMapper\BlockTranslationFieldMapper;

class Aggregate extends BlockTranslationFieldMapper
{
/**
* An array of aggregated field mappers.
*
* @var BlockTranslationFieldMapper[]
*/
protected $mappers = [];

/**
* @param blockTranslationFieldMapper[] $mappers
* An array of mappers, sorted by priority
*/
public function __construct(array $mappers = [])
{
foreach ($mappers as $mapper) {
$this->addMapper($mapper);
}
}

/**
* Adds given $mapper to the internal array.
*/
public function addMapper(BlockTranslationFieldMapper $mapper): void
{
$this->mappers[] = $mapper;
}

public function accept(SPIContent $content, string $languageCode): bool
{
return true;
}

public function mapFields(SPIContent $content, string $languageCode): array
{
$fields = [];

foreach ($this->mappers as $mapper) {
if ($mapper->accept($content, $languageCode)) {
$fields = [...$fields, ...$mapper->mapFields($content, $languageCode)];
}
}

return $fields;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Netgen\IbexaSearchExtra\Core\Search\Elasticsearch\DocumentMapper;

use Ibexa\Contracts\Core\Persistence\Content as SPIContent;

abstract class ContentFieldMapper
{
/**
* Indicates if the mapper accepts the given $content for mapping.
*/
abstract public function accept(SPIContent $content): bool;

/**
* Maps given $content to an array of search fields.
*
* @return \Ibexa\Contracts\Core\Search\Field[]
*/
abstract public function mapFields(SPIContent $content): array;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace Netgen\IbexaSearchExtra\Core\Search\Elasticsearch\DocumentMapper\ContentFieldMapper;

use Ibexa\Contracts\Core\Persistence\Content as SPIContent;
use Netgen\IbexaSearchExtra\Core\Search\Elasticsearch\DocumentMapper\ContentFieldMapper;

class Aggregate extends ContentFieldMapper
{
/**
* An array of aggregated field mappers.
*
* @var ContentFieldMapper[]
*/
protected $mappers = [];

/**
* @param contentFieldMapper[] $mappers
* An array of mappers
*/
public function __construct(array $mappers = [])
{
foreach ($mappers as $mapper) {
$this->addMapper($mapper);
}
}

/**
* Adds given $mapper to the internal array.
*/
public function addMapper(ContentFieldMapper $mapper): void
{
$this->mappers[] = $mapper;
}

public function accept(SPIContent $content): bool
{
return true;
}

public function mapFields(SPIContent $content): array
{
$fields = [];

foreach ($this->mappers as $mapper) {
if ($mapper->accept($content)) {
$fields = [...$fields, ...$mapper->mapFields($content)];
}
}

return $fields;
}
}
Loading
Loading