Passed
Push — sheepy/elevation-configuration ( f8fadb...77a4b0 )
by Marco
07:42
created

DocumentFactory::getValuesForField()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 5
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 11
ccs 4
cts 4
cp 1
crap 2
rs 10
1
<?php
2
/**
3
 * class DocumentFactory|Firesphere\SolrSearch\Factories\DocumentFactory Build a Solarium document to push
4
 *
5
 * @package Firesphere\SolrSearch\Factories
6
 * @author Simon `Firesphere` Erkelens; Marco `Sheepy` Hermo
7
 * @copyright Copyright (c) 2018 - now() Firesphere & Sheepy
8
 */
9
10
namespace Firesphere\SolrSearch\Factories;
11
12
use Exception;
13
use Firesphere\SolrSearch\Extensions\DataObjectExtension;
14
use Firesphere\SolrSearch\Helpers\DataResolver;
15
use Firesphere\SolrSearch\Helpers\FieldResolver;
16
use Firesphere\SolrSearch\Helpers\Statics;
17
use Firesphere\SolrSearch\Indexes\BaseIndex;
18
use Firesphere\SolrSearch\Services\SolrCoreService;
19
use Firesphere\SolrSearch\Traits\DocumentFactoryTrait;
20
use Firesphere\SolrSearch\Traits\LoggerTrait;
21
use SilverStripe\Core\ClassInfo;
22
use SilverStripe\Core\Config\Configurable;
23
use SilverStripe\Core\Extensible;
24
use SilverStripe\Core\Injector\Injector;
25
use SilverStripe\ORM\DataObject;
26
use SilverStripe\ORM\FieldType\DBDate;
27
use SilverStripe\ORM\FieldType\DBField;
28
use Solarium\QueryType\Update\Query\Document;
29
use Solarium\QueryType\Update\Query\Query;
30
31
/**
32
 * Class DocumentFactory
33
 * Factory to create documents to be pushed to Solr
34
 *
35
 * @package Firesphere\SolrSearch\Factories
36
 */
