EntriesRepository::upsertLocale()   B
last analyzed

Complexity

Conditions 7
Paths 5

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 7
eloc 14
c 4
b 0
f 0
nc 5
nop 2
dl 0
loc 26
ccs 0
cts 20
cp 0
crap 56
rs 8.8333
1
<?php
2
3
namespace Distilleries\Contentful\Repositories;
4
5
use Exception;
6
use Illuminate\Support\Str;
7
use Illuminate\Support\Collection;
8
use Illuminate\Support\Facades\DB;
9
use Illuminate\Database\Eloquent\Builder;
10
use Distilleries\Contentful\Models\Locale;
11
use Distilleries\Contentful\Helpers\NamespaceResolver;
12
use Distilleries\Contentful\Models\Base\ContentfulModel;
13
use Distilleries\Contentful\Models\Base\ContentfulMapper;
14
15
class EntriesRepository
16
{
17
    use Traits\EntryType;
18
19
    /**
20
     * Truncate all tables extending a ContentfulModel.
21
     *
22
     * @return void
23
     */
24
    public function truncateRelatedTables()
25
    {
26
        $modelPath = config('contentful.generator.model');
27
28
        foreach (glob($modelPath . '/*.php') as $file) {
29
            $modelInstance = NamespaceResolver::model(str_replace(
30
                [$modelPath, '.php', '/'],
31
                ['', '', '\\'],
32
                $file
33
            ));
34
35
            if (! empty($modelInstance) && $modelInstance instanceof ContentfulModel) {
36
                $modelInstance->query()->truncate();
37
            }
38
        }
39
40
        DB::table('entry_types')->truncate();
41
        DB::table('entry_relationships')->truncate();
42
    }
43
44
    /**
45
     * Map Contentful entry payload to an Eloquent one.
46
     *
47
     * @param  array  $entry
48
     * @param \Illuminate\Support\Collection  $locales
49
     * @return void
50
     * @throws \Exception
51
     */
52
    public function toContentfulModel(array $entry, Collection $locales)
53
    {
54
        $this->upsertEntryType($entry, $this->entryContentType($entry));
55
        $this->deleteRelationships($entry);
56
57
        $localeEntries = $this->entryMapper($entry)->toLocaleEntries($entry, $locales);
58
        foreach ($localeEntries as $localeEntry) {
59
            $model = $this->upsertLocale($entry, $localeEntry);
60
61
            if (! empty($model)) {
62
                if (isset($localeEntry['relationships'])) {
63
                    $this->handleRelationships($localeEntry['locale'], $localeEntry['country'], $localeEntry['contentful_id'], $this->entryContentType($entry), $localeEntry['relationships']);
64
                    unset($localeEntry['relationships']);
65
                }
66
            } else {
67
                if (isset($localeEntry['relationships'])) {
68
                    unset($localeEntry['relationships']);
69
                }
70
            }
71
        }
72
    }
73
74
    /**
75
     * Delete entry and relationships.
76
     *
77
     * @param  array  $entry
78
     * @return void
79
     * @throws \Exception
80
     */
81
    public function delete(array $entry)
82
    {
83
        $this->deleteEntryType($entry['sys']['id']);
84
        $this->deleteRelationships($entry);
85
86
        $this->entryModel($entry)->query()->where('contentful_id', '=', $entry['sys']['id'])->delete();
87
    }
88
89
    // --------------------------------------------------------------------------------
90
    // --------------------------------------------------------------------------------
91
    // --------------------------------------------------------------------------------
92
93
    /**
94
     * Return entry content-type.
95
     *
96
     * @param  array  $entry
97
     * @return string
98
     */
99
    private function entryContentType(array $entry): string
100
    {
101
        return $entry['sys']['contentType']['sys']['id'];
102
    }
103
104
    /**
105
     * Return entry content-type mapper class instance.
106
     *
107
     * @param  array  $entry
108
     * @return \Distilleries\Contentful\Models\Base\ContentfulMapper
109
     * @throws \Exception
110
     */
111
    private function entryMapper(array $entry): ContentfulMapper
112
    {
113
        $class = Str::studly($this->entryContentType($entry)) . 'Mapper';
114
        $mapperClass = NamespaceResolver::mapper($class);
115
116
        if (empty($mapperClass) && ! ($mapperClass instanceof ContentfulMapper)) {
117
            throw new Exception('Unknown mapper: ' . $class);
118
        }
119
120
        return new $mapperClass;
121
    }
122
123
    /**
124
     * Return entry content-type model class instance.
125
     *
126
     * @param  array  $entry
127
     * @return \Distilleries\Contentful\Models\Base\ContentfulModel
128
     * @throws \Exception
129
     */
130
    private function entryModel(array $entry): ContentfulModel
131
    {
132
        $model = Str::studly($this->entryContentType($entry));
133
        $modelClass = NamespaceResolver::modelClass($model);
134
135
        if (empty($modelClass)) {
136
            throw new Exception('Unknown model: ' . $model);
137
        }
138
139
        return new $modelClass;
140
    }
141
142
    // --------------------------------------------------------------------------------
143
    // --------------------------------------------------------------------------------
144
    // --------------------------------------------------------------------------------
145
146
    /**
147
     * Handle mapped relationships to fill `entry_relationships` pivot table.
148
     *
149
     * @param  string  $locale
150
     * @param  string  $country
151
     * @param  string  $sourceId
152
     * @param  string  $sourceType
153
     * @param  array  $relationships
154
     * @return void
155
     * @throws \Exception
156
     */
157
    private function handleRelationships(string $locale, string $country, string $sourceId, string $sourceType, array $relationships = [])
158
    {
159
        DB::table('entry_relationships')
160
            ->where('locale', '=', $locale)
161
            ->where('country', '=', $country)
162
            ->where('source_contentful_id', '=', $sourceId)
163
            ->delete();
164
165
        $order = 1;
166
        foreach ($relationships as $relationship) {
167
            if (! isset($relationship['id']) || ! isset($relationship['type'])) {
168
                throw new Exception('Relationships malformed! (' . print_r($relationship, true) . ')');
169
            }
170
171
            DB::table('entry_relationships')->insert([
172
                'locale' => $locale,
173
                'country' => $country,
174
                'source_contentful_id' => $sourceId,
175
                'source_contentful_type' => $sourceType,
176
                'related_contentful_id' => $relationship['id'],
177
                'related_contentful_type' => $relationship['type'],
178
                'order' => $order,
179
                'relation' => isset($relationship['field']) ? $relationship['field'] : null,
180
            ]);
181
182
            $order++;
183
        }
184
    }
185
186
    /**
187
     * Delete entry relationships for given Contentful entry.
188
     *
189
     * @param  array  $entry
190
     * @return void
191
     */
192
    private function deleteRelationships(array $entry)
193
    {
194
        DB::table('entry_relationships')
195
            ->where('source_contentful_id', '=', $entry['sys']['id'])
196
            ->where('source_contentful_type', '=', $this->entryContentType($entry))
197
            ->delete();
198
    }
199
200
    // --------------------------------------------------------------------------------
201
    // --------------------------------------------------------------------------------
202
    // --------------------------------------------------------------------------------
203
204
    /**
205
     * Return inserted / updated model instance for given parameters.
206
     *
207
     * @param  array  $entry
208
     * @param  array  $data
209
     * @return \Distilleries\Contentful\Models\Base\ContentfulModel|null
210
     * @throws \Exception
211
     */
212
    private function upsertLocale(array $entry, array $data): ?ContentfulModel
213
    {
214
        $model = $this->entryModel($entry);
215
        if ((method_exists($model, 'bootNotNullSlug') && empty($data['slug'])) || ! Locale::canBeSave($data['country'], $data['locale'])) {
216
            // Remove instance if slug is empty
217
            $instance = $this->instanceQueryBuilder($model, $data)->first();
218
            if (! empty($instance)) {
219
                $instance->delete();
220
            }
221
222
            return null;
223
        }
224
225
        if (! isset($data['payload'])) {
226
            throw new Exception('Mapper for model ' . class_basename($model) . ' must set a "payload" key');
227
        }
228
229
        $instance = $this->instanceQueryBuilder($model, $data)->first();
230
231
        if (empty($instance)) {
232
            $model->fill($data)->save();
233
        } else {
234
            $this->overridePayloadAndExtraFillables($model, $data);
235
        }
236
237
        return $this->instanceQueryBuilder($model, $data)->first();
238
    }
239
    
240
    /**
241
     * Override Eloquent entry with all fillable data.
242
     *
243
     * @param  \Distilleries\Contentful\Models\Base\ContentfulModel  $model
244
     * @param  array  $data
245
     * @return void
246
     */
247
    private function overridePayloadAndExtraFillables(ContentfulModel $model, array $data)
248
    {
249
        $fillables = $model->getFillable();
250
        $casts = $model->getCasts();
251
252
        // In this way we can delegate extra field update to
253
        // the Model itself (eg. adding slug or publishing date).
254
        $update = [];
255
        foreach ($data as $key => $value) {
256
            if (in_array($key, $fillables)) {
257
                $update[$key] = $this->isCastedAsArray($casts, $key) ? json_encode($value) : $value;
258
            }
259
        }
260
261
        $this->instanceQueryBuilder($model, $data)->update($update);
262
    }
263
264
    /**
265
     * Return if key is casted as array in model attributes casts.
266
     *
267
     * @param  array  $casts
268
     * @param  string  $key
269
     * @return bool
270
     */
271
    private function isCastedAsArray(array $casts, string $key): bool
272
    {
273
        if (isset($casts[$key]) && (Str::lower($casts[$key]) === 'array')) {
274
            return true;
275
        }
276
277
        return false;
278
    }
279
280
    /**
281
     * Return Eloquent QueryBuilder to target given entry.
282
     *
283
     * @param  \Distilleries\Contentful\Models\Base\ContentfulModel  $model
284
     * @param  array  $data
285
     * @return \Illuminate\Database\Eloquent\Builder
286
     */
287
    private function instanceQueryBuilder(ContentfulModel $model, array $data): Builder
288
    {
289
        return $model
290
            ->withoutGlobalScopes()
291
            ->where('contentful_id', '=', $data['contentful_id'])
292
            ->where('locale', '=', $data['locale'])
293
            ->where('country', '=', $data['country']);
294
    }
295
}
296