Test Failed
Push — master ( 018e2d...f0524d )
by Nuno
03:35
created

UpdateJob::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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