Skip to content

Solr document field mappers

You can use document field mappers to index additional data in the search engine.

The additional data can come from external sources (for example, the Personalization service), or from internal ones. An example of indexing internal data is indexing data through the Location hierarchy: from the parent Location to the child Location, or indexing child data on the parent Location. You can use this to find the content with full-text search, or to simplify a search in a complicated data model.

To do this effectively, you must understand how the data is indexed with the Solr search engine. Solr uses documents as a unit of data that is indexed. Documents are indexed per translation, as content blocks. A block is a nested document structure. When used in Ibexa DXP, a parent document represents content, and Locations are indexed as child documents of the content item. To avoid duplication, full-text data is indexed on the Content document only. Knowing this, you can index additional data by the following:

  • All block documents (meaning content and its Locations, all translations)
  • All block documents per translation
  • Content documents
  • Content documents per translation
  • Location documents

Additional data is indexed by implementing a document field mapper and registering it at one of the five extension points described above. You can create the field mapper class anywhere inside your bundle, as long as you register it as a Symfony service. There are three different field mappers. Each mapper implements two methods, by the same name, but accepting different arguments:

  • ContentFieldMapper
    • ::accept(Content $content)
    • ::mapFields(Content $content)
  • ContentTranslationFieldMapper
    • ::accept(Content $content, $languageCode)
    • ::mapFields(Content $content, $languageCode)
  • LocationFieldMapper
    • ::accept(Location $content)
    • ::mapFields(Location $content)

Mappers can be used on the extension points by registering them with the service container by using service tags, as follows:

  • All block documents
    • ibexa.search.solr.field.mapper.block
  • All block documents per translation
    • ibexa.search.solr.field.mapper.block.translation
  • Content documents
    • ibexa.search.solr.field.mapper.content
  • Content documents per translation
    • ibexa.search.solr.field.mapper.content.translation
  • Location documents
    • ibexa.search.solr.field.mapper.location

The following example shows how you can index data from the parent Location content, to make it available for full-text search on the child content. The example relies on a use case of indexing webinar data on the webinar events, which are children of the webinar. The field mapper could then look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?php declare(strict_types=1);

namespace App\Search\FieldMapper;

use Ibexa\Contracts\Core\Persistence\Content;
use Ibexa\Contracts\Core\Persistence\Content\Handler as ContentHandler;
use Ibexa\Contracts\Core\Persistence\Content\Location\Handler as LocationHandler;
use Ibexa\Contracts\Core\Search;
use Ibexa\Contracts\Solr\FieldMapper\ContentFieldMapper;

class WebinarEventTitleFulltextFieldMapper extends ContentFieldMapper
{
    /**
     * @var \Ibexa\Contracts\Core\Persistence\Content\Type\Handler
     */
    protected \Ibexa\Contracts\Core\Persistence\Content\Type\Handler $contentHandler;

    /**
     * @var \Ibexa\Contracts\Core\Persistence\Content\Location\Handler
     */
    protected \Ibexa\Contracts\Core\Persistence\Content\Location\Handler $locationHandler;

    /**
     * @param \Ibexa\Contracts\Core\Persistence\Content\Handler $contentHandler
     * @param \Ibexa\Contracts\Core\Persistence\Content\Location\Handler $locationHandler
     */
    public function __construct(
        ContentHandler $contentHandler,
        LocationHandler $locationHandler
    ) {
        $this->contentHandler = $contentHandler;
        $this->locationHandler = $locationHandler;
    }

    public function accept(Content $content)
    {
        // ContentType with ID 42 is webinar event
        return $content->versionInfo->contentInfo->contentTypeId == 42;
    }

    public function mapFields(Content $content)
    {
        $mainLocationId = $content->versionInfo->contentInfo->mainLocationId;
        $location = $this->locationHandler->load($mainLocationId);
        $parentLocation = $this->locationHandler->load($location->parentId);
        $parentContentInfo = $this->contentHandler->loadContentInfo($parentLocation->contentId);

        return [
            new Search\Field(
                'fulltext',
                $parentContentInfo->name,
                new Search\FieldType\FullTextField()
            ),
        ];
    }
}

You index full text data only on the content document, therefore, you would register the service like this:

1
2
3
4
5
6
7
services:
    App\Search\Mapper\WebinarEventTitleFulltextFieldMapper:
        arguments:
            - '@Ibexa\Contracts\Core\Persistence\Content\Handler'
            - '@Ibexa\Contracts\Core\Persistence\Content\Location\Handler'
        tags:
            - {name: ibexa.search.solr.field.mapper.content}

Permission issues when using Repository API in document field mappers

Document field mappers are low-level and expect to be able to index all content regardless of current user permissions. If you use PHP API in your custom document field mappers, apply sudo(), or use the Persistence SPI layer as in the example above.