Passed
Push — main ( 74e495...77a257 )
by Simon
01:15
created

ElasticConfigureTask::convertForJSON()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 2
eloc 9
nc 2
nop 1
dl 0
loc 14
rs 9.9666
c 1
b 1
f 0
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 Elastic\Elasticsearch\Response\Elasticsearch;
9
use Exception;
10
use Firesphere\ElasticSearch\Helpers\Statics;
11
use Firesphere\ElasticSearch\Indexes\ElasticIndex;
12
use Firesphere\ElasticSearch\Services\ElasticCoreService;
13
use Firesphere\SearchBackend\Helpers\FieldResolver;
14
use Firesphere\SearchBackend\Traits\LoggerTrait;
15
use Psr\Container\NotFoundExceptionInterface;
16
use SilverStripe\Control\HTTPRequest;
17
use SilverStripe\Core\ClassInfo;
18
use SilverStripe\Core\Injector\Injector;
19
use SilverStripe\Dev\BuildTask;
20
use SilverStripe\ORM\DB;
21
22
class ElasticConfigureTask extends BuildTask
23
{
24
    use LoggerTrait;
25
26
    /**
27
     * @var string URLSegment
28
     */
29
    private static $segment = 'ElasticConfigureTask';
0 ignored issues
show
introduced by
The private property $segment is not used, and could be removed.
Loading history...
30
    /**
31
     * @var string Title
32
     */
33
    protected $title = 'Configure Elastic cores';
34
    /**
35
     * @var string Description
36
     */
37
    protected $description = 'Create or reload a Elastic Core by adding or reloading a configuration.';
38
    /**
39
     * @var ElasticCoreService $service
40
     */
41
    protected $service;
42
43
    /**
44
     * @throws NotFoundExceptionInterface
45
     */
46
    public function __construct()
47
    {
48
        parent::__construct();
49
        $this->service = Injector::inst()->get(ElasticCoreService::class);
50
    }
51
52
    /**
53
     * Run the config
54
     *
55
     * @param HTTPRequest $request
56
     * @return void|array
57
     * @throws NotFoundExceptionInterface
58
     */
59
    public function run($request)
60
    {
61
        $this->extend('onBeforeElasticConfigureTask', $request);
62
63
        $indexes = $this->service->getValidIndexes();
64
65
        $result = [];
66
67
        foreach ($indexes as $index) {
68
            try {
69
                /** @var ElasticIndex $instance */
70
                $instance = Injector::inst()->get($index, false);
71
72
                if ($request->getVar('clear') && $instance->indexExists()) {
73
                    $this->getLogger()->info(sprintf('Clearing index %s', $instance->getIndexName()));
74
                    $deleteResult = $this->service->getClient()->indices()->delete(['index' => $instance->getIndexName()]);
75
                    $result[] = $deleteResult->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

75
                    /** @scrutinizer ignore-call */ 
76
                    $result[] = $deleteResult->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...
76
                }
77
78
                $configResult = $this->configureIndex($instance);
79
                $result[] = $configResult->asBool();
80
            } catch (Exception $error) {
81
                // @codeCoverageIgnoreStart
82
                $this->getLogger()->error(sprintf('Core loading failed for %s', $index));
83
                $this->getLogger()->error($error->getMessage()); // in browser mode, it might not always show
84
                // Continue to the next index
85
                continue;
86
                // @codeCoverageIgnoreEnd
87
            }
88
            $this->extend('onAfterConfigureIndex', $index);
89
        }
90
91
        $this->extend('onAfterElasticConfigureTask');
92
93
        if ($request->getVar('istest')) {
94
            return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result returns the type array|boolean[] which is incompatible with the return type mandated by SilverStripe\Dev\BuildTask::run() of void.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
95
        }
96
    }
97
98
    /**
99
     * Update/create a store
100
     * @param ElasticIndex $instance
101
     * @return Elasticsearch
102
     * @throws ClientResponseException
103
     * @throws MissingParameterException
104
     * @throws ServerResponseException
105
     * @throws NotFoundExceptionInterface
106
     */
107
    protected function configureIndex($instance): Elasticsearch
108
    {
109
        $indexName = $instance->getIndexName();
110
111
112
        $instanceConfig = $this->createConfigForIndex($instance);
113
114
        $mappings = $this->convertForJSON($instanceConfig);
115
116
        $body['index'] = $indexName;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$body was never initialized. Although not strictly required by PHP, it is generally a good practice to add $body = array(); before regardless.
Loading history...
117
        $client = $this->service->getClient();
118
119
        $method = $this->getMethod($instance);
120
        $msg = "%s index %s";
121
        if ($method === 'update') {
122
            $body['body'] = $mappings;
123
            $msg = sprintf($msg, 'Updating', $indexName);
124
            DB::alteration_message($msg);
125
            $this->getLogger()->info($msg);
126
            return $client->indices()->putMapping($body);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $client->indices()->putMapping($body) could return the type Http\Promise\Promise which is incompatible with the type-hinted return Elastic\Elasticsearch\Response\Elasticsearch. Consider adding an additional type-check to rule them out.
Loading history...
127
        } else {
128
            $body['body']['mappings'] = $mappings;
129
            $msg = sprintf($msg, 'Creating', $indexName);
130
            DB::alteration_message($msg);
131
            $this->getLogger()->info($msg);
132
            return $client->indices()->create($body);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $client->indices()->create($body) could return the type Http\Promise\Promise which is incompatible with the type-hinted return Elastic\Elasticsearch\Response\Elasticsearch. Consider adding an additional type-check to rule them out.
Loading history...
133
        }
134
    }
135
136
    /**
137
     * @param ElasticIndex $instance
138
     * @return array
139
     * @throws NotFoundExceptionInterface
140
     */
141
    protected function createConfigForIndex(ElasticIndex $instance)
142
    {
143
        /** @var FieldResolver $resolver */
144
        $resolver = Injector::inst()->get(FieldResolver::class);
145
        $resolver->setIndex($instance);
146
        $result = [];
147
148
        foreach ($instance->getFulltextFields() as $field) {
149
            $field = $resolver->resolveField($field);
150
            $result = array_merge($result, $field);
151
        }
152
153
        return $result;
154
    }
155
156
    /**
157
     * Take the config from the resolver and build an array that's
158
     * ready to be converted to JSON for Elastic.
159
     *
160
     * @param $config
161
     * @return array[]
162
     */
163
    protected function convertForJSON($config)
164
    {
165
        $base = [];
166
        $typeMap = Statics::getTypeMap();
167
        foreach ($config as $key => &$conf) {
168
            $shortClass = ClassInfo::shortName($conf['origin']);
169
            $dotField = str_replace('_', '.', $conf['fullfield']);
170
            $conf['name'] = sprintf('%s.%s', $shortClass, $dotField);
171
            $base[$conf['name']] = [
172
                'type' => $typeMap[$conf['type'] ?? '*']
173
            ];
174
        }
175
176
        return ['properties' => $base];
177
    }
178
179
    /**
180
     * Get the method to use. Create or Update
181
     *
182
     * WARNING: Update often fails because Elastic does not allow changing
183
     * of mappings on-the-fly, it will commonly require a delete-and-recreate!
184
     *
185
     * @param ElasticIndex $index
186
     * @return string
187
     * @throws ClientResponseException
188
     * @throws MissingParameterException
189
     * @throws ServerResponseException
190
     */
191
    protected function getMethod(ElasticIndex $index): string
192
    {
193
        $check = $index->indexExists();
194
195
        if ($check) {
196
            return 'update';
197
        }
198
199
        return 'create';
200
    }
201
202
    /**
203
     * @return ElasticCoreService|mixed|object|Injector
204
     */
205
    public function getService(): mixed
206
    {
207
        return $this->service;
208
    }
209
}
210