Passed
Pull Request — main (#65)
by Simon
01:14
created

ElasticIndexTask::getGroups()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
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
    public function setGroups(int $groups): void
231
    {
232
        $this->groups = $groups;
233
    }
234
235
    /**
236
     * Index a single class for a given index. {@link static::indexClassForIndex()}
237
     *
238
     * @param bool $isGroup Is a specific group indexed
239
     * @param string $class Class to index
240
     * @param int $group Group to index
241
     * @return int|bool
242
     * @throws HTTPException
243
     * @throws ValidationException
244
     */
245
    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...
246
    {
247
        $this->getLogger()->info(sprintf('Indexing %s for %s', $class, $this->getIndex()->getIndexName()));
248
        [$totalGroups, $groups] = IndexingHelper::getGroupSettings($isGroup, $class, $group);
249
        $this->getLogger()->info(sprintf('Total groups %s', $totalGroups));
250
        do {
251
            try {
252
                //                if ($this->hasPCNTL()) {
253
                //                    // @codeCoverageIgnoreStart
254
                //                    $group = $this->spawnChildren($class, $group, $groups);
255
                //                // @codeCoverageIgnoreEnd
256
                //                } else {
257
                $this->doReindex($group, $class);
258
                //                }
259
                $group++;
260
            } catch (Exception $error) {
261
                // @codeCoverageIgnoreStart
262
                $this->getLogger()->critical($error);
263
                continue;
264
                // @codeCoverageIgnoreEnd
265
            }
266
        } while ($group <= $groups);
267
268
        return $totalGroups;
269
    }
270
271
    /**
272
     * @return ElasticIndex
273
     */
274
    public function getIndex()
275
    {
276
        return $this->index;
277
    }
278
279
    /**
280
     * @param mixed $index
281
     */
282
    public function setIndex($index): void
283
    {
284
        $this->index = $index;
285
    }
286
287
    /**
288
     * Reindex the given group, for each state
289
     *
290
     * @param int $group Group to index
291
     * @param string $class Class to index
292
     * @param bool|int $pid Are we a child process or not
293
     * @throws Exception
294
     */
295
    private function doReindex(int $group, string $class, $pid = false)
296
    {
297
        $start = time();
298
        $states = SiteState::getStates();
299
        foreach ($states as $state) {
300
            if ($state !== SiteState::DEFAULT_STATE && !empty($state)) {
301
                SiteState::withState($state);
302
            }
303
            $this->indexStateClass($group, $class);
304
        }
305
306
        SiteState::withState(SiteState::DEFAULT_STATE);
307
        $end = gmdate('i:s', time() - $start);
308
        $this->getLogger()->info(sprintf('Indexed group %s in %s', $group, $end));
309
310
        // @codeCoverageIgnoreStart
311
        if ($pid !== false) {
312
            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...
313
        }
314
        // @codeCoverageIgnoreEnd
315
    }
316
317
    /**
318
     * Index a group of a class for a specific state and index
319
     *
320
     * @param string $group Group to index
321
     * @param string $class Class to index
322
     * @throws Exception
323
     */
324
    private function indexStateClass(string $group, string $class): void
325
    {
326
        // Generate filtered list of local records
327
        $baseClass = DataObject::getSchema()->baseDataClass($class);
328
        $batchLength = IndexingHelper::getBatchLength();
329
        /** @var DataList|DataObject[] $items */
330
        $items = DataObject::get($baseClass)
331
            ->sort('ID ASC')
332
            ->limit($batchLength, ($group * $batchLength));
333
        if ($items->count()) {
334
            $message = sprintf(
335
                "Indexing group %s for %s",
336
                $group,
337
                $this->getIndex()->getIndexName()
338
            );
339
            if (Director::is_cli()) {
340
                DB::alteration_message($message);
341
            }
342
            $this->logger->info($message);
343
            $this->updateIndex($items);
344
        }
345
    }
346
347
    /**
348
     * Execute the update on the client
349
     *
350
     * @param SS_List $items Items to index
351
     * @throws Exception
352
     * @throws NotFoundExceptionInterface
353
     */
354
    private function updateIndex($items): void
355
    {
356
        $index = $this->getIndex();
357
        $this->service->updateIndex($index, $items);
358
    }
359
}
360