DataObjectElasticExtension::deleteFromElastic()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 3
nop 0
dl 0
loc 16
rs 9.9332
c 0
b 0
f 0
1
<?php
2
/**
3
 * class DataObjectExtension|Firesphere\ElasticSearch\Extensions\DataObjectExtension Adds checking if changes should be
4
 * pushed to Elastic
5
 *
6
 * @package Firesphere\Elastic\Search
7
 * @author Simon `Firesphere` Erkelens; Marco `Sheepy` Hermo
8
 * @copyright Copyright (c) 2018 - now() Firesphere & Sheepy
9
 */
10
11
namespace Firesphere\ElasticSearch\Extensions;
12
13
use Elastic\Elasticsearch\Exception\ClientResponseException;
14
use Elastic\Elasticsearch\Exception\ServerResponseException;
15
use Elastic\Elasticsearch\Response\Elasticsearch;
16
use Exception;
17
use Firesphere\ElasticSearch\Indexes\ElasticIndex;
18
use Firesphere\ElasticSearch\Services\ElasticCoreService;
19
use Firesphere\SearchBackend\Extensions\DataObjectSearchExtension;
20
use Http\Promise\Promise;
21
use Psr\Container\NotFoundExceptionInterface;
22
use Psr\Log\LoggerInterface;
23
use SilverStripe\CMS\Model\SiteTree;
24
use SilverStripe\Core\Injector\Injector;
25
use SilverStripe\ORM\ArrayList;
26
use SilverStripe\ORM\DataExtension;
27
use SilverStripe\ORM\DataObject;
28
use SilverStripe\Versioned\Versioned;
29
30
/**
31
 * Class \Firesphere\ElasticSearch\Extensions\DataObjectElasticExtension
32
 *
33
 * @property DataObject|DataObjectElasticExtension $owner
34
 */
