Passed
Push — sheepy/introspection ( 9a33c8...7d1300 )
by Marco
05:54
created

DocumentFactory::findObjectData()   A

Complexity

Conditions 6
Paths 10

Size

Total Lines 32
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6.3949

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 19
dl 0
loc 32
ccs 14
cts 18
cp 0.7778
rs 9.0111
c 1
b 0
f 0
cc 6
nc 10
nop 2
crap 6.3949

1 Method

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