Passed
Push — master ( 3d15a9...a63c66 )
by Nuno
02:17 queued 12s
created

UpdateJob   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 218
Duplicated Lines 0 %

Test Coverage

Coverage 95.12%

Importance

Changes 0
Metric Value
eloc 82
dl 0
loc 218
ccs 78
cts 82
cp 0.9512
rs 10
c 0
b 0
f 0
wmc 29

7 Methods

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

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

239
                /** @scrutinizer ignore-deprecated */ ends_with((string) $reflectionClass->getMethod('toSearchableArray')->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...
240 18
                    (string) $reflectionClass->getFileName());
241
        }
242
243 18
        return $this->searchablesWithToSearchableArray[$searchableClass];
244
    }
245
246
    /**
247
     * Returns the default update job transformers.
248
     *
249
     * @return array
250
     */
251 13
    public static function getTransformers(): array
252
    {
253 13
        return self::$transformers;
254
    }
255
}
256