Passed
Pull Request — main (#59)
by Simon
01:18
created

ElasticConfigureTask::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 1
eloc 2
c 1
b 1
f 0
nc 1
nop 0
dl 0
loc 4
rs 10
1
<?php
2
3
namespace Firesphere\ElasticSearch\Tasks;
4
5
use Elastic\Elasticsearch\Exception\ClientResponseException;
6
use Elastic\Elasticsearch\Exception\MissingParameterException;
7
use Elastic\Elasticsearch\Exception\ServerResponseException;
8
use Exception;
9
use Firesphere\ElasticSearch\Helpers\Statics;
10
use Firesphere\ElasticSearch\Indexes\ElasticIndex;
11
use Firesphere\ElasticSearch\Services\ElasticCoreService;
12
use Firesphere\SearchBackend\Helpers\FieldResolver;
13
use Firesphere\SearchBackend\Traits\LoggerTrait;
14
use Psr\Container\NotFoundExceptionInterface;
15
use SilverStripe\Control\HTTPRequest;
16
use SilverStripe\Core\ClassInfo;
17
use SilverStripe\Core\Injector\Injector;
18
use SilverStripe\Dev\BuildTask;
19
20
class ElasticConfigureTask extends BuildTask
21
{
22
    use LoggerTrait;
23
24
    /**
25
     * @var string URLSegment
26
     */
27
    private static $segment = 'ElasticConfigureTask';
0 ignored issues
show
introduced by
The private property $segment is not used, and could be removed.
Loading history...
28
    /**
29
     * @var string Title
30
     */
31
    protected $title = 'Configure Elastic cores';
32
    /**
33
     * @var string Description
34
     */
35
    protected $description = 'Create or reload a Elastic Core by adding or reloading a configuration.';
36
    /**
37
     * @var ElasticCoreService $service
38
     */
39
    protected $service;
40
41
    /**
42
     * @throws NotFoundExceptionInterface
43
     */
44
    public function __construct()
45
    {
46
        parent::__construct();
47
        $this->service = Injector::inst()->get(ElasticCoreService::class);
48
    }
49
50
    /**
51
     * Run the config
52
     *
53
     * @param HTTPRequest $request
54
     * @return void
55
     * @throws NotFoundExceptionInterface
56
     */
57
    public function run($request)
58
    {
59
        $this->extend('onBeforeElasticConfigureTask', $request);
60
61
        $indexes = $this->service->getValidIndexes();
62
63
64
        foreach ($indexes as $index) {
65
            try {
66
                /** @var ElasticIndex $instance */
67
                $instance = Injector::inst()->get($index, false);
68
69
                if ($request->getVar('clear') && $this->indexIsNew($instance)) {
70
                    $this->getLogger()->info(sprintf('Clearing index %s', $instance->getIndexName()));
71
                    $this->service->getClient()->indices()->delete(['index' => $instance->getIndexName()]);
72
                }
73
74
                $this->configureIndex($instance);
75
            } catch (Exception $error) {
76
                // @codeCoverageIgnoreStart
77
                $this->getLogger()->error(sprintf('Core loading failed for %s', $index));
78
                $this->getLogger()->error($error->getMessage()); // in browser mode, it might not always show
79
                // Continue to the next index
80
                continue;
81
                // @codeCoverageIgnoreEnd
82
            }
83
            $this->extend('onAfterConfigureIndex', $index);
84
        }
85
86
        $this->extend('onAfterElasticConfigureTask');
87
    }
88
89
    /**
90
     * Update/create a store
91
     * @param string $index
92
     * @return void
93
     * @throws ClientResponseException
94
     * @throws MissingParameterException
95
     * @throws ServerResponseException
96
     * @throws NotFoundExceptionInterface
97
     */
98
    protected function configureIndex($instance)
99
    {
100
        $indexName = $instance->getIndexName();
101
102
103
        $instanceConfig = $this->createConfigForIndex($instance);
104
105
        $body = $this->convertForJSON($instanceConfig);
106
        $body['index'] = $indexName;
107
        $client = $this->service->getClient();
108
109
        $method = $this->getMethod($instance);
110
        if ($method === 'update') {
111
            $client->indices()->putMapping($body);
112
        } else {
113
            $client->indices()->create($body);
114
        }
115
    }
116
117
    /**
118
     * @param ElasticIndex $instance
119
     * @return array
120
     * @throws NotFoundExceptionInterface
121
     */
122
    protected function createConfigForIndex(ElasticIndex $instance)
123
    {
124
        /** @var FieldResolver $resolver */
125
        $resolver = Injector::inst()->get(FieldResolver::class);
126
        $resolver->setIndex($instance);
127
        $result = [];
128
129
        foreach ($instance->getFulltextFields() as $field) {
130
            $field = $resolver->resolveField($field);
131
            $result = array_merge($result, $field);
132
        }
133
134
        return $result;
135
    }
136
137
    /**
138
     * Take the config from the resolver and build an array that's
139
     * ready to be converted to JSON for Elastic.
140
     *
141
     * @param $config
142
     * @return array[]
143
     */
144
    protected function convertForJSON($config)
145
    {
146
        $base = [];
147
        $typeMap = Statics::getTypeMap();
148
        foreach ($config as $key => &$conf) {
149
            $shortClass = ClassInfo::shortName($conf['origin']);
150
            $dotField = str_replace('_', '.', $conf['fullfield']);
151
            $conf['name'] = sprintf('%s.%s', $shortClass, $dotField);
152
            $base[$conf['name']] = [
153
                'type' => $typeMap[$conf['type'] ?? '*']
154
            ];
155
        }
156
157
        $mappings = ['properties' => $base];
158
159
        return ['body' => ['mappings' => $mappings]];
160
    }
161
162
    /**
163
     * Get the method to use. Create or Update
164
     *
165
     * WARNING: Update often fails because Elastic does not allow changing
166
     * of mappings on-the-fly, it will commonly require a delete-and-recreate!
167
     *
168
     * @param ElasticIndex $index
169
     * @return string
170
     * @throws ClientResponseException
171
     * @throws MissingParameterException
172
     * @throws ServerResponseException
173
     */
174
    protected function getMethod(ElasticIndex $index): string
175
    {
176
        $check = $this->indexIsNew($index);
177
178
        if ($check) {
179
            return 'update';
180
        }
181
182
        return 'create';
183
    }
184
185
    /**
186
     * @param ElasticIndex $index
187
     * @return bool
188
     * @throws ClientResponseException
189
     * @throws MissingParameterException
190
     * @throws ServerResponseException
191
     */
192
    protected function indexIsNew(ElasticIndex $index): bool
193
    {
194
        $check = $this->service->getClient()
195
            ->indices()
196
            ->exists(['index' => $index->getIndexName()])
197
            ->asBool();
0 ignored issues
show
Bug introduced by
The method asBool() does not exist on Http\Promise\Promise. ( Ignorable by Annotation )

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

197
            ->/** @scrutinizer ignore-call */ asBool();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
198
199
        return $check;
200
    }
201
202
    /**
203
     * @return ElasticCoreService|mixed|object|Injector
204
     */
205
    public function getService(): mixed
206
    {
207
        return $this->service;
208
    }
209
210
    /**
211
     * @param ElasticCoreService|mixed|object|Injector $service
212
     */
213
    public function setService(mixed $service): void
214
    {
215
        $this->service = $service;
216
    }
217
}
218