Passed
Pull Request — master (#191)
by Simon
10:58 queued 02:05
created

DocumentFactory::addField()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

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