Passed
Pull Request — master (#170)
by
unknown
08:08
created

UpdateJob::handle()   B

Complexity

Conditions 10
Paths 33

Size

Total Lines 49
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 10.2918

Importance

Changes 0
Metric Value
cc 10
eloc 28
nc 33
nop 1
dl 0
loc 49
ccs 24
cts 28
cp 0.8571
crap 10.2918
rs 7.6666
c 0
b 0
f 0

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

192
                        $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...
193 4
                        break;
194 2
                    case $result instanceof SplitterContract:
195 2
                        $pieces[$splittedBy] = $result->split($model, $value);
196 8
                        break;
197
                }
198
            }
199
        }
200 8
        $objects = [[]];
201 8
        foreach ($pieces as $splittedBy => $values) {
202 8
            $temp = [];
203 8
            foreach ($objects as $object) {
204 8
                foreach ($values as $value) {
205 8
                    $temp[] = array_merge($object, [$splittedBy => $value]);
206
                }
207
            }
208 8
            $objects = $temp;
209
        }
210
211
        return array_map(function ($object) use ($array) {
212 8
            return array_merge($array, $object);
213 8
        }, $objects);
214
    }
215
216
    /**
217
     * Determine if the given searchable uses soft deletes.
218
     *
219
     * @param  object $searchable
220
     *
221
     * @return bool
222
     */
223 1
    private function usesSoftDelete($searchable): bool
224
    {
225 1
        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 22
    private function hasToSearchableArray($searchable): bool
234
    {
235 22
        $searchableClass = get_class($searchable);
236
237 22
        if (! array_key_exists($searchableClass, $this->searchablesWithToSearchableArray)) {
238 22
            $reflectionClass = new ReflectionClass(get_class($searchable));
239
240 22
            $this->searchablesWithToSearchableArray[$searchableClass] = ends_with((string) $reflectionClass->getMethod('toSearchableArray')->getFileName(), (string) $reflectionClass->getFileName());
0 ignored issues
show
Deprecated Code introduced by
The function ends_with() has been deprecated: Str::endsWith() should be used directly instead. Will be removed in Laravel 5.9. ( Ignorable by Annotation )

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

240
            $this->searchablesWithToSearchableArray[$searchableClass] = /** @scrutinizer ignore-deprecated */ ends_with((string) $reflectionClass->getMethod('toSearchableArray')->getFileName(), (string) $reflectionClass->getFileName());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
241
        }
242
243 22
        return $this->searchablesWithToSearchableArray[$searchableClass];
244
    }
245
246
    /**
247
     * Returns the default update job transformers.
248
     *
249
     * @return array
250
     */
251 17
    public static function getTransformers(): array
252
    {
253 17
        return self::$transformers;
254
    }
255
}
256