Completed
Push — master ( 4b1496...c64940 )
by Maxime
04:19
created

EntriesRepository::toContentfulModel()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
nc 5
nop 2
dl 0
loc 20
ccs 0
cts 17
cp 0
crap 30
rs 9.2888
c 0
b 0
f 0
1
<?php
2
3
namespace Distilleries\Contentful\Repositories;
4
5
use Distilleries\Contentful\Models\Locale;
6
use Distilleries\Contentful\Models\Scopes\NotNullSlugScope;
7
use Exception;
8
use Illuminate\Support\Collection;
9
use Illuminate\Support\Facades\DB;
10
use Illuminate\Database\Eloquent\Builder;
11
use Distilleries\Contentful\Models\Base\ContentfulModel;
12
use Distilleries\Contentful\Models\Base\ContentfulMapper;
13
14
class EntriesRepository
15
{
16
    use Traits\EntryType;
17
18
    /**
19
     * Truncate all tables extending a ContentfulModel.
20
     *
21
     * @return void
22
     */
23
    public function truncateRelatedTables()
24
    {
25
        $modelPath = config('contentful.generator.model');
26
        $namespace = config('contentful.namespace.model');
27
28
        foreach (glob($modelPath . '/*.php') as $file) {
29
            $modelClass = $namespace . str_replace(
30
                    [$modelPath, '.php', '/'],
31
                    ['', '', '\\'],
32
                    $file
33
                );
34
35
            $modelInstance = new $modelClass;
36
            if ($modelInstance instanceof ContentfulModel) {
37
                $modelInstance->query()->truncate();
38
            }
39
        }
40
41
        DB::table('entry_types')->truncate();
42
        DB::table('entry_relationships')->truncate();
43
    }
44
45
    /**
46
     * Map Contentful entry payload to an Eloquent one.
47
     *
48
     * @param  array $entry
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
60
            $model = $this->upsertLocale($entry, $localeEntry);
61
62
            if (!empty($model)) {
63
                if (isset($localeEntry['relationships'])) {
64
                    $this->handleRelationships($localeEntry['locale'], $localeEntry['contentful_id'], $this->entryContentType($entry), $localeEntry['relationships']);
65
                    unset($localeEntry['relationships']);
66
                }
67
            } else if (isset($localeEntry['relationships'])) {
68
                unset($localeEntry['relationships']);
69
            }
70
        }
71
    }
72
73
    /**
74
     * Delete entry and relationships.
75
     *
76
     * @param  array $entry
77
     * @return void
78
     * @throws \Exception
79
     */
80
    public function delete(array $entry)
81
    {
82
        $this->deleteEntryType($entry['sys']['id']);
83
        $this->deleteRelationships($entry);
84
85
        $this->entryModel($entry)->query()->where('contentful_id', '=', $entry['sys']['id'])->delete();
86
    }
87
88
    // --------------------------------------------------------------------------------
89
    // --------------------------------------------------------------------------------
90
    // --------------------------------------------------------------------------------
91
92
    /**
93
     * Return entry content-type.
94
     *
95
     * @param  array $entry
96
     * @return string
97
     */
98
    private function entryContentType(array $entry): string
99
    {
100
        return $entry['sys']['contentType']['sys']['id'];
101
    }
102
103
    /**
104
     * Return entry content-type mapper class instance.
105
     *
106
     * @param  array $entry
107
     * @return \Distilleries\Contentful\Models\Base\ContentfulMapper
108
     * @throws \Exception
109
     */
110
    private function entryMapper(array $entry): ContentfulMapper
111
    {
112
        $mapperNamespace = config('contentful.namespace.mapper');
113
        $mapperClass = $mapperNamespace . '\\' . studly_case($this->entryContentType($entry)) . 'Mapper';
114
115
        if (!class_exists($mapperClass)) {
116
            throw new Exception('Unknown mapper: ' . $mapperClass);
117
        }
118
119
        return new $mapperClass;
120
    }
121
122
    /**
123
     * Return entry content-type model class instance.
124
     *
125
     * @param  array $entry
126
     * @return \Distilleries\Contentful\Models\Base\ContentfulModel
127
     * @throws \Exception
128
     */
129
    private function entryModel(array $entry): ContentfulModel
130
    {
131
        $namespace = config('contentful.namespace.model');
132
        $modelClass = $namespace . '\\' . studly_case($this->entryContentType($entry));
133
134
        if (!class_exists($modelClass)) {
135
            throw new Exception('Unknown model: ' . $modelClass);
136
        }
137
138
        return new $modelClass;
139
    }
140
141
    // --------------------------------------------------------------------------------
142
    // --------------------------------------------------------------------------------
143
    // --------------------------------------------------------------------------------
144
145
    /**
146
     * Handle mapped relationships to fill `entry_relationships` pivot table.
147
     *
148
     * @param  string $locale
149
     * @param  string $sourceId
150
     * @param  string $sourceType
151
     * @param  array $relationships
152
     * @return void
153
     * @throws \Exception
154
     */
155
    private function handleRelationships(string $locale, string $sourceId, string $sourceType, array $relationships = [])
156
    {
157
        $country = Locale::getCountry($locale);
158
        $iso = Locale::getLocale($locale);
159
160
        DB::table('entry_relationships')
161
            ->where('locale', '=', $iso)
162
            ->where('country', '=', $country)
163
            ->where('source_contentful_id', '=', $sourceId)
164
            ->delete();
165
166
        $order = 1;
167
        foreach ($relationships as $relationship) {
168
            if (!isset($relationship['id']) or !isset($relationship['type'])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
169
                throw new Exception('Relationships malformed! (' . print_r($relationship, true) . ')');
170
            }
171
172
            DB::table('entry_relationships')->insert([
173
                'locale' => $iso,
174
                'country' => $country,
175
                'source_contentful_id' => $sourceId,
176
                'source_contentful_type' => $sourceType,
177
                'related_contentful_id' => $relationship['id'],
178
                'related_contentful_type' => $relationship['type'],
179
                'order' => $order,
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 (($model instanceof NotNullSlugScope && empty($data['slug'])) || !Locale::canBeSave($data['country'],$data['locale'])) {
216
            return null;
217
        }
218
219
        if (!isset($data['payload'])) {
220
            throw new Exception('Mapper for model ' . class_basename($model) . ' must set a "payload" key');
221
        }
222
223
        $instance = $this->instanceQueryBuilder($model, $data)->first();
224
225
        if (empty($instance)) {
226
            $model->fill($data)->save();
227
        } else {
228
            $this->overridePayloadAndExtraFillables($model, $data);
229
        }
230
231
        return $this->instanceQueryBuilder($model, $data)->first();
232
    }
233
234
    /**
235
     * Override Eloquent entry with all fillable data.
236
     *
237
     * @param  \Distilleries\Contentful\Models\Base\ContentfulModel $model
238
     * @param  array $data
239
     * @return void
240
     */
241
    private function overridePayloadAndExtraFillables(ContentfulModel $model, array $data)
242
    {
243
        $fillables = $model->getFillable();
244
245
        // In this way we can delegate extra field update to
246
        // the Model itself (eg. adding slug or publishing date).
247
        $update = [];
248
        foreach ($data as $key => $value) {
249
            if (in_array($key, $fillables)) {
250
                $update[$key] = $value;
251
            }
252
        }
253
        $update['payload'] = json_encode($data['payload']);
254
255
        $this->instanceQueryBuilder($model, $data)->update($update);
256
    }
257
258
    /**
259
     * Return Eloquent QueryBuilder to target given entry.
260
     *
261
     * @param  \Distilleries\Contentful\Models\Base\ContentfulModel $model
262
     * @param  array $data
263
     * @return \Illuminate\Database\Eloquent\Builder
264
     */
265
    private function instanceQueryBuilder(ContentfulModel $model, array $data): Builder
266
    {
267
        return $model->query()
268
            ->where('contentful_id', '=', $data['contentful_id'])
269
            ->where('locale', '=', $data['locale']);
270
    }
271
}
272