ElasticIndexTask::getGroups()   A
last analyzed

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\Versioned\Versioned;
34
35
/**
36
 * Class ElasticIndexTask
37
 *
38
 * @description Index items to Elastic through a tasks
39
 * @package Firesphere\Elastic\Search
40
 */
41
class ElasticIndexTask extends BuildTask
42
{
43
    use IndexingTrait;
44
45
    /**
46
     * URLSegment of this task
47
     *
48
     * @var string
49
     */
50
    private static $segment = 'ElasticIndexTask';
0 ignored issues
show
introduced by
The private property $segment is not used, and could be removed.
Loading history...
51
    /**
52
     * Store the current states for all instances of SiteState
53
     *
54
     * @var array
55
     */
56
    public $currentStates;
57
    /**
58
     * My name
59
     *
60
     * @var string
61
     */
62
    protected $title = 'Elastic Index update';
63
    /**
64
     * What do I do?
65
     *
66
     * @var string
67
     */
68
    protected $description = 'Add or update documents to an existing Elastic core.';
69
70
    /**
71
     * @var ElasticCoreService
72
     */
73
    protected $service;
74
75
    /**
76
     * @var LoggerInterface
77
     */
78
    protected $logger;
79
80
    /**
81
     * @var Bool
82
     */
83
    protected $debug;
84
85
    /**
86
     * @var ElasticIndex
87
     */
88
    protected $index;
89
90
    /**
91
     * @var int
92
     */
93
    protected $groups = 0;
94
95
    /**
96
     * ElasticIndexTask constructor. Sets up the document factory
97
     *
98
     * @throws NotFoundExceptionInterface
99
     */
100
    public function __construct()
101
    {
102
        parent::__construct();
103
        // Only index live items.
104
        // The old FTS module also indexed Draft items. This is unnecessary
105
        // If versioned is needed, a separate Versioned Search module is required
106
        Versioned::set_reading_mode(Versioned::DEFAULT_MODE);
107
        $this->setService(Injector::inst()->get(ElasticCoreService::class));
108
        $this->setLogger(Injector::inst()->get(LoggerInterface::class));
109
    }
110
111
    /**
112
     * @param HTTPRequest $request
113
     * @return bool|int
114
     * @throws HttpClientException
115
     * @throws HttpException
116
     * @throws NotFoundExceptionInterface
117
     * @throws ClientResponseException
118
     * @throws MissingParameterException
119
     * @throws ServerResponseException
120
     */
121
    public function run($request)
122
    {
123
        $start = time();
124
        $this->getLogger()->info(date('Y-m-d H:i:s'));
125
        [$vars, $group, $isGroup] = $this->taskSetup($request);
126
        $groups = 0;
127
        $indexes = $this->service->getValidIndexes($request->getVar('index'));
128
        foreach ($indexes as $indexName) {
129
            /** @var ElasticIndex $index */
130
            $index = Injector::inst()->get($indexName, false);
131
            $this->setIndex($index);
132
            $indexClasses = $this->index->getClasses();
133
            $classes = $this->getClasses($vars, $indexClasses);
134
            if (!count($classes)) {
135
                continue;
136
            }
137
            // If clearing, also configure
138
            if ($index->deleteIndex($request)) {
139
                (new ElasticConfigureTask())->configureIndex($index);
140
            }
141
142
            // Get the groups
143
            $groups = $this->indexClassForIndex($classes, $isGroup, $group);
144
        }
145
        $this->getLogger()->info(gmdate('Y-m-d H:i:s'));
146
        $time = gmdate('H:i:s', (time() - $start));
147
        $this->getLogger()->info(sprintf('Time taken: %s', $time));
148
149
        $this->groups = $groups;
150
    }
151
152
    /**
153
     * @return mixed
154
     */
155
    public function getLogger()
156
    {
157
        return $this->logger;
158
    }
159
160
    /**
161
     * @param mixed $logger
162
     */
163
    public function setLogger($logger): void
164
    {
165
        $this->logger = $logger;
166
    }
167
168
    /**
169
     * get the classes to run for this task execution
170
     *
171
     * @param array $vars URL GET Parameters
172
     * @param array $classes Classes to index
173
     * @return array
174
     */
175
    protected function getClasses(array $vars, array $classes): array
176
    {
177
        if (isset($vars['class'])) {
178
            return array_intersect($classes, [$vars['class']]);
179
        }
180
181
        return $classes;
182
    }
183
184
    /**
185
     * @return mixed
186
     */
187
    public function getService()
188
    {
189
        return $this->service;
190
    }
191
192
    /**
193
     * @param mixed $service
194
     */
195
    public function setService($service): void
196
    {
197
        $this->service = $service;
198
    }
199
200
    public function isDebug(): bool
201
    {
202
        return $this->debug ?? false;
203
    }
204
205
    /**
206
     * Set the debug mode
207
     *
208
     * @param bool $debug Set the task in debug mode
209
     * @param bool $force Force a task in debug mode, despite e.g. being Live and not CLI
210
     * @return self
211
     */
212
    public function setDebug(bool $debug, bool $force = false): self
213
    {
214
        // Make the debug a configurable, forcing it to always be false from config
215
        if (!$force && ElasticCoreService::config()->get('debug') === false) {
216
            $debug = false;
217
        }
218
219
        $this->debug = $debug;
220
221
        return $this;
222
    }
223
224
    public function getGroups(): int
225
    {
226
        return $this->groups;
227
    }
228
229
    /**
230
     * Index a single class for a given index. {@link static::indexClassForIndex()}
231
     *
232
     * @param bool $isGroup Is a specific group indexed
233
     * @param string $class Class to index
234
     * @param int $group Group to index
235
     * @return int|bool
236
     */
237
    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...
238
    {
239
        $this->getLogger()->info(sprintf('Indexing %s for %s', $class, $this->getIndex()->getIndexName()));
240
        [$totalGroups, $groups] = IndexingHelper::getGroupSettings($isGroup, $class, $group);
241
        $this->getLogger()->info(sprintf('Total groups %s', $totalGroups));
242
        do {
243
            try {
244
                //                if ($this->hasPCNTL()) {
245
                //                    // @codeCoverageIgnoreStart
246
                //                    $group = $this->spawnChildren($class, $group, $groups);
247
                //                // @codeCoverageIgnoreEnd
248
                //                } else {
249
                $this->doReindex($group, $class);
250
                //                }
251
                $group++;
252
            } catch (Exception $error) {
253
                // @codeCoverageIgnoreStart
254
                $this->getLogger()->critical($error);
255
                continue;
256
                // @codeCoverageIgnoreEnd
257
            }
258
        } while ($group <= $groups);
259
260
        return $totalGroups;
261
    }
262
263
    /**
264
     * @return ElasticIndex
265
     */
266
    public function getIndex()
267
    {
268
        return $this->index;
269
    }
270
271
    /**
272
     * @param mixed $index
273
     */
274
    public function setIndex($index): void
275
    {
276
        $this->index = $index;
277
    }
278
279
    /**
280
     * Reindex the given group, for each state
281
     *
282
     * @param int $group Group to index
283
     * @param string $class Class to index
284
     * @param bool|int $pid Are we a child process or not
285
     * @throws Exception
286
     */
287
    private function doReindex(int $group, string $class, $pid = false)
288
    {
289
        $start = time();
290
        $states = SiteState::getStates();
291
        foreach ($states as $state) {
292
            if ($state !== SiteState::DEFAULT_STATE && !empty($state)) {
293
                SiteState::withState($state);
294
            }
295
            $this->indexStateClass($group, $class);
296
        }
297
298
        SiteState::withState(SiteState::DEFAULT_STATE);
299
        $end = gmdate('i:s', time() - $start);
300
        $this->getLogger()->info(sprintf('Indexed group %s in %s', $group, $end));
301
302
        // @codeCoverageIgnoreStart
303
        if ($pid !== false) {
304
            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...
305
        }
306
        // @codeCoverageIgnoreEnd
307
    }
308
309
    /**
310
     * Index a group of a class for a specific state and index
311
     *
312
     * @param string $group Group to index
313
     * @param string $class Class to index
314
     * @throws NotFoundExceptionInterface
315
     */
316
    private function indexStateClass(string $group, string $class): void
317
    {
318
        // Generate filtered list of local records
319
        $baseClass = DataObject::getSchema()->baseDataClass($class);
320
        $batchLength = IndexingHelper::getBatchLength();
321
        /** @var DataList|DataObject[] $items */
322
        $items = DataObject::get($baseClass)
323
            ->sort('ID ASC')
324
            ->limit($batchLength, ($group * $batchLength));
325
        if ($items->count()) {
326
            $message = sprintf(
327
                "Indexing group %s for %s",
328
                $group,
329
                $this->getIndex()->getIndexName()
330
            );
331
            if (Director::is_cli()) {
332
                DB::alteration_message($message);
333
            }
334
            $this->logger->info($message);
335
            $this->updateIndex($items);
336
        }
337
    }
338
339
    /**
340
     * Execute the update on the client
341
     *
342
     * @param SS_List $items Items to index
343
     * @throws Exception
344
     * @throws NotFoundExceptionInterface
345
     */
346
    private function updateIndex($items): void
347
    {
348
        $index = $this->getIndex();
349
        $this->service->updateIndex($index, $items);
350
    }
351
}
352