Passed
Push — main ( 87db12...b71501 )
by Simon
01:24
created

ElasticIndexTask   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 309
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 30
eloc 88
c 5
b 0
f 0
dl 0
loc 309
rs 10

16 Methods

Rating   Name   Duplication   Size   Complexity  
A isDebug() 0 3 1
A setService() 0 3 1
A setLogger() 0 3 1
A __construct() 0 9 1
A getClasses() 0 7 2
A getService() 0 3 1
A setDebug() 0 10 3
A run() 0 29 4
A getLogger() 0 3 1
A getGroups() 0 3 1
A setIndex() 0 3 1
A getIndex() 0 3 1
A doReindex() 0 18 5
A indexClass() 0 24 3
A indexStateClass() 0 20 3
A updateIndex() 0 4 1
1
<?php
2
/**
3
 * Class ElasticIndexTask|Firesphere\ElasticSearch\Tasks\ElasticIndexTask Index Elastic cores
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\Tasks;
11
12
use Elastic\Elasticsearch\Exception\ClientResponseException;
13
use Elastic\Elasticsearch\Exception\HttpClientException;
14
use Elastic\Elasticsearch\Exception\MissingParameterException;
15
use Elastic\Elasticsearch\Exception\ServerResponseException;
16
use Exception;
17
use Firesphere\ElasticSearch\Indexes\ElasticIndex;
18
use Firesphere\ElasticSearch\Services\ElasticCoreService;
19
use Firesphere\SearchBackend\Helpers\IndexingHelper;
20
use Firesphere\SearchBackend\States\SiteState;
21
use Firesphere\SearchBackend\Traits\IndexingTraits\IndexingTrait;
22
use HttpException;
23
use Psr\Container\NotFoundExceptionInterface;
24
use Psr\Log\LoggerInterface;
25
use SilverStripe\Control\Director;
26
use SilverStripe\Control\HTTPRequest;
27
use SilverStripe\Core\Injector\Injector;
28
use SilverStripe\Dev\BuildTask;
29
use SilverStripe\ORM\DataList;
30
use SilverStripe\ORM\DataObject;
31
use SilverStripe\ORM\DB;
32
use SilverStripe\ORM\SS_List;
33
use SilverStripe\ORM\ValidationException;
34
use SilverStripe\Versioned\Versioned;
35
36
/**
37
 * Class ElasticIndexTask
38
 *
39
 * @description Index items to Elastic through a tasks
40
 * @package Firesphere\Elastic\Search
41
 */
