Passed
Push — sheepy/introspection ( 851cdd...69e16c )
by Marco
06:22
created

SolrCoreService::updateItems()   A

Complexity

Conditions 5
Paths 12

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 13
c 2
b 0
f 0
dl 0
loc 24
rs 9.5222
ccs 13
cts 13
cp 1
cc 5
nc 12
nop 3
crap 5
1
<?php
2
3
namespace Firesphere\SolrSearch\Services;
4
5
use Exception;
6
use Firesphere\SolrSearch\Factories\DocumentFactory;
7
use Firesphere\SolrSearch\Helpers\SearchIntrospection;
8
use Firesphere\SolrSearch\Indexes\BaseIndex;
9
use Firesphere\SolrSearch\Interfaces\ConfigStore;
10
use GuzzleHttp\HandlerStack;
11
use LogicException;
12
use ReflectionClass;
13
use ReflectionException;
14
use SilverStripe\Core\ClassInfo;
15
use SilverStripe\Core\Config\Configurable;
16
use SilverStripe\Core\Injector\Injector;
17
use SilverStripe\ORM\ArrayList;
18
use SilverStripe\ORM\DataObject;
19
use SilverStripe\ORM\SS_List;
20
use Solarium\Client;
21
use Solarium\Core\Client\Adapter\Guzzle;
22
use Solarium\QueryType\Server\CoreAdmin\Query\Query;
23
use Solarium\QueryType\Server\CoreAdmin\Result\StatusResult;
24
use Solarium\QueryType\Update\Result;
25
26
class SolrCoreService
27
{
28
    /**
29
     * Unique ID in Solr
30
     */
31
    public const ID_FIELD = 'id';
32
    /**
33
     * SilverStripe ID of the object
34
     */
35
    public const CLASS_ID_FIELD = 'ObjectID';
36
    /**
37
     * Solr update types
38
     */
39
    public const DELETE_TYPE_ALL = 'deleteall';
40
    public const DELETE_TYPE = 'delete';
41
    public const UPDATE_TYPE = 'update';
42
    public const CREATE_TYPE = 'create';
43
44
45
    use Configurable;
46
47
    /**
48
     * @var Client
49
     */
50
    protected $client;
51
52
    /**
53
     * @var array
54
     */
55
    protected $validIndexes = [];
56
57
    /**
58
     * @var Query
59
     */
60
    protected $admin;
61
62
    /**
63
     * Add debugging information
64
     * @var bool
65
     */
66
    protected $inDebugMode = false;
67
68
69
    /**
70
     * SolrCoreService constructor.
71
     * @throws ReflectionException
72
     */
73 30
    public function __construct()
74
    {
75 30
        $config = static::config()->get('config');
76 30
        $this->client = new Client($config);
77 30
        $this->client->setAdapter(new Guzzle());
78 30
        $this->admin = $this->client->createCoreAdmin();
79 30
        $this->filterIndexes();
80 30
    }
81
82
    /**
83
     * @throws ReflectionException
84
     */
85 30
    protected function filterIndexes(): void
86
    {
87 30
        $indexes = ClassInfo::subclassesFor(BaseIndex::class);
88 30
        foreach ($indexes as $subindex) {
89 30
            $ref = new ReflectionClass($subindex);
90 30
            if ($ref->isInstantiable()) {
91 30
                $this->validIndexes[] = $subindex;
92
            }
93
        }
94 30
    }
95
96
    /**
97
     * Create a new core
98
     * @param $core string - The name of the core
99
     * @param ConfigStore $configStore
100
     * @return bool
101
     */
102 2
    public function coreCreate($core, $configStore): bool
103
    {
104 2
        $action = $this->admin->createCreate();
105
106 2
        $action->setCore($core);
107
        $action->setInstanceDir($configStore->instanceDir($core));
108 2
        $this->admin->setAction($action);
109
        $response = $this->client->coreAdmin($this->admin);
110 2
111
        return $response->getWasSuccessful();
112 2
    }
113
114 2
    /**
115
     * @param $core
116
     * @return StatusResult|null
117
     */
118
    public function coreReload($core): ?StatusResult
119
    {
120
        $reload = $this->admin->createReload();
121 12
        $reload->setCore($core);
122
123 12
        $this->admin->setAction($reload);
124 12
125
        $response = $this->client->coreAdmin($this->admin);
126 12
127
        return $response->getStatusResult();
128 12
    }
129
130 12
    /**
131
     * @param string $core
132
     * @return StatusResult|null
133
     * @deprecated backward compatibility stub
134
     */
135
    public function coreIsActive($core): ?StatusResult
136
    {
137
        return $this->coreStatus($core);
138 1
    }
139
140 1
    /**
141
     * @param string $core
142
     * @return StatusResult|null
143
     */
144
    public function coreStatus($core): ?StatusResult
145
    {
146
        $status = $this->admin->createStatus();
147 12
        $status->setCore($core);
148
149 12
        $this->admin->setAction($status);
150 12
        $response = $this->client->coreAdmin($this->admin);
151
152 12
        return $response->getStatusResult();
153 12
    }
154
155 12
    /**
156
     * Remove a core from Solr
157
     * @param string $core core name
158
     * @return StatusResult|null A result is successful
159
     */
160
    public function coreUnload($core): ?StatusResult
161
    {
162
        $unload = $this->admin->createUnload();
163 1
        $unload->setCore($core);
164
165 1
        $this->admin->setAction($unload);
166 1
        $response = $this->client->coreAdmin($this->admin);
167
168 1
        return $response->getStatusResult();
169 1
    }
170
171 1
    /**
172
     * @param SS_List|DataObject $items
173
     * @param string $type
174
     * @param null|string $index
175
     * @return bool|Result
176
     * @throws ReflectionException
177
     * @throws Exception
178
     */
179
    public function updateItems($items, $type, $index = null)
180
    {
181
        $indexes = $this->getValidIndexes($index);
182 21
183
        $result = false;
184 21
        $items = ($items instanceof DataObject) ? ArrayList::create([$items]) : $items;
185 1
        $items = ($items instanceof SS_List) ? $items : ArrayList::create($items);
186
187 20
        $hierarchy = SearchIntrospection::getHierarchy($items->first()->ClassName);
188 1
189
        foreach ($indexes as $indexString) {
190
            /** @var BaseIndex $index */
191 19
            $index = Injector::inst()->get($indexString);
192
            $classes = $index->getClasses();
193 18
            $inArray = array_intersect($classes, $hierarchy);
194 18
            // No point in sending a delete|update|create for something that's not in the index
195 18
            if (!count($inArray)) {
196
                continue;
197 18
            }
198
199 18
            $result = $this->doManipulate($items, $type, $index);
200
        }
201 18
202 18
        return $result;
203 18
    }
204
205 18
    /**
206 17
     * Get valid indexes for the project
207
     * @param null|string $index
208
     * @return array
209 3
     */
210
    public function getValidIndexes($index = null): ?array
211
    {
212 18
        if ($index && !in_array($index, $this->validIndexes, true)) {
213
            throw new LogicException('Incorrect index ' . $index);
214
        }
215
216
        if ($index) {
217
            return [$index];
218
        }
219
220 28
        // return the array values, to reset the keys
221
        return array_values($this->validIndexes);
222 28
    }
223 1
224
    /**
225
     * @param SS_List $items
226 28
     * @param $type
227 4
     * @param BaseIndex $index
228
     * @return Result
229
     * @throws Exception
230
     */
231 27
    public function doManipulate($items, $type, BaseIndex $index): Result
232
    {
233
        $client = $index->getClient();
234
235
        // get an update query instance
236
        $update = $client->createUpdate();
237
238
        switch ($type) {
239
            case static::DELETE_TYPE:
240
                // By pushing to a single array, we have less memory usage and no duplicates
241 5
                // This is faster, and more efficient, because we only do one DB query
242
                $delete = $items->map('ID', 'ClassName')->toArray();
243 5
                array_walk($delete, static function (&$item, $key) {
244
                    $item = sprintf('%s-%s', $item, $key);
245
                });
246 5
                $update->addDeleteByIds(array_values($delete));
247
                // Remove the deletion array from memory
248
                break;
249 5
            case static::DELETE_TYPE_ALL:
250
                $update->addDeleteQuery('*:*');
251
                break;
252 1
            case static::UPDATE_TYPE:
253 1
            case static::CREATE_TYPE:
254 1
                $this->updateIndex($index, $items, $update);
255 1
        }
256 1
        $update->addCommit();
257
258 1
        return $client->update($update);
259 5
    }
260 2
261 2
    /**
262 3
     * @param BaseIndex $index
263
     * @param SS_List $items
264 3
     * @param \Solarium\QueryType\Update\Query\Query $update
265
     * @throws Exception
266 5
     */
267
    public function updateIndex($index, $items, $update): void
268 5
    {
269
        $fields = $index->getFieldsForIndexing();
270
        $factory = $this->getFactory($items);
271
        $docs = $factory->buildItems($fields, $index, $update);
272
        if (count($docs)) {
273
            $update->addDocuments($docs);
274
        }
275
    }
276
277 13
    /**
278
     * @param SS_List $items
279 13
     * @return DocumentFactory
280 13
     */
281 13
    protected function getFactory($items): DocumentFactory
282 13
    {
283 13
        $factory = Injector::inst()->get(DocumentFactory::class);
284
        $factory->setItems($items);
285 13
        $factory->setClass($items->first()->ClassName);
286
        $factory->setDebug($this->isInDebugMode());
287
288
        return $factory;
289
    }
290
291 13
    /**
292
     * @return bool
293 13
     */
294 13
    public function isInDebugMode(): bool
295 13
    {
296 13
        return $this->inDebugMode;
297
    }
298 13
299
    /**
300
     * @param bool $inDebugMode
301
     * @return SolrCoreService
302
     */
303
    public function setInDebugMode(bool $inDebugMode): SolrCoreService
304 13
    {
305
        $this->inDebugMode = $inDebugMode;
306 13
307
        return $this;
308
    }
309
310
    /**
311
     * @param HandlerStack|null $handler Used for testing the solr version
312
     * @return int
313 28
     */
314
    public function getSolrVersion($handler = null): int
315 28
    {
316
        $config = self::config()->get('config');
317 28
        $firstEndpoint = array_shift($config['endpoint']);
318
        $clientConfig = [
319
            'base_uri' => 'http://' . $firstEndpoint['host'] . ':' . $firstEndpoint['port']
320
        ];
321
322
        if ($handler) {
323
            $clientConfig['handler'] = $handler;
324 13
        }
325
326 13
        $client = new \GuzzleHttp\Client($clientConfig);
327 13
328
        $result = $client->get('solr/admin/info/system');
329 13
        $result = json_decode($result->getBody(), 1);
330
331
        $solrVersion = 5;
332 13
        $version = version_compare('5.0.0', $result['lucene']['solr-spec-version']);
333 1
        if ($version > 0) {
334
            $solrVersion = 4;
335
        }
336 13
337
        return $solrVersion;
338 13
    }
339 13
340
    /**
341 13
     * @return Client
342 13
     */
343 13
    public function getClient(): Client
344 1
    {
345
        return $this->client;
346
    }
347 13
348
    /**
349
     * @param Client $client
350
     * @return SolrCoreService
351
     */
352
    public function setClient($client): SolrCoreService
353 1
    {
354
        $this->client = $client;
355 1
356
        return $this;
357
    }
358
}
359