Completed
Pull Request — master (#211)
by
unknown
07:20
created

UpdateJob::handle()   C

Complexity

Conditions 15
Paths 133

Size

Total Lines 63
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 35
CRAP Score 15.0356

Importance

Changes 4
Bugs 1 Features 0
Metric Value
cc 15
eloc 38
c 4
b 1
f 0
nc 133
nop 1
dl 0
loc 63
ccs 35
cts 37
cp 0.9459
crap 15.0356
rs 5.6416

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 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\Arr;
22
use Illuminate\Support\Str;
23
use Illuminate\Support\Collection;
24
use Algolia\AlgoliaSearch\SearchClient;
25
use Illuminate\Database\Eloquent\Model;
26
use Illuminate\Database\Eloquent\SoftDeletes;
27
use Algolia\ScoutExtended\Searchable\ModelsResolver;
28
use Algolia\ScoutExtended\Contracts\SplitterContract;
29
use Algolia\ScoutExtended\Searchable\ObjectIdEncrypter;
30
use Algolia\ScoutExtended\Transformers\ConvertDatesToTimestamps;
31
use Algolia\ScoutExtended\Transformers\ConvertNumericStringsToNumbers;
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 21
    public function __construct(Collection $searchables)
82
    {
83 21
        $this->searchables = $searchables;
84 21
    }
85
86
    /**
87
     * @param \Algolia\AlgoliaSearch\SearchClient $client
88
     *
89
     * @return void
90
     */
91 21
    public function handle(SearchClient $client): void
92
    {
93 21
        if ($this->searchables->isEmpty()) {
94
            return;
95
        }
96
97 21
        if (config('scout.soft_delete', false) && $this->usesSoftDelete($this->searchables->first())) {
98 1
            $this->searchables->each->pushSoftDeleteMetadata();
99
        }
100
101 21
        $index = $client->initIndex($this->searchables->first()->searchableAs());
102
103 21
        $objectsToSave = [];
104 21
        $searchablesToDelete = [];
105
106 21
        foreach ($this->searchables as $key => $searchable) {
107 21
            $metadata = Arr::except($searchable->scoutMetadata(), ModelsResolver::$metadata);
108
109 21
            $model = $searchable->getModel();
110 21
            $hasToSearchableRecords = method_exists($model, 'toSearchableRecords');
111
112 21
            if ($hasToSearchableRecords) {
113 1
                $objects = $model->toSearchableRecords();
114
            } else {
115 20
                $objects = [$searchable->toSearchableArray()];
116
            }
117
118 21
            if (empty($objects) || empty($objects[0])) {
119 2
                continue;
120
            }
121
122 20
            foreach ($objects as $part => $object) {
123 20
                $object = array_merge($object, $metadata);
124
125 20
                if (! $this->hasToSearchableArray($searchable)) {
126 15
                    $object = $model->transform($object);
127
                }
128
129 20
                $object['_tags'] = (array) ($object['_tags'] ?? []);
130 20
                $object['_tags'][] = ObjectIdEncrypter::encrypt($searchable);
131 20
                $objects[$part] = $object;
132
            }
133
134 20
            if ($hasToSearchableRecords || $this->shouldBeSplitted($searchable)) {
135 6
                if (!$hasToSearchableRecords) {
136 5
                    $objects = $this->splitSearchable($searchable, $objects[0]);
137
                }
138 6
                foreach ($objects as $part => $object) {
139 6
                    $object['objectID'] = ObjectIdEncrypter::encrypt($searchable, (int) $part);
140 6
                    $objectsToSave[] = $object;
141
                }
142 6
                $searchablesToDelete[] = $searchable;
143
            } else {
144 14
                $objects[0]['objectID'] = ObjectIdEncrypter::encrypt($searchable);
145 20
                $objectsToSave[] = $objects[0];
146
            }
147
        }
148
149 21
        dispatch_now(new DeleteJob(collect($searchablesToDelete)));
150
151 21
        $result = $index->saveObjects($objectsToSave);
152 21
        if (config('scout.synchronous', false)) {
153
            $result->wait();
154
        }
155 21
    }
156
157
    /**
158
     * @param  object $searchable
159
     *
160
     * @return bool
161
     */
162 19
    private function shouldBeSplitted($searchable): bool
163
    {
164 19
        $class = get_class($searchable->getModel());
165
166 19
        if (! array_key_exists($class, $this->splittables)) {
167 19
            $this->splittables[$class] = false;
168
169 19
            foreach ($searchable->toSearchableArray() as $key => $value) {
170 19
                $method = 'split'.Str::camel($key);
171 19
                $model = $searchable->getModel();
172 19
                if (method_exists($model, $method)) {
173 5
                    $this->splittables[$class] = true;
174 19
                    break;
175
                }
176
            }
177
        }
178
179 19
        return $this->splittables[$class];
180
    }
181
182
    /**
183
     * @param  object $searchable
184
     * @param  array $array
185
     *
186
     * @return array
187
     */
188 5
    private function splitSearchable($searchable, array $array): array
189
    {
190 5
        $pieces = [];
191 5
        foreach ($array as $key => $value) {
192 5
            $method = 'split'.Str::camel((string) $key);
193 5
            $model = $searchable->getModel();
194 5
            if (method_exists($model, $method)) {
195 5
                $result = $model->{$method}($value);
196 5
                $splittedBy = $key;
197 5
                $pieces[$splittedBy] = [];
198
                switch (true) {
199 5
                    case is_array($result):
200 3
                        $pieces[$splittedBy] = $result;
201 3
                        break;
202 3
                    case is_string($result):
203 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

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