UpdateJob::handle()   B
last analyzed

Complexity

Conditions 10
Paths 33

Size

Total Lines 51
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 10.0328

Importance

Changes 4
Bugs 1 Features 0
Metric Value
cc 10
eloc 29
c 4
b 1
f 0
nc 33
nop 1
dl 0
loc 51
ccs 27
cts 29
cp 0.931
crap 10.0328
rs 7.6666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

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