ImportVocabulary   A
last analyzed

Complexity

Total Complexity 38

Size/Duplication

Total Lines 272
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 38
eloc 147
dl 0
loc 272
rs 9.36
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A isElementSet() 0 3 1
A setLanguage() 0 4 2
A failed() 0 3 1
A makeErrorLogEntry() 0 3 1
A setResults() 0 3 1
A getStatements() 0 9 2
A UpdatePrefLabelId() 0 10 2
A makeStatement() 0 19 2
A addUpdateStatement() 0 8 4
A makeResource() 0 12 2
A setFormproperties() 0 18 3
C handle() 0 112 16
1
<?php
2
3
namespace App\Jobs;
4
5
use App\Events\ImportFailed;
6
use App\Events\ImportFinished;
7
use App\Models\Concept;
8
use App\Models\ConceptAttribute;
9
use App\Models\Element;
10
use App\Models\ElementAttribute;
11
use App\Models\Import;
12
use Exception;
13
use Illuminate\Bus\Queueable;
14
use Illuminate\Contracts\Queue\ShouldQueue;
15
use Illuminate\Database\Eloquent\Collection;
16
use Illuminate\Database\Eloquent\Model;
17
use Illuminate\Foundation\Bus\Dispatchable;
18
use Illuminate\Queue\InteractsWithQueue;
19
use Illuminate\Queue\SerializesModels;
20
use Illuminate\Support\Facades\DB;
21
use MailThief\Message;
22
23
class ImportVocabulary implements ShouldQueue
24
{
25
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
0 ignored issues
show
introduced by
The trait Illuminate\Queue\SerializesModels requires some properties which are not provided by App\Jobs\ImportVocabulary: $id, $class
Loading history...
26
    /** @var Import */
27
    private $import;
28
    /** @var Model */
29
    private $resource;
30
    /** @var Collection */
31
    private $formResourceProps;
32
    /** @var Collection */
33
    private $formLanguageProps;
34
    /** @var string */
35
    private $resourceLang;
36
    /** @var array */
37
    private $updatedStatements;
38
    /** @var array */
39
    private $results;
40
    /** @var int */
41
    private $userId;
42
43
    /**
44
     * Create a new job instance.
45
     *
46
     * @param int $importId
47
     */
48
    public function __construct(int $importId)
49
    {
50
        $this->import = Import::find($importId);
51
    }
52
53
    /**
54
     * Execute the job.
55
     *   step through the changeset and change/add the statements
56
     *   each row of the changeset is a resource
57
     *   if the row has an id then we're updating it
58
     *   if there's a statement id for a column, we're updating it
59
     *   if we delete a row, we have to delete all of the statements
60
     *   changing the statements should trigger an addition to the history table for each one
61
     *   update each resource base model with data from the statements.
62
     *
63
     * @return void
64
     * @throws \Throwable
65
     * @throws \Exception
66
     */
67
    public function handle()
68
    {
69
        $timer           = new \DateTime();
70
        $this->setFormproperties();
71
        $this->setLanguage();
72
        $this->userId    = $this->import->user_id;
73
        $vocabId         = $this->isElementSet() ? $this->import->schema_id : $this->import->vocabulary_id;
74
        $changeset       = $this->import->instructions;
75
        $total_processed = 0;
76
        $added           = 0;
77
        $updated         = 0;
78
        $deleted         = 0;
79
        //each item in the main array is a row. Each item in the statements array is a statement
80
        foreach ($changeset['update'] as $reg_id => $row) {
81
            $this->updatedStatements = [];
82
            //start a transaction
83
            DB::transaction(function () use ($reg_id, $row, &$updated) {
84
                $statements = $this->getStatements($reg_id);
85
                $dirty = false;
86
                foreach ($row as $statement) {
87
                    $old = $statements->find($statement['statement_id']);
88
                    if ($old) {
89
                        if ($statement['new value']) {
90
                            $old->update([
91
                                'object'         => $statement['new value'],
92
                                'last_import_id' => $this->import->id,
93
                                'is_generated'   => false,
94
                            ]);
95
                            $this->addUpdateStatement($statement);
96
                            $dirty = true;
97
                        } else {
98
                            $old->delete();
99
                            $this->addUpdateStatement($statement);
100
                            $dirty = true;
101
                        }
102
                    } else {
103
                        //make a new one
104
                        $existingStatement = $statements->filter(function ($item) use ($statement) {
105
                            /* @var Model $item */
106
                            return $item->profile_property_id == $statement['property_id'] &&
107
                                $item->object == $statement['new value'] &&
108
                                $item->getOriginal('language') === $statement['language'];
109
                        });
110
                        if ($existingStatement->count() === 0) {
111
                            $newStatement = $this->makeStatement($statement);
112
                            $this->resource->statements()->save($newStatement);
0 ignored issues
show
Bug introduced by
The method statements() does not exist on null. ( Ignorable by Annotation )

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

112
                            $this->resource->/** @scrutinizer ignore-call */ 
113
                                             statements()->save($newStatement);

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...
113
                            $this->addUpdateStatement($statement);
114
                            $dirty = true;
115
                        }
116
                    }
117
                }
118
                if (\count($this->updatedStatements)) {
119
                    try {
120
                        $this->resource->updated_user_id = $this->userId;
121
                        $this->resource->updateFromStatements($this->updatedStatements);
122
                    } catch (Exception $e) {
123
                        //log the error
124
                        $this->makeErrorLogEntry($row['*uri']['new value'], $e->getMessage());
125
                        //cancel the transaction
126
                        return false;
127
                    }
128
                }
129
                if ($dirty) {
130
                    $updated++;
131
                }
132
            });
133
            $total_processed++;
134
        }
135
        foreach ($changeset['add'] as $row) {
136
            DB::transaction(function () use ($row, $vocabId, &$added) {
137
                $allStatements = [];
138
                foreach ($row as $statement) {
139
                    $this->addUpdateStatement($statement);
140
                    $allStatements[] = $this->makeStatement($statement);
141
                }
142
                try {
143
                    //this will make a resource with insufficient values...
144
                    $resource = $this->makeResource($vocabId);
145
                    //this will update the resource from the statements and save it...
146
                    $resource->updateFromStatements($this->updatedStatements);
147
                    //this will add the statements...
148
                    $resource->statements()->saveMany($allStatements);
149
                    $this->UpdatePrefLabelId($resource);
150
                } catch (Exception $e) {
151
                    //log the error
152
                    $this->makeErrorLogEntry($row['*uri']['new value'], $e->getMessage());
153
                    //cancel the transaction
154
                    return false;
155
                }
156
157
                $added++;
158
            });
159
            $total_processed++;
160
        }
161
        foreach ($changeset['delete'] as $row) {
162
            //TODO implement this...
163
            //delete the resource
164
            //cascade delete the statements, which should cascade delete the reciprocals
165
            $total_processed++;
166
            $deleted++;
167
        }
168
        $this->setResults('timer', $timer->diff(new \DateTime())->format('%h hours; %i minutes; %s seconds'));
169
        $this->import->results = $this->results;
170
        //$this->import->batch->increment('handled_count');
171
        $this->import->total_processed_count = $total_processed;
172
        $this->import->added_count           = $added;
173
        $this->import->updated_count         = $updated;
174
        $this->import->deleted_count         = $deleted;
175
        $this->import->imported_at           = new \DateTime();
176
        $this->import->save();
177
178
        event(new ImportFinished($this->import));
179
    }
180
181
    public function failed(Exception $exception)
182
    {
183
        event(new ImportFailed($this->import, $exception));
184
    }
185
186
    private function addUpdateStatement($statement): void
187
    {
188
        if ($this->formLanguageProps->contains($statement['property_id']) &&
189
            $this->resourceLang === $statement['language']) {
190
            $this->updatedStatements[$statement['property_id']] = $statement['new value'];
191
        }
192
        if ($this->formResourceProps->contains($statement['property_id'])) {
193
            $this->updatedStatements[$statement['property_id']] = $statement['new value'];
194
        }
195
    }
196
197
    private function getStatements(int $reg_id): ?Collection
198
    {
199
        if ($this->isElementSet()) {
200
            $this->resource = Element::find($reg_id)->load('statements');
201
        } else {
202
            $this->resource = Concept::find($reg_id)->load('statements');
203
        }
204
205
        return $this->resource->statements;
206
    }
207
208
    private function setFormproperties(): void
209
    {
210
        $this->formResourceProps =
211
            $this->isElementSet() ? $this->import->elementset->profile->profile_properties()->where([
0 ignored issues
show
Bug introduced by
The method profile_properties() does not exist on null. ( Ignorable by Annotation )

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

211
            $this->isElementSet() ? $this->import->elementset->profile->/** @scrutinizer ignore-call */ profile_properties()->where([

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...
212
                ['is_in_form', true],
213
                ['has_language', false],
214
            ])->get(['id'])->pluck('id') : $this->import->vocabulary->profile->profile_properties()->where([
0 ignored issues
show
Bug introduced by
The method profile_properties() does not exist on null. ( Ignorable by Annotation )

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

214
            ])->get(['id'])->pluck('id') : $this->import->vocabulary->profile->/** @scrutinizer ignore-call */ profile_properties()->where([

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...
215
                ['is_in_form', true],
216
                ['has_language', false],
217
            ])->get(['id'])->pluck('id');
218
        $this->formLanguageProps =
219
            $this->isElementSet() ? $this->import->elementset->profile->profile_properties()->where([
220
                ['is_in_form', true],
221
                ['has_language', true],
222
            ])->get(['id'])->pluck('id') : $this->import->vocabulary->profile->profile_properties()->where([
223
                ['is_in_form', true],
224
                ['has_language', true],
225
            ])->get(['id'])->pluck('id');
226
    }
227
228
    private function setLanguage(): void
229
    {
230
        $this->resourceLang =
231
            $this->isElementSet() ? $this->import->elementset->getOriginal('language') : $this->import->vocabulary->getOriginal('language');
0 ignored issues
show
Bug introduced by
The method getOriginal() does not exist on null. ( Ignorable by Annotation )

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

231
            $this->isElementSet() ? $this->import->elementset->getOriginal('language') : $this->import->vocabulary->/** @scrutinizer ignore-call */ getOriginal('language');

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...
Documentation Bug introduced by
It seems like $this->isElementSet() ? ...getOriginal('language') can also be of type array. However, the property $resourceLang is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
232
    }
233
234
    private function makeResource(int $vocabId): ?Model
235
    {
236
        if ($this->isElementSet()) {
237
            $this->resource = Element::make(['schema_id' => $vocabId]);
238
        } else {
239
            $this->resource = Concept::make(['vocabulary_id' => $vocabId]);
240
        }
241
242
        $this->resource->created_user_id = $this->userId;
243
        $this->resource->updated_user_id = $this->userId;
244
245
        return $this->resource;
246
    }
247
248
    private function makeStatement(array $statement)
249
    {
250
        $values = [
251
            'object'              => $statement['new value'],
252
            'language'            => $statement['language'],
253
            'profile_property_id' => $statement['property_id'],
254
            'last_import_id'      => $this->import->id,
255
            'created_user_id'     => $this->userId,
256
            'updated_user_id'     => $this->userId,
257
        ];
258
        if ($this->isElementSet()) {
259
            $values['status_id'] = $this->import->elementset->status_id;
260
261
            return ElementAttribute::make($values);
262
        }
263
264
        $values['status_id'] = $this->import->vocabulary->status_id;
265
266
        return ConceptAttribute::make($values);
267
    }
268
269
    private function UpdatePrefLabelId(Model $resource)
270
    {
271
        if (! $this->isElementSet()) {
272
            $id = $resource->statements()
273
                ->where([
274
                    ['profile_property_id', 45],
275
                    ['language', $this->resourceLang],
276
                ])->pluck('id');
277
            $resource->pref_label_id = $id[0] ?? null;
278
            $resource->save();
279
        }
280
    }
281
282
    private function isElementSet(): bool
283
    {
284
        return (bool) $this->import->schema_id;
285
    }
286
287
    private function setResults($element, $value)
288
    {
289
        $this->results[$element][] = $value;
290
    }
291
292
    private function makeErrorLogEntry($rowId, $message)
293
    {
294
        $this->setResults('errors', ['row'=> $rowId, 'message' =>$message]);
295
    }
296
}
297