Passed
Push — main ( 5aca0a...5528ce )
by Simon
01:31
created

DataObjectElasticExtension   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 167
Duplicated Lines 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
eloc 61
c 3
b 1
f 0
dl 0
loc 167
rs 10
wmc 20

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getDeleteQuery() 0 8 1
A onAfterDelete() 0 4 1
A executeQuery() 0 18 2
A deleteFromElastic() 0 16 3
A pushToElastic() 0 17 3
A isDeletedFromElastic() 0 3 1
A shouldPush() 0 15 4
A onAfterWrite() 0 13 5
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\ORM\ValidationException;
29
use SilverStripe\Versioned\Versioned;
30
31
/**
32
 * Class \Firesphere\ElasticSearch\Extensions\DataObjectElasticExtension
33
 *
34
 * @property DataObject|DataObjectElasticExtension $owner
35
 */
36
class DataObjectElasticExtension extends DataExtension
37
{
38
    protected $deletedFromElastic;
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 = new ElasticCoreService();
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'])) {
1 ignored issue
show
Bug Best Practice introduced by
The property ClassName does not exist on Firesphere\ElasticSearch...aObjectElasticExtension. Did you maybe forget to declare it?
Loading history...
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)
2 ignored issues
show
Bug Best Practice introduced by
The property ClassName does not exist on Firesphere\ElasticSearch...aObjectElasticExtension. Did you maybe forget to declare it?
Loading history...
Bug introduced by
The property ID does not exist on Firesphere\ElasticSearch...aObjectElasticExtension. Did you mean IDs?
Loading history...
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') &&
1 ignored issue
show
Bug introduced by
The method hasField() does not exist on Firesphere\ElasticSearch...aObjectElasticExtension. ( Ignorable by Annotation )

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

132
        if ($owner->/** @scrutinizer ignore-call */ hasField('ShowInSearch') &&

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
133
            $owner->isChanged('ShowInSearch') &&
1 ignored issue
show
Bug introduced by
The method isChanged() does not exist on Firesphere\ElasticSearch...aObjectElasticExtension. ( Ignorable by Annotation )

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

133
            $owner->/** @scrutinizer ignore-call */ 
134
                    isChanged('ShowInSearch') &&

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

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