42
class ElasticIndexTask extends BuildTask
43
{
44
    use IndexingTrait;
45
46
    /**
47
     * URLSegment of this task
48
     *
49
     * @var string
50
     */
51
    private static $segment = 'ElasticIndexTask';
0 ignored issues
show
introduced by
The private property $segment is not used, and could be removed.
Loading history...
52
    /**
53
     * Store the current states for all instances of SiteState
54
     *
55
     * @var array
56
     */
57
    public $currentStates;
58
    /**
59
     * My name
60
     *
61
     * @var string
62
     */
63
    protected $title = 'Elastic Index update';
64
    /**
65
     * What do I do?
66
     *
67
     * @var string
68
     */
69
    protected $description = 'Add or update documents to an existing Elastic core.';
70
71
    /**
72
     * @var ElasticCoreService
73
     */
74
    protected $service;
75
76
    /**
77
     * @var LoggerInterface
78
     */
79
    protected $logger;
80
81
    /**
82
     * @var Bool
83
     */
84
    protected $debug;
85
86
    /**
87
     * @var ElasticIndex
88
     */
89
    protected $index;
90
91
    /**
92
     * @var int
93
     */
94
    protected $groups = 0;
95
96
    /**
97
     * ElasticIndexTask constructor. Sets up the document factory
98
     *
99
     * @throws NotFoundExceptionInterface
100
     */
101
    public function __construct()
102
    {
103
        parent::__construct();
104
        // Only index live items.
105
        // The old FTS module also indexed Draft items. This is unnecessary
106
        // If versioned is needed, a separate Versioned Search module is required
107
        Versioned::set_reading_mode(Versioned::DEFAULT_MODE);
108
        $this->setService(Injector::inst()->get(ElasticCoreService::class));
109
        $this->setLogger(Injector::inst()->get(LoggerInterface::class));
110
    }
111
112
    /**
113
     * @param HTTPRequest $request
114
     * @return bool|int
115
     * @throws HttpClientException
116
     * @throws HttpException
117
     * @throws NotFoundExceptionInterface
118
     * @throws ClientResponseException
119
     * @throws MissingParameterException
120
     * @throws ServerResponseException
121
     */
122
    public function run($request)
123
    {
124
        $start = time();
125
        $this->getLogger()->info(date('Y-m-d H:i:s'));
126
        [$vars, $group, $isGroup] = $this->taskSetup($request);
127
        $groups = 0;
128
        $indexes = $this->service->getValidIndexes($request->getVar('index'));
129
        foreach ($indexes as $indexName) {
130
            /** @var ElasticIndex $index */
131
            $index = Injector::inst()->get($indexName, false);
132
            $this->setIndex($index);
133
            $indexClasses = $this->index->getClasses();
134
            $classes = $this->getClasses($vars, $indexClasses);
135
            if (!count($classes)) {
136
                continue;
137
            }
138
            // If clearing, also configure
139
            if ($index->deleteIndex($request)) {
140
                (new ElasticConfigureTask())->configureIndex($index);
141
            }
142
143
            // Get the groups
144
            $groups = $this->indexClassForIndex($classes, $isGroup, $group);
145
        }
146
        $this->getLogger()->info(gmdate('Y-m-d H:i:s'));
147
        $time = gmdate('H:i:s', (time() - $start));
148
        $this->getLogger()->info(sprintf('Time taken: %s', $time));
149
150
        $this->groups = $groups;
151
    }
152
153
    /**
154
     * @return mixed
155
     */
156
    public function getLogger()
157
    {
158
        return $this->logger;
159
    }
160
161
    /**
162
     * @param mixed $logger
163
     */
164
    public function setLogger($logger): void
165
    {
166
        $this->logger = $logger;
167
    }
168
169
    /**
170
     * get the classes to run for this task execution
171
     *
172
     * @param array $vars URL GET Parameters
173
     * @param array $classes Classes to index
174
     * @return array
175
     */
176
    protected function getClasses(array $vars, array $classes): array
177
    {
178
        if (isset($vars['class'])) {
179
            return array_intersect($classes, [$vars['class']]);
180
        }
181
182
        return $classes;
183
    }
184
185
    /**
186
     * @return mixed
187
     */
188
    public function getService()
189
    {
190
        return $this->service;
191
    }
192
193
    /**
194
     * @param mixed $service
195
     */
196
    public function setService($service): void
197
    {
198
        $this->service = $service;
199
    }
200
201
    public function isDebug(): bool
202
    {
203
        return $this->debug ?? false;
204
    }
205
206
    /**
207
     * Set the debug mode
208
     *
209
     * @param bool $debug Set the task in debug mode
210
     * @param bool $force Force a task in debug mode, despite e.g. being Live and not CLI
211
     * @return self
212
     */
213
    public function setDebug(bool $debug, bool $force = false): self
214
    {
215
        // Make the debug a configurable, forcing it to always be false from config
216
        if (!$force && ElasticCoreService::config()->get('debug') === false) {
217
            $debug = false;
218
        }
219
220
        $this->debug = $debug;
221
222
        return $this;
223
    }
224
225
    public function getGroups(): int
226
    {
227
        return $this->groups;
228
    }
229
230
    /**
231
     * Index a single class for a given index. {@link static::indexClassForIndex()}
232
     *
233
     * @param bool $isGroup Is a specific group indexed
234
     * @param string $class Class to index
235
     * @param int $group Group to index
236
     * @return int|bool
237
     */
238
    private function indexClass(bool $isGroup, string $class, int $group)
0 ignored issues
show
Unused Code introduced by
The method indexClass() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
239
    {
240
        $this->getLogger()->info(sprintf('Indexing %s for %s', $class, $this->getIndex()->getIndexName()));
241
        [$totalGroups, $groups] = IndexingHelper::getGroupSettings($isGroup, $class, $group);
242
        $this->getLogger()->info(sprintf('Total groups %s', $totalGroups));
243
        do {
244
            try {
245
                //                if ($this->hasPCNTL()) {
246
                //                    // @codeCoverageIgnoreStart
247
                //                    $group = $this->spawnChildren($class, $group, $groups);
248
                //                // @codeCoverageIgnoreEnd
249
                //                } else {
250
                $this->doReindex($group, $class);
251
                //                }
252
                $group++;
253
            } catch (Exception $error) {
254
                // @codeCoverageIgnoreStart
255
                $this->getLogger()->critical($error);
256
                continue;
257
                // @codeCoverageIgnoreEnd
258
            }
259
        } while ($group <= $groups);
260
261
        return $totalGroups;
262
    }
263
264
    /**
265
     * @return ElasticIndex
266
     */
267
    public function getIndex()
268
    {
269
        return $this->index;
270
    }
271
272
    /**
273
     * @param mixed $index
274
     */
275
    public function setIndex($index): void
276
    {
277
        $this->index = $index;
278
    }
279
280
    /**
281
     * Reindex the given group, for each state
282
     *
283
     * @param int $group Group to index
284
     * @param string $class Class to index
285
     * @param bool|int $pid Are we a child process or not
286
     * @throws Exception
287
     */
288
    private function doReindex(int $group, string $class, $pid = false)
289
    {
290
        $start = time();
291
        $states = SiteState::getStates();
292
        foreach ($states as $state) {
293
            if ($state !== SiteState::DEFAULT_STATE && !empty($state)) {
294
                SiteState::withState($state);
295
            }
296
            $this->indexStateClass($group, $class);
297
        }
298
299
        SiteState::withState(SiteState::DEFAULT_STATE);
300
        $end = gmdate('i:s', time() - $start);
301
        $this->getLogger()->info(sprintf('Indexed group %s in %s', $group, $end));
302
303
        // @codeCoverageIgnoreStart
304
        if ($pid !== false) {
305
            exit(0);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
306
        }
307
        // @codeCoverageIgnoreEnd
308
    }
309
310
    /**
311
     * Index a group of a class for a specific state and index
312
     *
313
     * @param string $group Group to index
314
     * @param string $class Class to index
315
     * @throws Exception
316
     */
317
    private function indexStateClass(string $group, string $class): void
318
    {
319
        // Generate filtered list of local records
320
        $baseClass = DataObject::getSchema()->baseDataClass($class);
321
        $batchLength = IndexingHelper::getBatchLength();
322
        /** @var DataList|DataObject[] $items */
323
        $items = DataObject::get($baseClass)
324
            ->sort('ID ASC')
325
            ->limit($batchLength, ($group * $batchLength));
326
        if ($items->count()) {
327
            $message = sprintf(
328
                "Indexing group %s for %s",
329
                $group,
330
                $this->getIndex()->getIndexName()
331
            );
332
            if (Director::is_cli()) {
333
                DB::alteration_message($message);
334
            }
335
            $this->logger->info($message);
336
            $this->updateIndex($items);
337
        }
338
    }
339
340
    /**
341
     * Execute the update on the client
342
     *
343
     * @param SS_List $items Items to index
344
     * @throws Exception
345
     * @throws NotFoundExceptionInterface
346
     */
347
    private function updateIndex($items): void
348
    {
349
        $index = $this->getIndex();
350
        $this->service->updateIndex($index, $items);
351
    }
352
}
353