ElasticCoreService::getFactory()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
c 2
b 0
f 0
nc 1
nop 1
dl 0
loc 8
rs 10
1
<?php
2
/**
3
 * class ElasticCoreService|Firesphere\ElasticSearch\Services\ElasticCoreService Base service for communicating with the core
4
 *
5
 * @package Firesphere\Elastic\Search
6
 * @author Simon `Firesphere` Erkelens; Marco `Sheepy` Hermo
7
 * @copyright Copyright (c) 2018 - now() Firesphere & Sheepy
8
 */
9
10
namespace Firesphere\ElasticSearch\Services;
11
12
use Elastic\ElasticSearch\Client;
13
use Elastic\Elasticsearch\ClientBuilder;
14
use Elastic\Elasticsearch\Exception\AuthenticationException;
15
use Elastic\Elasticsearch\Exception\ClientResponseException;
16
use Elastic\Elasticsearch\Exception\ServerResponseException;
17
use Elastic\Elasticsearch\Response\Elasticsearch;
18
use Firesphere\ElasticSearch\Factories\DocumentFactory;
19
use Firesphere\ElasticSearch\Indexes\ElasticIndex;
20
use Firesphere\SearchBackend\Services\BaseService;
21
use Http\Promise\Promise;
22
use Psr\Container\NotFoundExceptionInterface;
23
use ReflectionException;
24
use SilverStripe\Core\Config\Configurable;
25
use SilverStripe\Core\Environment;
26
use SilverStripe\Core\Injector\Injector;
27
use SilverStripe\ORM\SS_List;
28
29
/**
30
 * Class ElasticCoreService provides the base connection to Elastic.
31
 *
32
 * Default service to connect to Elastic and handle all base requirements to support Elastic.
33
 * Default constants are available to support any set up.
34
 *
35
 * @package Firesphere\Elastic\Search
36
 */
37
class ElasticCoreService extends BaseService
38
{
39
    use Configurable;
40
41
    public const ID_KEY = 'UniqueKey';
42
43
    private const ENVIRONMENT_VARS = [
44
        'ELASTIC_ENDPOINT' => 'host',
45
        'ELASTIC_USERNAME' => 'username',
46
        'ELASTIC_PASSWORD' => 'password',
47
        'ELASTIC_API_KEY'  => 'apiKey',
48
        'ELASTIC_PORT'     => 'port',
49
        'ELASTIC_PROTOCOL' => 'protocol'
50
    ];
51
52
    /**
53
     * @var Client Comms client with Elastic
54
     */
55
    protected $client;
56
57
    /**
58
     * @throws ReflectionException
59
     * @throws AuthenticationException
60
     */
61
    public function __construct()
62
    {
63
        $endpoint0 = $this->getEndpointConfig();
64
        $uri = str_replace(['https://', 'http://'], '', $endpoint0['host']);
65
        $uri = sprintf(
66
            '%s://%s:%s',
67
            $endpoint0['protocol'],
68
            $uri,
69
            $endpoint0['port'] ?: 9200
70
        );
71
        $builder = $this->getBuilder($uri, $endpoint0);
72
        $this->client = $builder->build();
73
        parent::__construct(ElasticIndex::class);
74
    }
75
76
    /**
77
     * @return array
78
     */
79
    private function getEndpointConfig(): array
80
    {
81
        $config = self::config()->get('config');
82
        if ($config['endpoint'] === 'ENVIRONMENT') {
83
            $endpoint0 = [];
84
            foreach (self::ENVIRONMENT_VARS as $envVar => $elasticVar) {
85
                if (!Environment::hasEnv($envVar)) {
86
                    continue; // skip vars not set
87
                }
88
                $endpoint0[$elasticVar] = Environment::getEnv($envVar);
89
            }
90
        } else {
91
            $endpoint0 = $config['endpoint'][0];
92
        }
93
        // default to https
94
        $endpoint0['protocol'] = $endpoint0['protocol'] ?? 'https';
95
96
        return $endpoint0;
97
    }
98
99
    /**
100
     * @param string $uri
101
     * @param array $endpoint0
102
     * @return ClientBuilder
103
     */
104
    private function getBuilder(string $uri, array $endpoint0): ClientBuilder
105
    {
106
        $builder = ClientBuilder::create()
107
            ->setHosts([$uri]);
108
        if (isset($endpoint0['apiKey'])) {
109
            $builder->setApiKey($endpoint0['apiKey']);
110
        }
111
        if (isset($endpoint0['username']) && isset($endpoint0['password'])) {
112
            $builder->setBasicAuthentication($endpoint0['username'], $endpoint0['password']);
113
        }
114
        // Disable the SSL Certificate check
115
        if (Environment::getEnv('ELASTIC_DISABLE_SSLCHECK')) {
116
            $builder->setSSLVerification(false);
117
        }
118
119
        return $builder;
120
    }
121
122
    public function getClient(): Client
123
    {
124
        return $this->client;
125
    }
126
127
    public function setClient(Client $client): void
128
    {
129
        $this->client = $client;
130
    }
131
132
    /**
133
     * @param ElasticIndex $index
134
     * @param SS_List $items
135
     * @return array|bool|Elasticsearch|Promise
136
     * @throws NotFoundExceptionInterface
137
     * @throws ClientResponseException
138
     * @throws ServerResponseException
139
     */
140
    public function updateIndex($index, $items, $returnDocs = false)
141
    {
142
        $result = null;
143
        $fields = $index->getFieldsForIndexing();
144
        $factory = $this->getFactory($items);
145
        $docs = $factory->buildItems($fields, $index);
146
        $body = ['body' => []];
147
        if (count($docs)) {
148
            $body = $this->buildBody($docs, $index);
149
            $result = $this->client->bulk($body);
150
        }
151
        if ($returnDocs) {
152
            return $body['body'];
153
        }
154
155
        return $result;
156
157
    }
158
159
    /**
160
     * Get the document factory prepared
161
     *
162
     * @param SS_List $items
163
     * @return DocumentFactory
164
     * @throws NotFoundExceptionInterface
165
     */
166
    protected function getFactory($items): DocumentFactory
167
    {
168
        $factory = Injector::inst()->get(DocumentFactory::class);
169
        $factory->setItems($items);
170
        $factory->setClass($items->first()->ClassName);
171
        $factory->setDebug(true);
172
173
        return $factory;
174
    }
175
176
    /**
177
     * @param array $docs
178
     * @param ElasticIndex $index
179
     * @return array
180
     */
181
    public function buildBody(array $docs, ElasticIndex $index): array
182
    {
183
        $body = ['body' => []];
184
        if (self::config()->get('pipeline')) {
185
            $body['body'] = [ // @todo Check if this is indeed how it works
186
                              'index'    => $index->getIndexName(),
187
                              'pipeline' => self::config()->get('pipeline')
188
            ];
189
        }
190
        foreach ($docs as $doc) {
191
            $body['body'][] = [
192
                'index' => [
193
                    '_index' => $index->getIndexName(),
194
                    '_id'    => $doc[self::ID_KEY]
195
                ]
196
            ];
197
            $doc['_extract_binary_content'] = true;
198
            $doc['_reduce_whitespace'] = true;
199
            $doc['_run_ml_inference'] = false;
200
            $body['body'][] = $doc;
201
        }
202
203
        return $body;
204
    }
205
}
206