DocumentFactory::buildFields()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 5
c 2
b 0
f 0
nc 3
nop 3
dl 0
loc 12
rs 10
1
<?php
2
/**
3
 * class DocumentFactory|Firesphere\ElasticSearch\Factories\DocumentFactory Build a Solarium document to push
4
 *
5
 * @package Firesphere\Elastic\Search
6
 * @author Simon `Firesphere` Erkelens; Marco `Sheepy` Hermo
7
 * @copyright Copyright (c) 2018 - now() Firesphere & Sheepy
8
 */
9
10
namespace Firesphere\ElasticSearch\Factories;
11
12
use Exception;
13
use Firesphere\ElasticSearch\Indexes\ElasticIndex as ElasticBaseIndex;
14
use Firesphere\ElasticSearch\Services\ElasticCoreService;
15
use Firesphere\SearchBackend\Extensions\DataObjectSearchExtension;
16
use Firesphere\SearchBackend\Factories\DocumentCoreFactory;
17
use Firesphere\SearchBackend\Services\BaseService;
18
use Psr\Container\NotFoundExceptionInterface;
19
use SilverStripe\Core\ClassInfo;
20
use SilverStripe\Core\Config\Configurable;
21
use SilverStripe\Core\Extensible;
22
use SilverStripe\ORM\DataList;
23
use SilverStripe\ORM\DataObject;
24
use SilverStripe\ORM\FieldType\DBField;
25
26
/**
27
 * Class DocumentFactory
28
 * Factory to create documents to be pushed to Elastic
29
 *
30
 *  Firesphere\Elastic\Search
31
 */
32
class DocumentFactory extends DocumentCoreFactory
33
{
34
    use Configurable;
35
    use Extensible;
36
37
    /**
38
     * Note, it can only take one type of class at a time!
39
     * So make sure you properly loop and set $class
40
     *
41
     * @param array $fields Fields to index
42
     * @param ElasticBaseIndex $index Index to push the documents to
43
     * @param null $update Elastic doesn't have an "Update" object
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $update is correct as it would always require null to be passed?
Loading history...
44
     * @return array Documents to be pushed
45
     * @throws NotFoundExceptionInterface
46
     */
47
    public function buildItems($fields, $index, $update = null): array
48
    {
49
        $this->getFieldResolver()->setIndex($index);
50
        $docs = [];
51
        if ($this->debug) {
52
            $this->indexGroupMessage($index);
53
        }
54
55
        /** @var DataList|DataObject[] $item */
56
        foreach ($this->getItems() as $item) {
57
            // Don't index items that should not show in search explicitly.
58
            // Just a "not" is insufficient, as it could be null or false (both meaning, not set)
59
            if ($item->ShowInSearch === 0) {
60
                continue;
61
            }
62
            $doc = [];
63
            $doc = $this->buildFields($fields, $doc, $item);
64
            foreach ($index->getCopyFields() as $copyFieldName => $copyFields) {
65
                $doc[$copyFieldName] = $this->recursiveImplode($doc);
66
            }
67
            $doc = $this->addDefaultFields($doc, $item);
68
69
            $docs[] = $doc;
70
        }
71
72
        return $docs;
73
    }
74
75
    /**
76
     * Create the required record for a field
77
     *
78
     * @param array $fields Fields to build a record for
79
     * @param array $doc Document for Elastic
80
     * @param DataObject $item Object to get the data for
81
     * @throws Exception
82
     */
83
    protected function buildFields($fields, array $doc, DataObject $item): array //, array $boostFields): void
84
    {
85
        foreach ($fields as $field) {
86
            $fieldData = $this->getFieldResolver()->resolveField($field);
87
            foreach ($fieldData as $dataField => $options) {
88
                // Not an Elastic thing, for now
89
                //                $options['boost'] = $boostFields[$field] ?? null;
90
                $this->addField($doc, $item, $options);
91
            }
92
        }
93
94
        return $doc;
95
    }
96
97
    /**
98
     * Add a single field to the Elastic index
99
     *
100
     * @param array $doc Elastic Document
101
     * @param DataObject $object Object whose field is to be added
102
     * @param array $options Additional options
103
     */
104
    protected function addField(&$doc, $object, $options): void
105
    {
106
        if (!$this->classIs($object, $options['origin'])) {
107
            return;
108
        }
109
110
        $this->extend('onBeforeAddField', $options);
111
112
        $valuesForField = $this->getValuesForField($object, $options);
113
114
        foreach ($valuesForField as $value) {
115
            $this->extend('onBeforeAddDoc', $options, $value);
116
            $this->addToDoc($doc, $options, $value);
117
        }
118
    }
119
120
    /**
121
     * Push field to a document
122
     *
123
     * @param array $doc Elastic document
124
     * @param array $options Custom options
125
     * @param DBField|string|null $value Value(s) of the field
126
     */
127
    protected function addToDoc(&$doc, $options, $value): void
128
    {
129
        /* Elastic requires dates in the form of a timestamp in milliseconds, so we need to normalize to GMT */
130
        if (str_contains($options['type'], 'Date')) {
131
            $value = strtotime((string)$value) * 1000;
132
        }
133
134
        $name = $this->getShortFieldName($options['name']);
135
        $name = str_replace('_', '.', $name);
136
137
        $doc[$name] = $value;//, $options['boost'], Document::MODIFIER_SET);
138
    }
139
140
    /**
141
     * Recursively implode the array with values
142
     * to a single text blob to use as the main indexed
143
     * @param array $arr
144
     * @return string
145
     */
146
    protected function recursiveImplode($arr): string
147
    {
148
        $return = [];
149
        foreach ($arr as $key => $value) {
150
            if (is_array($value)) {
151
                $return[] = $this->recursiveImplode($value);
152
            } else {
153
                $return[] = $value;
154
            }
155
        }
156
157
        return implode(', ', $return);
158
    }
159
160
    /**
161
     * Add fields that should always be included
162
     *
163
     * @param array $doc Elastic Document
164
     * @param DataObject|DataObjectSearchExtension $item Item to get the data from
165
     */
166
    protected function addDefaultFields($doc, $item)
167
    {
168
        $doc[BaseService::ID_FIELD] = $item->ClassName . '-' . $item->ID;
169
        $doc[BaseService::CLASS_ID_FIELD] = $item->ID;
170
        $doc[ElasticCoreService::ID_KEY] = $this->keyService->generateKey($item);
0 ignored issues
show
Bug introduced by
It seems like $item can also be of type Firesphere\SearchBackend...taObjectSearchExtension; however, parameter $object of SilverStripe\ORM\UniqueK...nterface::generateKey() does only seem to accept SilverStripe\ORM\DataObject, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

170
        $doc[ElasticCoreService::ID_KEY] = $this->keyService->generateKey(/** @scrutinizer ignore-type */ $item);
Loading history...
171
        // Set a known ID, with field name _id, for Elastic
172
        $doc['ClassName'] = $item->ClassName;
173
        $hierarchy = ClassInfo::ancestry($item);
174
        $classHierarchy = [];
175
        foreach ($hierarchy as $lower => $camel) {
176
            $classHierarchy[] = $camel;
177
        }
178
        $doc['ClassHierarchy'] = $classHierarchy;
179
        $doc['ViewStatus'] = $item->getViewStatus();
180
        $this->extend('updateDefaultFields', $doc, $item);
181
182
        return $doc;
183
    }
184
}
185