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

SolrCoreService::getSolrVersion()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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