35
class DataObjectElasticExtension extends DataExtension
36
{
37
    protected $deletedFromElastic;
38
39
    /**
40
     * @throws NotFoundExceptionInterface
41
     */
42
    public function onAfterDelete()
43
    {
44
        parent::onAfterDelete();
45
        $this->deleteFromElastic();
46
    }
47
48
    /**
49
     * Can be called directly, if a DataObject needs to be removed
50
     * immediately.
51
     * @return bool|Elasticsearch|Promise
52
     * @throws NotFoundExceptionInterface
53
     */
54
    public function deleteFromElastic()
55
    {
56
        $result = false;
57
        $service = Injector::inst()->get(ElasticCoreService::class);
58
        $indexes = $service->getValidIndexes();
59
        foreach ($indexes as $index) {
60
            /** @var ElasticIndex $idx */
61
            $idx = Injector::inst()->get($index);
62
            $config = ElasticIndex::config()->get($idx->getIndexName());
63
            if (in_array($this->owner->ClassName, $config['Classes'])) {
64
                $deleteQuery = $this->getDeleteQuery($idx);
65
                $result = $this->executeQuery($service, $deleteQuery);
66
            }
67
        }
68
69
        return $result;
70
    }
71
72
    /**
73
     * @param ElasticIndex $index
74
     * @return array
75
     */
76
    private function getDeleteQuery(ElasticIndex $index): array
77
    {
78
        return [
79
            'index' => $index->getIndexName(),
80
            'body'  => [
81
                'query' => [
82
                    'match' => [
83
                        'id' => sprintf('%s-%s', $this->owner->ClassName, $this->owner->ID)
84
                    ]
85
                ]
86
            ]
87
        ];
88
    }
89
90
    /**
91
     * @param ElasticCoreService $service
92
     * @param array $deleteQuery
93
     * @return Elasticsearch|Promise|bool
94
     * @throws NotFoundExceptionInterface
95
     */
96
    protected function executeQuery(ElasticCoreService $service, array $deleteQuery)
97
    {
98
        try {
99
            return $service->getClient()->deleteByQuery($deleteQuery);
100
        } catch (Exception $e) {
101
            /** @var DataObjectSearchExtension|DataObject $owner */
102
            $owner = $this->owner;
103
            // DirtyClass handling is a DataObject Search Core extension
104
            $dirty = $owner->getDirtyClass('DELETE');
105
            $ids = json_decode($dirty->IDs ?? '[]');
106
            $ids[] = $owner->ID;
107
            $dirty->IDs = json_encode($ids);
108
            $dirty->write();
109
            /** @var LoggerInterface $logger */
110
            $logger = Injector::inst()->get(LoggerInterface::class);
111
            $logger->error($e->getMessage(), $e->getTrace());
112
113
            return false;
114
        }
115
    }
116
117
    /**
118
     * Reindex after write, if it's an indexed new/updated object
119
     * @throws ClientResponseException
120
     * @throws NotFoundExceptionInterface
121
     * @throws ServerResponseException
122
     */
123
    public function onAfterWrite()
124
    {
125
        parent::onAfterWrite();
126
        /** @var DataObject|SiteTree|DataObjectElasticExtension|DataObjectSearchExtension|Versioned $owner */
127
        $owner = $this->owner;
128
        if ($this->shouldPush($owner)) {
0 ignored issues
show
Bug introduced by
It seems like $owner can also be of type Firesphere\ElasticSearch...aObjectElasticExtension; however, parameter $owner of Firesphere\ElasticSearch...Extension::shouldPush() does only seem to accept SilverStripe\ORM\DataObject, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

128
        if ($this->shouldPush(/** @scrutinizer ignore-type */ $owner)) {
Loading history...
129
            $this->pushToElastic();
130
        }
131
132
        if ($owner->hasField('ShowInSearch') &&
133
            $owner->isChanged('ShowInSearch') &&
134
            !$owner->ShowInSearch
135
        ) {
136
            $this->deletedFromElastic = $this->deleteFromElastic();
137
        }
138
    }
139
140
    /**
141
     * Check if:
142
     * - Owner has Versioned
143
     * - The versioned object is published
144
     * - The owner has the "ShowInSearch" Field
145
     * - And if so, is it set.
146
     * @param SiteTree|DataObjectSearchExtension|DataObjectElasticExtension|Versioned|DataObject $owner
147
     * @return bool
148
     */
149
    public function shouldPush(DataObject $owner): bool
150
    {
151
        $showInSearch = true;
152
        $versioned = $owner->hasExtension(Versioned::class);
153
        if ($versioned) {
154
            $versioned = $owner->isPublished();
155
        } else {
156
            // The owner is not versioned, so no publishing check
157
            $versioned = true;
158
        }
159
        $hasField = $owner->hasField('ShowInSearch');
160
        if ($hasField) {
161
            $showInSearch = $owner->ShowInSearch;
162
        }
163
164
        return ($versioned && $showInSearch);
165
    }
166
167
    /**
168
     * This is a separate method from the delete action, as it's a different route
169
     * and query components.
170
     * It can be called to add an object to the index immediately, without
171
     * requiring a write.
172
     * @return mixed
173
     * @throws ClientResponseException
174
     * @throws NotFoundExceptionInterface
175
     * @throws ServerResponseException
176
     */
177
    public function pushToElastic()
178
    {
179
        $result = false;
180
        $list = ArrayList::create();
181
        $list->push($this->owner);
182
        /** @var ElasticCoreService $service */
183
        $service = Injector::inst()->get(ElasticCoreService::class);
184
        foreach ($service->getValidIndexes() as $indexStr) {
185
            /** @var ElasticIndex $index */
186
            $index = Injector::inst()->get($indexStr);
187
            $idxConfig = ElasticIndex::config()->get($index->getIndexName());
188
            if (in_array($this->owner->ClassName, $idxConfig['Classes'])) {
189
                $result = $service->updateIndex($index, $list);
190
            }
191
        }
192
193
        return $result;
194
    }
195
196
    /**
197
     * Add ability to see what the response
198
     * from Elasticsearch was after a delete action.
199
     *
200
     * @return mixed
201
     */
202
    public function isDeletedFromElastic()
203
    {
204
        return $this->deletedFromElastic;
205
    }
206
}
207