DocumentFactory::classEquals()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
crap 6
1
<?php
2
/**
3
 * class DocumentFactory|Firesphere\SolrSearch\Factories\DocumentFactory Build a Solarium document to push
4
 *
5
 * @package Firesphere\Solr\Search
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\Solr\Search
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
        $hierarchy = ClassInfo::ancestry($item);
132 8
        $classHierarchy = [];
133 8
        foreach ($hierarchy as $lower => $camel) {
134 8
            $classHierarchy[] = $camel;
135
        }
136
        $doc->addField('ClassHierarchy', $classHierarchy);
137
        $doc->addField('ViewStatus', $item->getViewStatus());
138
        $this->extend('updateDefaultFields', $doc, $item);
139
    }
140
141
    /**
142
     * Create the required record for a field
143
     *
144
     * @param array $fields Fields to build a record for
145 8
     * @param Document $doc Document for Solr
146
     * @param DataObject $item Object to get the data for
147 8
     * @param array $boostFields Custom set of index-time-boosted fields
148 8
     * @throws Exception
149 8
     */
150 8
    protected function buildFields($fields, Document $doc, DataObject $item, array $boostFields): void
151 8
    {
152
        foreach ($fields as $field) {
153
            $fieldData = $this->getFieldResolver()->resolveField($field);
154 8
            foreach ($fieldData as $dataField => $options) {
155
                $options['boost'] = $boostFields[$field] ?? null;
156
                $this->addField($doc, $item, $options);
157
            }
158
        }
159
    }
160
161
    /**
162
     * Add a single field to the Solr index
163 8
     *
164
     * @param Document $doc Solr Document
165 8
     * @param DataObject $object Object whose field is to be added
166
     * @param array $options Additional options
167
     */
168
    protected function addField($doc, $object, $options): void
169 8
    {
170
        if (!$this->classIs($object, $options['origin'])) {
171 8
            return;
172
        }
173 8
174 8
        $this->extend('onBeforeAddField', $options);
175
176 8
        $valuesForField = $this->getValuesForField($object, $options);
177 8
178 8
        $typeMap = Statics::getTypeMap();
179
        $type = $typeMap[$options['type']] ?? $typeMap['*'];
180 8
181
        foreach ($valuesForField as $value) {
182
            $this->extend('onBeforeAddDoc', $options, $value);
183
            $this->addToDoc($doc, $options, $type, $value);
184
        }
185
    }
186
187
    /**
188
     * Determine if the given object is one of the given type
189 8
     *
190
     * @param string|DataObject $class Class to compare
191 8
     * @param array|string $base Class or list of base classes
192
     * @return bool
193 8
     */
194 8
    protected function classIs($class, $base): bool
195 8
    {
196
        $base = is_array($base) ? $base : [$base];
197
198
        foreach ($base as $nextBase) {
199
            if ($this->classEquals($class, $nextBase)) {
200
                return true;
201
            }
202
        }
203
204
        return false;
205
    }
206
207
    /**
208
     * Check if a base class is an instance of the expected base group
209 8
     *
210
     * @param string|DataObject $class Class to compare
211 8
     * @param string $base Base class
212
     * @return bool
213
     */
214
    protected function classEquals($class, $base): bool
215
    {
216
        return $class === $base || ($class instanceof $base);
217
    }
218
219
    /**
220
     * Use the DataResolver to find the value(s) for a field.
221
     * Returns an array of values, and if it's multiple, it becomes a long array
222 8
     *
223
     * @param DataObject $object Object to resolve
224
     * @param array $options Customised options
225 8
     * @return array
226 6
     */
227
    protected function getValuesForField($object, $options): array
228
    {
229
        try {
230
            $valuesForField = [DataResolver::identify($object, $options['fullfield'])];
231
        } catch (Exception $error) {
232 8
            // @codeCoverageIgnoreStart
233
            $valuesForField = [];
234
            // @codeCoverageIgnoreEnd
235
        }
236
237
        return $valuesForField;
238
    }
239
240
    /**
241
     * Push field to a document
242
     *
243 8
     * @param Document $doc Solr document
244
     * @param array $options Custom options
245
     * @param string $type Type of Solr field
246 8
     * @param DBField|string|null $value Value(s) of the field
247 7
     */
248
    protected function addToDoc($doc, $options, $type, $value): void
249
    {
250 8
        /* Solr requires dates in the form 1995-12-31T23:59:59Z, so we need to normalize to GMT */
251
        if (($value && $type === 'tdate') || $value instanceof DBDate) {
252 8
            $value = gmdate('Y-m-d\TH:i:s\Z', strtotime($value));
253 8
        }
254
255
        $name = getShortFieldName($options['name']);
256
257
        $doc->addField($name, $value, $options['boost'], Document::MODIFIER_SET);
258
    }
259
260 1
    /**
261
     * Are we debugging?
262 1
     *
263
     * @return bool
264
     */
265
    public function isDebug(): bool
266
    {
267
        return $this->debug;
268
    }
269
270
    /**
271 8
     * Set to true if debugging should be enabled
272
     *
273 8
     * @param bool $debug
274
     * @return DocumentFactory
275 8
     */
276
    public function setDebug(bool $debug): DocumentFactory
277
    {
278
        $this->debug = $debug;
279
280
        return $this;
281
    }
282
}
283