Completed
Push — fix/queing-aggregators ( 045327...615861 )
by Nuno
57:33 queued 52:55
created

UpdateJob::usesSoftDelete()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 1
nc 2
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * This file is part of Scout Extended.
7
 *
8
 * (c) Algolia Team <[email protected]>
9
 *
10
 *  For the full copyright and license information, please view the LICENSE
11
 *  file that was distributed with this source code.
12
 */
13
14
namespace Algolia\ScoutExtended\Jobs;
15
16
use ReflectionClass;
17
use function in_array;
18
use function is_array;
19
use function get_class;
20
use function is_string;
21
use Illuminate\Support\Str;
22
use Illuminate\Support\Collection;
23
use Algolia\AlgoliaSearch\SearchClient;
24
use Illuminate\Database\Eloquent\Model;
25
use Illuminate\Database\Eloquent\SoftDeletes;
26
use Algolia\ScoutExtended\Contracts\SplitterContract;
27
use Algolia\ScoutExtended\Searchable\ObjectIdEncrypter;
28
use Algolia\ScoutExtended\Transformers\ConvertDatesToTimestamps;
29
use Algolia\ScoutExtended\Transformers\ConvertNumericStringsToNumbers;
30
31
/**
32
 * @internal
33
 */
34
final class UpdateJob
35
{
36
    /**
37
     * Contains a list of splittables searchables.
38
     *
39
     * Example: [
40
     *      '\App\Thread' => true,
41
     *      '\App\User' => false,
42
     * ];
43
     *
44
     * @var array
45
     */
46
    private $splittables = [];
47
48
    /**
49
     * @var \Illuminate\Support\Collection
50
     */
51
    private $searchables;
52
53
    /**
54
     * Holds the searchables with a declared
55
     * toSearchableArray method.
56
     *
57
     * @var array
58
     */
59
    private $searchablesWithToSearchableArray = [];
60
61
    /**
62
     * Holds a list of transformers to apply by
63
     * default.
64
     *
65
     * @var array
66
     */
67
    private static $transformers = [
68
        ConvertNumericStringsToNumbers::class,
69
        ConvertDatesToTimestamps::class,
70
    ];
71
72
    /**
73
     * UpdateJob constructor.
74
     *
75
     * @param \Illuminate\Support\Collection $searchables
76
     *
77
     * @return void
78
     */
79 15
    public function __construct(Collection $searchables)
80
    {
81 15
        $this->searchables = $searchables;
82 15
    }
83
84
    /**
85
     * @param \Algolia\AlgoliaSearch\SearchClient $client
86
     *
87
     * @return void
88
     */
89 15
    public function handle(SearchClient $client): void
90
    {
91 15
        if ($this->searchables->isEmpty()) {
92
            return;
93
        }
94
95 15
        if (config('scout.soft_delete', false) && $this->usesSoftDelete($this->searchables->first())) {
96
            $this->searchables->each->pushSoftDeleteMetadata();
97
        }
98
99 15
        $index = $client->initIndex($this->searchables->first()->searchableAs());
100
101 15
        $objectsToSave = [];
102 15
        $searchablesToDelete = [];
103
104 15
        foreach ($this->searchables as $key => $searchable) {
105 15
            if (empty($array = array_merge($searchable->toSearchableArray(), $searchable->scoutMetadata()))) {
106
                continue;
107
            }
108
109 15
            if (! $this->hasToSearchableArray($searchable)) {
110 12
                $array = $searchable->getModel()->transform($array);
111
            }
112
113 15
            $array['_tags'] = (array) ($array['_tags'] ?? []);
114
115 15
            $array['_tags'][] = ObjectIdEncrypter::encrypt($searchable);
116
117 15
            if ($this->shouldBeSplitted($searchable)) {
118 5
                $objects = $this->splitSearchable($searchable, $array);
119
120 5
                foreach ($objects as $part => $object) {
121 5
                    $object['objectID'] = ObjectIdEncrypter::encrypt($searchable, (int) $part);
122 5
                    $objectsToSave[] = $object;
123
                }
124 5
                $searchablesToDelete[] = $searchable;
125
            } else {
126 10
                $array['objectID'] = ObjectIdEncrypter::encrypt($searchable);
127 15
                $objectsToSave[] = $array;
128
            }
129
        }
130
131 15
        dispatch_now(new DeleteJob(collect($searchablesToDelete)));
132
133 15
        $result = $index->saveObjects($objectsToSave);
134 15
        if (config('scout.synchronous', false)) {
135
            $result->wait();
136
        }
137 15
    }
138
139
    /**
140
     * @param  object $searchable
141
     *
142
     * @return bool
143
     */
144 15
    private function shouldBeSplitted($searchable): bool
145
    {
146 15
        $class = get_class($searchable->getModel());
147
148 15
        if (! array_key_exists($class, $this->splittables)) {
149 15
            $this->splittables[$class] = false;
150
151 15
            foreach ($searchable->toSearchableArray() as $key => $value) {
152 15
                $method = 'split'.Str::camel($key);
153 15
                $model = $searchable->getModel();
154 15
                if (method_exists($model, $method)) {
155 5
                    $this->splittables[$class] = true;
156 15
                    break;
157
                }
158
            }
159
        }
160
161 15
        return $this->splittables[$class];
162
    }
163
164
    /**
165
     * @param  object $searchable
166
     * @param  array $array
167
     *
168
     * @return array
169
     */
170 5
    private function splitSearchable($searchable, array $array): array
171
    {
172 5
        $pieces = [];
173 5
        foreach ($array as $key => $value) {
174 5
            $method = 'split'.Str::camel((string) $key);
175 5
            $model = $searchable->getModel();
176 5
            if (method_exists($model, $method)) {
177 5
                $result = $model->{$method}($value);
178 5
                $splittedBy = $key;
179 5
                $pieces[$splittedBy] = [];
180
                switch (true) {
181 5
                    case is_array($result):
182 3
                        $pieces[$splittedBy] = $result;
183 3
                        break;
184 3
                    case is_string($result):
185 1
                        $pieces[$splittedBy] = app($result)->split($model, $value);
0 ignored issues
show
Bug introduced by
The method split() does not exist on Illuminate\Foundation\Application. ( Ignorable by Annotation )

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

185
                        $pieces[$splittedBy] = app($result)->/** @scrutinizer ignore-call */ split($model, $value);

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...
186 1
                        break;
187 2
                    case $result instanceof SplitterContract:
188 2
                        $pieces[$splittedBy] = $result->split($model, $value);
189 5
                        break;
190
                }
191
            }
192
        }
193
194 5
        $objects = [[]];
195 5
        foreach ($pieces as $splittedBy => $values) {
196 5
            $temp = [];
197 5
            foreach ($objects as $object) {
198 5
                foreach ($values as $value) {
199 5
                    $temp[] = array_merge($object, [$splittedBy => $value]);
200
                }
201
            }
202 5
            $objects = $temp;
203
        }
204
205
        return array_map(function ($object) use ($array) {
206 5
            return array_merge($array, $object);
207 5
        }, $objects);
208
    }
209
210
    /**
211
     * Determine if the given searchable uses soft deletes.
212
     *
213
     * @param  object $searchable
214
     *
215
     * @return bool
216
     */
217 1
    private function usesSoftDelete($searchable): bool
218
    {
219 1
        return $searchable instanceof Model && in_array(SoftDeletes::class, class_uses_recursive($searchable), true);
220
    }
221
222
    /**
223
     * @param  object $searchable
224
     *
225
     * @return bool
226
     */
227 15
    private function hasToSearchableArray($searchable): bool
228
    {
229 15
        $searchableClass = get_class($searchable);
230
231 15
        if (! array_key_exists($searchableClass, $this->searchablesWithToSearchableArray)) {
232 15
            $reflectionClass = new ReflectionClass(get_class($searchable));
233
234 15
            $this->searchablesWithToSearchableArray[$searchableClass] =
235 15
                ends_with((string) $reflectionClass->getMethod('toSearchableArray')->getFileName(),
236 15
                    (string) $reflectionClass->getFileName());
237
        }
238
239 15
        return $this->searchablesWithToSearchableArray[$searchableClass];
240
    }
241
242
    /**
243
     * Returns the default update job transformers.
244
     *
245
     * @return array
246
     */
247 12
    public static function getTransformers(): array
248
    {
249 12
        return self::$transformers;
250
    }
251
}
252