Passed
Push — sheepy/introspection ( 69e16c...c6c7ca )
by Marco
05:28
created

DocumentFactory::buildField()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
dl 0
loc 9
rs 10
ccs 3
cts 3
cp 1
cc 4
nc 4
nop 4
crap 4
1
<?php
2
3
4
namespace Firesphere\SolrSearch\Factories;
5
6
use Exception;
7
use Firesphere\SolrSearch\Extensions\DataObjectExtension;
8
use Firesphere\SolrSearch\Helpers\SearchIntrospection;
9
use Firesphere\SolrSearch\Helpers\Statics;
10
use Firesphere\SolrSearch\Indexes\BaseIndex;
11
use Firesphere\SolrSearch\Services\SolrCoreService;
12
use Firesphere\SolrSearch\Traits\DocumentFactoryTrait;
13
use Firesphere\SolrSearch\Traits\LoggerTrait;
14
use Psr\Log\LoggerInterface;
15
use SilverStripe\Core\ClassInfo;
16
use SilverStripe\Core\Config\Configurable;
17
use SilverStripe\Core\Injector\Injector;
18
use SilverStripe\ORM\DataObject;
19
use SilverStripe\ORM\FieldType\DBDate;
20
use SilverStripe\ORM\FieldType\DBField;
21
use SilverStripe\ORM\SS_List;
22
use Solarium\QueryType\Update\Query\Document\Document;
23
use Solarium\QueryType\Update\Query\Query;
24
25
class DocumentFactory
26
{
27
    use Configurable;
28
    use DocumentFactoryTrait;
29
    use LoggerTrait;
30
31
    /**
32
     * Numeral types in Solr
33
     * @var array
34
     */
35
    protected static $numerals = [
36
        'tint',
37
        'tfloat',
38
        'tdouble'
39
    ];
40
    /**
41
     * @var bool
42
     */
43
    protected $debug = false;
44
45
    /**
46
     * @var null|LoggerInterface
47
     */
48
    protected $logger;
49
50
    /**
51
     * DocumentFactory constructor, sets up introspection
52
     */
53
    public function __construct()
54
    {
55
        $this->introspection = Injector::inst()->get(SearchIntrospection::class);
56
    }
57
58
    /**
59
     * Note, it can only take one type of class at a time!
60
     * So make sure you properly loop and set $class
61
     * @param $fields
62 4
     * @param BaseIndex $index
63
     * @param Query $update
64 4
     * @return array
65 4
     * @throws Exception
66
     */
67
    public function buildItems($fields, $index, $update): array
68
    {
69
        $class = $this->getClass();
70
        $this->getIntrospection()->setIndex($index);
71
        $boostFields = $index->getBoostedFields();
72
        $docs = [];
73
        $debugString = sprintf('Adding %s to %s%s', $class, $index->getIndexName(), PHP_EOL);
74
        $debugString .= '[';
75
        foreach ($this->getItems() as $item) {
76 14
            if ($item->ShowInSearch === 0) {
77
                continue;
78 14
            }
79 14
            $debugString .= "$item->ID, ";
80 14
            /** @var Document $doc */
81 14
            $doc = $update->createDocument();
82 14
            $this->addDefaultFields($doc, $item);
83 14
84 14
            $this->buildField($fields, $doc, $item, $boostFields);
85 14
            $item->destroy();
86
87 14
            $docs[] = $doc;
88 14
        }
89
90 14
        if ($this->debug) {
91 14
            $this->getLogger()->info(rtrim($debugString, ', ') . ']' . PHP_EOL);
92
        }
93 14
94
        return $docs;
95
    }
96 14
97 11
    /**
98
     * @param Document $doc
99
     * @param DataObject|DataObjectExtension $item
100 14
     */
101
    protected function addDefaultFields(Document $doc, DataObject $item)
102 14
    {
103
        $doc->setKey(SolrCoreService::ID_FIELD, $item->ClassName . '-' . $item->ID);
104
        $doc->addField(SolrCoreService::CLASS_ID_FIELD, $item->ID);
105
        $doc->addField('ClassName', $item->ClassName);
106
        $doc->addField('ClassHierarchy', ClassInfo::ancestry($item));
107
        $doc->addField('ViewStatus', $item->getViewStatus());
108 14
    }
109
110 14
    /**
111
     * @param $fields
112
     * @param Document $doc
113
     * @param DataObject $item
114
     * @param array $boostFields
115
     * @throws Exception
116
     */
117 14
    protected function buildField($fields, Document $doc, DataObject $item, array $boostFields): void
118
    {
119 14
        foreach ($fields as $field) {
120
            $fieldData = $this->getIntrospection()->getFieldIntrospection($field);
121 14
            foreach ($fieldData as $dataField => $options) {
122
                // Only one field per class, so let's take the fieldData. This will override previous additions
123
                $this->addField($doc, $item, $fieldData[$dataField]);
124
                if (array_key_exists($field, $boostFields)) {
125
                    $doc->setFieldBoost($dataField, $boostFields[$field]);
126
                }
127 15
            }
128
        }
129 15
    }
130
131
    /**
132
     * @param Document $doc
133
     * @param $object
134
     * @param $field
135 14
     */
136
    protected function addField($doc, $object, $field): void
137 14
    {
138
        if (!$this->classIs($object, $field['origin'])) {
139
            return;
140
        }
141
142
        $valuesForField = $this->getValueForField($object, $field);
143
144 14
        $typeMap = Statics::getTypeMap();
145
        $type = $typeMap[$field['type']] ?? $typeMap['*'];
146 14
147
        foreach ($valuesForField as $value) {
148 14
            if ($value === null) {
149
                continue;
150
            }
151
            $this->addToDoc($doc, $field, $type, $value);
152
        }
153
    }
154
155 14
    /**
156
     * Determine if the given object is one of the given type
157 14
     * @param string|array $class
158 14
     * @param array|string $base Class or list of base classes
159 14
     * @return bool
160 14
     */
161 14
    protected function classIs($class, $base): bool
162 14
    {
163
        $base = is_array($base) ? $base : [$base];
164
165
        foreach ($base as $nextBase) {
166
            if ($this->classEquals($class, $nextBase)) {
167
                return true;
168
            }
169
        }
170
171 14
        return false;
172
    }
173 14
174 14
    /**
175 14
     * Check if a base class is an instance of the expected base group
176
     * @param $class
177 14
     * @param $base
178 14
     * @return bool
179 14
     */
180
    protected function classEquals($class, $base): bool
181
    {
182
        return $class === $base || ($class instanceof $base);
183 14
    }
184
185
    /**
186
     * Given an object and a field definition get the current value of that field on that object
187
     *
188
     * @param DataObject|array $objects - The object to get the value from
189
     * @param array $field - The field definition to use
190 14
     * @return array Technically, it's always an array
191
     */
192 14
    protected function getValueForField($objects, $field): array
193 10
    {
194
        // Make sure we always have an array to iterate
195
        $objects = is_array($objects) ? $objects : [$objects];
196 14
197
        while ($step = array_shift($field['lookup_chain'])) {
198 14
            // If we're looking up this step on an array or SS_List, do the step on every item, merge result
199 14
            $objects = $this->getNext($objects, $step);
200
        }
201 14
202 14
        return $objects;
203 14
    }
204
205
    /**
206
     * @param $objects
207 14
     * @param $step
208 13
     * @return array
209
     */
210
    protected function getNext($objects, $step): array
211
    {
212 14
        $next = [];
213
214 14
        foreach ($objects as $item) {
215
            $item = $this->getItemForStep($step, $item);
216 14
            $next = is_array($item) ? array_merge($next, $item) : [$item];
217
        }
218
219
        // When all items have been processed, put them in to objects
220
        // This ensures the next step is an array of the correct objects to index
221
        $objects = $next;
222
        unset($next);
223
224 14
        return $objects;
225
    }
226 14
227
    /**
228 14
     * Find the item for the current ste
229 14
     * This can be a DataList or ArrayList, or a string
230 14
     * @param $step
231
     * @param $item
232
     * @return array
233
     */
234 10
    protected function getItemForStep($step, $item)
235
    {
236
        if ($step['call'] === 'method') {
237
            $item = $this->getItemMethod($step, $item);
238
        } else {
239
            $item = $this->getItemProperty($step, $item);
240
        }
241
242
        if ($item instanceof SS_List) {
243 14
            $item = $item->toArray();
244
        }
245 14
246
        return is_array($item) ? $item : [$item];
247
    }
248
249
    /**
250
     * @param $step
251
     * @param $item
252
     * @return mixed
253
     */
254
    protected function getItemMethod($step, $item)
255 14
    {
256
        $method = $step['method'];
257
        $item = $item->$method();
258 14
259
        return $item;
260 14
    }
261
262 14
    /**
263
     * @param $step
264 14
     * @param $item
265 14
     * @return mixed
266
     */
267 14
    protected function getItemProperty($step, $item)
268 14
    {
269
        $property = $step['property'];
270
        $item = $item->$property;
271
272
        return $item;
273 14
    }
274
275
    /**
276
     * @param Document $doc
277
     * @param array $field
278 14
     * @param string $type
279 14
     * @param DBField|string $value
280
     */
281
    protected function addToDoc($doc, $field, $type, $value): void
282 14
    {
283
        /* Solr requires dates in the form 1995-12-31T23:59:59Z, so we need to normalize to GMT */
284
        if ($type === 'tdate' || $value instanceof DBDate) {
285
            $value = gmdate('Y-m-d\TH:i:s\Z', strtotime($value));
286
        }
287
288
        $name = $this->sanitiseName($field['name']);
289
290
        $doc->addField($name, $value);
291
    }
292 14
293
    /**
294 14
     * @param string $field
295
     * @return string
296
     */
297
    public function sanitiseName($field)
298 14
    {
299 14
        $name = explode('\\', $field);
300
301
        return end($name);
302 14
    }
303
304
    /**
305
     * @return bool
306 14
     */
307
    public function isDebug(): bool
308
    {
309
        return $this->debug;
310
    }
311
312
    /**
313
     * @param bool $debug
314
     * @return DocumentFactory
315 14
     */
316
    public function setDebug(bool $debug): DocumentFactory
317
    {
318 14
        $this->debug = $debug;
319 14
320
        return $this;
321
    }
322
}
323