37
class DocumentFactory
38
{
39
    use Configurable;
40
    use Extensible;
41
    use DocumentFactoryTrait;
42
    use LoggerTrait;
43
44
    /**
45
     * @var array Numeral types in Solr
46
     */
47
    protected static $numerals = [
48
        'tint',
49
        'tfloat',
50
        'tdouble',
51
    ];
52
    /**
53
     * @var bool Debug this build
54
     */
55
    protected $debug = false;
56
57
    /**
58
     * DocumentFactory constructor, sets up the field resolver
59
     */
60 11
    public function __construct()
61
    {
62 11
        $this->fieldResolver = Injector::inst()->get(FieldResolver::class);
63 11
    }
64
65
    /**
66
     * Note, it can only take one type of class at a time!
67
     * So make sure you properly loop and set $class
68
     *
69
     * @param array $fields Fields to index
70
     * @param BaseIndex $index Index to push the documents to
71
     * @param Query $update Update Query object
72
     * @return array Documents to be pushed
73
     * @throws Exception
74
     */
75 8
    public function buildItems($fields, $index, $update): array
76
    {
77 8
        $this->getFieldResolver()->setIndex($index);
78 8
        $boostFields = $index->getBoostedFields();
79 8
        $docs = [];
80 8
        if ($this->debug) {
81 1
            $this->indexGroupMessage($index);
82
        }
83
84 8
        foreach ($this->getItems() as $item) {
85
            // Don't index items that should not show in search explicitly.
86
            // Just a "not" is insufficient, as it could be null or false (both meaning, not set)
87 8
            if ($item->ShowInSearch === 0) {
88 1
                continue;
89
            }
90
            /** @var Document $doc */
91 8
            $doc = $update->createDocument();
92 8
            $this->addDefaultFields($doc, $item);
93
94 8
            $this->buildFields($fields, $doc, $item, $boostFields);
95 8
            $item->destroy();
96
97 8
            $docs[] = $doc;
98
        }
99
100 8
        return $docs;
101
    }
102
103
    /**
104
     * Show the message about what is being indexed
105
     *
106
     * @param BaseIndex $index
107
     */
108 1
    protected function indexGroupMessage(BaseIndex $index): void
109
    {
110 1
        $debugString = sprintf(
111 1
            'Indexing %s on %s (%s items)%s',
112 1
            $this->getClass(),
113 1
            $index->getIndexName(),
114 1
            $this->getItems()->count(),
115 1
            PHP_EOL
116
        );
117 1
        $this->getLogger()->info($debugString);
118 1
    }
119
120
    /**
121
     * Add fields that should always be included
122
     *
123
     * @param Document $doc Solr Document
124
     * @param DataObject|DataObjectExtension $item Item to get the data from
125
     */
126 8
    protected function addDefaultFields(Document $doc, DataObject $item)
127
    {
128 8
        $doc->setKey(SolrCoreService::ID_FIELD, $item->ClassName . '-' . $item->ID);
129 8
        $doc->addField(SolrCoreService::CLASS_ID_FIELD, $item->ID);
130 8
        $doc->addField('ClassName', $item->ClassName);
131 8
        $doc->addField('ClassHierarchy', ClassInfo::ancestry($item));
132 8
        $doc->addField('ViewStatus', $item->getViewStatus());
133 8
        $this->extend('updateDefaultFields', $doc, $item);
134 8
    }
135
136
    /**
137
     * Create the required record for a field
138
     *
139
     * @param array $fields Fields to build a record for
140
     * @param Document $doc Document for Solr
141
     * @param DataObject $item Object to get the data for
142
     * @param array $boostFields Custom set of index-time-boosted fields
143
     * @throws Exception
144
     */
145 8
    protected function buildFields($fields, Document $doc, DataObject $item, array $boostFields): void
146
    {
147 8
        foreach ($fields as $field) {
148 8
            $fieldData = $this->getFieldResolver()->resolveField($field);
149 8
            foreach ($fieldData as $dataField => $options) {
150 8
                $options['boost'] = $boostFields[$field] ?? null;
151 8
                $this->addField($doc, $item, $options);
152
            }
153
        }
154 8
    }
155
156
    /**
157
     * Add a single field to the Solr index
158
     *
159
     * @param Document $doc Solr Document
160
     * @param DataObject $object Object whose field is to be added
161
     * @param array $options Additional options
162
     */
163 8
    protected function addField($doc, $object, $options): void
164
    {
165 8
        if (!$this->classIs($object, $options['origin'])) {
166
            return;
167
        }
168
169 8
        $this->extend('onBeforeAddField', $options);
170
171 8
        $valuesForField = $this->getValuesForField($object, $options);
172
173 8
        $typeMap = Statics::getTypeMap();
174 8
        $type = $typeMap[$options['type']] ?? $typeMap['*'];
175
176 8
        foreach ($valuesForField as $value) {
177 8
            $this->extend('onBeforeAddDoc', $options, $value);
178 8
            $this->addToDoc($doc, $options, $type, $value);
179
        }
180 8
    }
181
182
    /**
183
     * Determine if the given object is one of the given type
184
     *
185
     * @param string|DataObject $class Class to compare
186
     * @param array|string $base Class or list of base classes
187
     * @return bool
188
     * @todo remove in favour of the inheritance check from PHP
189
     */
190 8
    protected function classIs($class, $base): bool
191
    {
192 8
        $base = is_array($base) ? $base : [$base];
193
194 8
        foreach ($base as $nextBase) {
195 8
            if ($this->classEquals($class, $nextBase)) {
196 8
                return true;
197
            }
198
        }
199
200
        return false;
201
    }
202
203
    /**
204
     * Check if a base class is an instance of the expected base group
205
     *
206
     * @param string|DataObject $class Class to compare
207
     * @param string $base Base class
208
     * @return bool
209
     */
210 8
    protected function classEquals($class, $base): bool
211
    {
212 8
        return $class === $base || ($class instanceof $base);
213
    }
214
215
    /**
216
     * Use the DataResolver to find the value(s) for a field.
217
     * Returns an array of values, and if it's multiple, it becomes a long array
218
     *
219
     * @param DataObject $object Object to resolve
220
     * @param array $options Customised options
221
     * @return array
222
     */
223 8
    protected function getValuesForField($object, $options): array
224
    {
225
        try {
226 8
            $valuesForField = [DataResolver::identify($object, $options['fullfield'])];
227 6
        } catch (Exception $error) {
228
            // @codeCoverageIgnoreStart
229
            $valuesForField = [];
230
            // @codeCoverageIgnoreEnd
231
        }
232
233 8
        return $valuesForField;
234
    }
235
236
    /**
237
     * Push field to a document
238
     *
239
     * @param Document $doc Solr document
240
     * @param array $options Custom options
241
     * @param string $type Type of Solr field
242
     * @param DBField|string|null $value Value(s) of the field
243
     */
244 8
    protected function addToDoc($doc, $options, $type, $value): void
245
    {
246
        /* Solr requires dates in the form 1995-12-31T23:59:59Z, so we need to normalize to GMT */
247 8
        if (($value && $type === 'tdate') || $value instanceof DBDate) {
248 7
            $value = gmdate('Y-m-d\TH:i:s\Z', strtotime($value));
249
        }
250
251 8
        $name = getShortFieldName($options['name']);
252
253 8
        $doc->addField($name, $value, $options['boost'], Document::MODIFIER_SET);
254 8
    }
255
256
    /**
257
     * Are we debugging?
258
     *
259
     * @return bool
260
     */
261 1
    public function isDebug(): bool
262
    {
263 1
        return $this->debug;
264
    }
265
266
    /**
267
     * Set to true if debugging should be enabled
268
     *
269
     * @param bool $debug
270
     * @return DocumentFactory
271
     */
272 8
    public function setDebug(bool $debug): DocumentFactory
273
    {
274 8
        $this->debug = $debug;
275
276 8
        return $this;
277
    }
278
}
279