Passed
Push — hans/Added-endpoints ( 66e262 )
by Simon
07:19
created

DocumentFactory   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 236
Duplicated Lines 0 %

Test Coverage

Coverage 97.4%

Importance

Changes 17
Bugs 3 Features 0
Metric Value
eloc 72
c 17
b 3
f 0
dl 0
loc 236
ccs 75
cts 77
cp 0.974
rs 10
wmc 26

11 Methods

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