Passed
Pull Request — primary (#265)
by Simon
49:12 queued 24:12
created

SolrCoreService::getSolrAuthentication()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 2
nop 1
dl 0
loc 14
ccs 0
cts 0
cp 0
crap 12
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * class SolrCoreService|Firesphere\SolrSearch\Services\SolrCoreService Base service for communicating with the core
4
 *
5
 * @package Firesphere\Solr\Search
6
 * @author Simon `Firesphere` Erkelens; Marco `Sheepy` Hermo
7
 * @copyright Copyright (c) 2018 - now() Firesphere & Sheepy
8
 */
9
10
namespace Firesphere\SolrSearch\Services;
11
12
use Exception;
13
use Firesphere\SolrSearch\Factories\DocumentFactory;
14
use Firesphere\SolrSearch\Helpers\FieldResolver;
15
use Firesphere\SolrSearch\Indexes\BaseIndex;
16
use Firesphere\SolrSearch\Traits\CoreAdminTrait;
17
use Firesphere\SolrSearch\Traits\CoreServiceTrait;
18
use GuzzleHttp\Client as GuzzleClient;
19
use GuzzleHttp\HandlerStack;
20
use Http\Discovery\HttpClientDiscovery;
21
use Http\Discovery\Psr17FactoryDiscovery;
22
use LogicException;
23
use ReflectionClass;
24
use ReflectionException;
25
use SilverStripe\Core\ClassInfo;
26
use SilverStripe\Core\Config\Configurable;
27
use SilverStripe\Core\Injector\Injectable;
28
use SilverStripe\Core\Injector\Injector;
29
use SilverStripe\ORM\ArrayList;
30
use SilverStripe\ORM\DataObject;
31
use SilverStripe\ORM\SS_List;
32
use Solarium\Client;
33
use Solarium\Core\Client\Adapter\Psr18Adapter;
34
use Solarium\Core\Client\Client as CoreClient;
35
use Solarium\Core\Client\Request;
36
use Solarium\QueryType\Update\Query\Query;
37
use Solarium\QueryType\Update\Result;
38
use Symfony\Component\EventDispatcher\EventDispatcher;
39
40
/**
41
 * Class SolrCoreService provides the base connection to Solr.
42
 *
43
 * Default service to connect to Solr and handle all base requirements to support Solr.
44
 * Default constants are available to support any set up.
45
 *
46
 * @package Firesphere\Solr\Search
47
 */
48
class SolrCoreService
49
{
50
    use Injectable;
51
    use Configurable;
52
    use CoreServiceTrait;
53
    use CoreAdminTrait;
54
55
    /**
56
     * Unique ID in Solr
57
     */
58
    const ID_FIELD = 'id';
59
    /**
60
     * SilverStripe ID of the object
61
     */
62
    const CLASS_ID_FIELD = 'ObjectID';
63
    /**
64
     * Name of the field that can be used for queries
65
     */
66
    const CLASSNAME = 'ClassName';
67
    /**
68
     * Solr update types
69
     */
70
    const DELETE_TYPE_ALL = 'deleteall';
71
    /**
72
     * string
73
     */
74
    const DELETE_TYPE = 'delete';
75
    /**
76
     * string
77
     */
78
    const UPDATE_TYPE = 'update';
79
    /**
80
     * string
81
     */
82
    const CREATE_TYPE = 'create';
83
84
    /**
85
     * @var array Base indexes that exist
86
     */
87
    protected $baseIndexes = [];
88
    /**
89
     * @var array Valid indexes out of the base indexes
90
     */
91
    protected $validIndexes = [];
92
    /**
93
     * @var array Available config versions
94 88
     */
95
    protected static $solr_versions = [
96 88
        "9.0.0",
97 88
        "7.0.0",
98 88
        "5.0.0",
99 88
        "4.0.0",
100 88
    ];
101 88
102 88
    /**
103
     * SolrCoreService constructor.
104
     *
105
     * @throws ReflectionException
106
     */
107
    public function __construct()
108
    {
109
        $config = static::config()->get('config');
110 88
        $httpClient = HTTPClientDiscovery::find();
111
        $requestFactory = Psr17FactoryDiscovery::findRequestFactory();
112 88
        $streamFactory = Psr17FactoryDiscovery::findStreamFactory();
113 88
        $eventDispatcher = new EventDispatcher();
114 88
        $adapter = new Psr18Adapter($httpClient, $requestFactory, $streamFactory);
115
        $this->client = new Client($adapter, $eventDispatcher, $config);
116
        $this->admin = $this->client->createCoreAdmin();
117 88
        $this->baseIndexes = ClassInfo::subclassesFor(BaseIndex::class);
118 88
        $this->filterIndexes();
119
    }
120 88
121
    /**
122 88
     * Filter enabled indexes down to valid indexes that can be instantiated
123
     * or are allowed from config
124 88
     *
125
     * @throws ReflectionException
126
     */
127
    protected function filterIndexes(): void
128
    {
129
        $enabledIndexes = static::config()->get('indexes');
130
        $enabledIndexes = is_array($enabledIndexes) ? $enabledIndexes : $this->baseIndexes;
131
        foreach ($this->baseIndexes as $subindex) {
132
            // If the config of indexes is set, and the requested index isn't in it, skip addition
133 88
            // Or, the index simply doesn't exist, also a valid option
134
            if (!in_array($subindex, $enabledIndexes, true) ||
135 88
                !$this->checkReflection($subindex)
136
            ) {
137 88
                continue;
138
            }
139
            $this->validIndexes[] = $subindex;
140
        }
141
    }
142
143
    /**
144
     * Check if the class is instantiable
145
     *
146
     * @param $subindex
147
     * @return bool
148
     * @throws ReflectionException
149
     */
150 9
    protected function checkReflection($subindex): bool
151
    {
152 9
        $reflectionClass = new ReflectionClass($subindex);
153
154 8
        return $reflectionClass->isInstantiable();
155 8
    }
156 8
157
    /**
158 8
     * Update items in the list to Solr
159
     *
160 8
     * @param SS_List|DataObject $items
161
     * @param string $type
162 8
     * @param null|string $index
163 8
     * @return bool|Result
164 8
     * @throws ReflectionException
165
     * @throws Exception
166 8
     */
167 7
    public function updateItems($items, $type, $index = null)
168
    {
169
        $indexes = $this->getValidIndexes($index);
170 8
171
        $result = false;
172
        $items = ($items instanceof DataObject) ? ArrayList::create([$items]) : $items;
173 8
        $items = ($items instanceof SS_List) ? $items : ArrayList::create($items);
174
175
        $hierarchy = FieldResolver::getHierarchy($items->first()->ClassName);
176
177
        foreach ($indexes as $indexString) {
178
            /** @var BaseIndex $index */
179
            $index = Injector::inst()->get($indexString);
180
            $classes = $index->getClasses();
181
            $inArray = array_intersect($classes, $hierarchy);
182 42
            // No point in sending a delete|update|create for something that's not in the index
183
            if (!count($inArray)) {
184 42
                continue;
185 1
            }
186
187
            $result = $this->doManipulate($items, $type, $index);
188 42
        }
189 5
190
        return $result;
191
    }
192
193 41
    /**
194
     * Get valid indexes for the project
195
     *
196
     * @param null|string $index
197
     * @return array
198
     */
199
    public function getValidIndexes($index = null): array
200
    {
201
        if ($index && !in_array($index, $this->validIndexes, true)) {
202
            throw new LogicException('Incorrect index ' . $index);
203
        }
204
205 10
        if ($index) {
206
            return [$index];
207 10
        }
208
209 10
        // return the array values, to reset the keys
210
        return array_values($this->validIndexes);
211
    }
212
213 10
    /**
214
     * Execute the manipulation of solr documents
215
     *
216
     * @param SS_List $items
217
     * @param $type
218
     * @param BaseIndex $index
219
     * @return Result
220
     * @throws Exception
221
     */
222
    public function doManipulate($items, $type, BaseIndex $index): Result
223
    {
224
        $client = $index->getClient();
225
226 10
        $update = $this->getUpdate($items, $type, $index, $client);
227
228
        // commit immediately when in dev mode
229 10
230
        return $client->update($update);
231
    }
232 10
233
    /**
234
     * get the update object ready
235 6
     *
236 6
     * @param SS_List $items
237 6
     * @param string $type
238 6
     * @param BaseIndex $index
239 6
     * @param CoreClient $client
240
     * @return mixed
241 6
     * @throws Exception
242 9
     */
243 2
    protected function getUpdate($items, $type, BaseIndex $index, CoreClient $client)
244 2
    {
245 7
        // get an update query instance
246 1
        $update = $client->createUpdate();
247 7
248 7
        switch ($type) {
249
            case static::DELETE_TYPE:
250 10
                // 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 10
                $delete = $items->map('ID', 'ClassName')->toArray();
253
                array_walk($delete, static function (&$item, $key) {
254
                    $item = sprintf('%s-%s', $item, $key);
255
                });
256
                $update->addDeleteByIds(array_values($delete));
257
                // Remove the deletion array from memory
258
                break;
259
            case static::DELETE_TYPE_ALL:
260
                $update->addDeleteQuery('*:*');
261
                break;
262
            case static::UPDATE_TYPE:
263 7
            case static::CREATE_TYPE:
264
                $this->updateIndex($index, $items, $update);
265 7
                break;
266 7
        }
267 7
        $update->addCommit();
268 7
269 7
        return $update;
270
    }
271 7
272
    /**
273
     * Create the documents and add to the update
274
     *
275
     * @param BaseIndex $index
276
     * @param SS_List $items
277
     * @param Query $update
278
     * @throws Exception
279 7
     */
280
    public function updateIndex($index, $items, $update): void
281 7
    {
282 7
        $fields = $index->getFieldsForIndexing();
283 7
        $factory = $this->getFactory($items);
284 7
        $docs = $factory->buildItems($fields, $index, $update);
285
        if (count($docs)) {
286 7
            $update->addDocuments($docs);
287
        }
288
    }
289
290
    /**
291
     * Get the document factory prepared
292
     *
293
     * @param SS_List $items
294
     * @return DocumentFactory
295 37
     */
296
    protected function getFactory($items): DocumentFactory
297 37
    {
298 37
        $factory = Injector::inst()->get(DocumentFactory::class);
299
        $factory->setItems($items);
300 37
        $factory->setClass($items->first()->ClassName);
301
        $factory->setDebug($this->isDebug());
302
303 37
        return $factory;
304 1
    }
305
306
    /**
307 37
     * Check the Solr version to use
308
     * In version compare, we have the following results:
309 37
     *       1 means "result version is higher"
310
     *       0 means "result version is equal"
311 37
     *      -1 means "result version is lower"
312 37
     * We want to use the version "higher or equal to", because the
313
     * configs are for version X-and-up.
314 37
     * We loop through the versions available from high to low
315
     * therefore, if the version is lower, we want to check the next config version
316 37
     *
317 1
     * If no valid version is found, throw an error
318
     *
319
     * @param HandlerStack|null $handler Used for testing the solr version
320 37
     * @throws LogicException
321 1
     * @return int
322
     */
323
    public function getSolrVersion($handler = null): int
0 ignored issues
show
Unused Code introduced by
The parameter $handler is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

323
    public function getSolrVersion(/** @scrutinizer ignore-unused */ $handler = null): int

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
324 37
    {
325
        $query = $this->client->createApi([
326
            'handler' => 'admin/info/system',
327
            'version' => Request::API_V1,
328
        ]);
329
330
        $result = $this->client->execute($query)->getData();
331
332
        foreach (static::$solr_versions as $version) {
333
            $compare = version_compare($version, $result['lucene']['solr-spec-version']);
334 37
            if ($compare !== -1) {
335
                [$v] = explode('.', $version);
336 37
                return (int)$v;
337
            }
338 37
        }
339
340
        throw new LogicException('No valid version of Solr found!', 255);
341 37
    }
342
}
343