Completed
Pull Request — master (#24)
by Anton
02:57 queued 39s
created

UpgradeV5ToV6   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 310
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 37
eloc 162
dl 0
loc 310
rs 9.44
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A collectLikeableTypes() 0 12 1
A collectLikeTypes() 0 12 1
A populateReactionTypes() 0 24 4
A filesystemCleanup() 0 6 1
B populateReacters() 0 40 7
A deleteMigrationFiles() 0 6 3
A dbMigrate() 0 3 1
A getUserClass() 0 7 1
A dbCleanup() 0 15 1
A reactionTypeNameFromLikeTypeName() 0 4 1
B populateReactions() 0 68 6
A handle() 0 9 1
B populateReactants() 0 45 8
A collectLikerTypes() 0 4 1
1
<?php
2
3
/*
4
 * This file is part of Laravel Love.
5
 *
6
 * (c) Anton Komarev <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Cog\Laravel\Love\Console\Commands;
15
16
use Cog\Contracts\Love\Reactable\Models\Reactable as ReactableContract;
17
use Cog\Contracts\Love\Reacterable\Models\Reacterable as ReacterableContract;
18
use Cog\Laravel\Love\Reaction\Models\Reaction;
19
use Cog\Laravel\Love\ReactionType\Models\ReactionType;
20
use Illuminate\Console\Command;
21
use Illuminate\Database\Eloquent\Relations\Relation;
22
use Illuminate\Support\Facades\DB;
23
use Illuminate\Support\Facades\File;
24
25
final class UpgradeV5ToV6 extends Command
26
{
27
    /**
28
     * The name and signature of the console command.
29
     *
30
     * @var string
31
     */
32
    protected $signature = 'love:upgrade-v5-to-v6';
33
34
    /**
35
     * The console command description.
36
     *
37
     * @var string
38
     */
39
    protected $description = 'Upgrade love package from v5 to v6';
40
41
    /**
42
     * Execute the console command.
43
     *
44
     * @return void
45
     */
46
    public function handle(): void
47
    {
48
        $this->dbMigrate();
49
        $this->populateReactionTypes();
50
        $this->populateReacters();
51
        $this->populateReactants();
52
        $this->populateReactions();
53
        $this->dbCleanup();
54
        $this->filesystemCleanup();
55
    }
56
57
    private function dbMigrate(): void
58
    {
59
        $this->call('migrate');
60
    }
61
62
    private function dbCleanup(): void
63
    {
64
        $this->info('Deleting old database tables');
65
        DB::statement('
66
            DROP TABLE `love_like_counters`;
67
        ');
68
        DB::statement('
69
            DROP TABLE `love_likes`;
70
        ');
71
        DB::statement('
72
            DELETE FROM `migrations`
73
            WHERE `migration` = "2016_09_02_153301_create_love_likes_table"
74
            LIMIT 1;
75
        ');
76
        DB::statement('
77
            DELETE FROM `migrations`
78
            WHERE `migration` = "2016_09_02_163301_create_love_like_counters_table"
79
            LIMIT 1;
80
        ');
81
    }
82
83
    private function filesystemCleanup(): void
84
    {
85
        $this->info('Deleting old database migration files');
86
        $this->deleteMigrationFiles([
87
            '2016_09_02_153301_create_love_likes_table.php',
88
            '2016_09_02_163301_create_love_like_counters_table.php',
89
        ]);
90
    }
91
92
    private function populateReactionTypes(): void
93
    {
94
        $this->info('Populating Reaction Types');
95
        $names = $this->collectLikeTypes();
96
        $weights = [
97
            'Like' => 1,
98
            'Dislike' => -1,
99
        ];
100
101
        foreach ($names as $name) {
102
            $name = $this->reactionTypeNameFromLikeTypeName($name);
103
104
            if (!isset($weights[$name])) {
105
                $this->warn("Reaction weight for type `{$name}` not found.");
106
                continue;
107
            }
108
109
            if (ReactionType::query()->where('name', $name)->exists()) {
110
                continue;
111
            }
112
113
            ReactionType::query()->create([
114
                'name' => $name,
115
                'weight' => $weights[$name],
116
            ]);
117
        }
118
    }
119
120
    private function populateReacters(): void
121
    {
122
        $this->info('Populating Reacters');
123
        $classes = $this->collectLikerTypes();
124
125
        $reacterableClasses = [];
126
        foreach ($classes as $class) {
127
            if (!class_exists($class)) {
128
                $this->warn("Class `{$class}` is not found.");
129
                continue;
130
            }
131
132
            if (!in_array(ReacterableContract::class, class_implements($class))) {
133
                $this->warn("Class `{$class}` need to implement Reacterable contract.");
134
                continue;
135
            }
136
137
            $reacterableClasses[] = $class;
138
        }
139
140
        foreach ($reacterableClasses as $class) {
141
            /** @var \Illuminate\Database\Eloquent\Model[] $reacterables */
142
            $reacterables = $class::query()->get();
143
            $progress = $this->output->createProgressBar($reacterables->count());
144
            foreach ($reacterables as $reacterable) {
145
                if ($reacterable->getAttributeValue('love_reacter_id') > 0) {
146
                    $progress->advance();
147
                    continue;
148
                }
149
150
                $reacter = $reacterable->loveReacter()->create([
151
                    'type' => $reacterable->getMorphClass(),
152
                ]);
153
                $reacterable->setAttribute('love_reacter_id', $reacter->getId());
154
                $reacterable->save();
155
                $progress->advance();
156
            }
157
            $progress->finish();
158
        }
159
        $this->info('');
160
    }
161
162
    private function populateReactants(): void
163
    {
164
        $this->info('Populating Reactants');
165
        $classes = $this->collectLikeableTypes();
166
167
        $reactableClasses = [];
168
        foreach ($classes as $class) {
169
            $actualClass = Relation::getMorphedModel($class);
170
            if (!is_null($actualClass)) {
171
                $class = $actualClass;
172
            }
173
174
            if (!class_exists($class)) {
175
                $this->warn("Class `{$class}` is not found.");
176
                continue;
177
            }
178
179
            if (!in_array(ReactableContract::class, class_implements($class))) {
180
                $this->warn("Class `{$class}` need to implement Reactable contract.");
181
                continue;
182
            }
183
184
            $reactableClasses[] = $class;
185
        }
186
187
        foreach ($reactableClasses as $class) {
188
            /** @var \Illuminate\Database\Eloquent\Model[] $reactables */
189
            $reactables = $class::query()->get();
190
            $progress = $this->output->createProgressBar($reactables->count());
191
            foreach ($reactables as $reactable) {
192
                if ($reactable->getAttributeValue('love_reactant_id') > 0) {
193
                    $progress->advance();
194
                    continue;
195
                }
196
197
                $reactant = $reactable->loveReactant()->create([
198
                    'type' => $reactable->getMorphClass(),
199
                ]);
200
                $reactable->setAttribute('love_reactant_id', $reactant->getId());
201
                $reactable->save();
202
                $progress->advance();
203
            }
204
            $progress->finish();
205
        }
206
        $this->info('');
207
    }
208
209
    private function populateReactions(): void
210
    {
211
        $this->info('Converting Likes & Dislikes to Reactions');
212
        /** @var \Illuminate\Database\Query\Builder $query */
213
        $query = DB::query();
214
        $likes = $query
215
            ->select('*')
216
            ->from('love_likes')
217
            ->orderBy('created_at', 'asc')
218
            ->get();
219
220
        $progress = $this->output->createProgressBar($likes->count());
221
        foreach ($likes as $like) {
222
            $class = $like->likeable_type;
223
            $actualClass = Relation::getMorphedModel($class);
224
            if (!is_null($actualClass)) {
225
                $class = $actualClass;
226
            }
227
228
            if (!class_exists($class)) {
229
                $this->warn("Class `{$class}` is not found.");
230
                $progress->advance();
231
                continue;
232
            }
233
234
            /** @var \Cog\Contracts\Love\Reactable\Models\Reactable $reactable */
235
            $reactable = $class::whereKey($like->likeable_id)->firstOrFail();
236
237
            $userClass = $this->getUserClass();
238
239
            if (!class_exists($class)) {
240
                $this->warn("Class `{$userClass}` is not found.");
241
                $progress->advance();
242
                continue;
243
            }
244
245
            /** @var \Cog\Contracts\Love\Reacterable\Models\Reacterable $reacterable */
246
            $reacterable = $userClass::whereKey($like->user_id)->firstOrFail();
247
            $reactionTypeName = $this->reactionTypeNameFromLikeTypeName($like->type_id);
248
249
            $reactionTypeId = ReactionType::fromName($reactionTypeName)->getId();
250
            $reactantId = $reactable->getLoveReactant()->getId();
251
            $reacterId = $reacterable->getLoveReacter()->getId();
252
253
            $isReactionExists = Reaction::query()
254
                ->where('reaction_type_id', $reactionTypeId)
255
                ->where('reactant_id', $reactantId)
256
                ->where('reacter_id', $reacterId)
257
                ->exists();
258
259
            if ($isReactionExists) {
260
                $progress->advance();
261
                continue;
262
            }
263
264
            $reaction = new Reaction();
265
            $reaction->forceFill([
266
                'reaction_type_id' => $reactionTypeId,
267
                'reactant_id' => $reactantId,
268
                'reacter_id' => $reacterId,
269
                'created_at' => $like->created_at,
270
                'updated_at' => $like->updated_at,
271
            ]);
272
            $reaction->save();
273
            $progress->advance();
274
        }
275
        $progress->finish();
276
        $this->info('');
277
    }
278
279
    private function collectLikeableTypes(): iterable
280
    {
281
        /** @var \Illuminate\Database\Query\Builder $query */
282
        $query = DB::query();
283
        $types = $query
284
            ->select('likeable_type')
285
            ->from('love_likes')
286
            ->groupBy('likeable_type')
287
            ->get()
288
            ->pluck('likeable_type');
289
290
        return $types;
291
    }
292
293
    private function collectLikerTypes(): iterable
294
    {
295
        return [
296
            $this->getUserClass(),
297
        ];
298
    }
299
300
    private function collectLikeTypes(): iterable
301
    {
302
        /** @var \Illuminate\Database\Query\Builder $query */
303
        $query = DB::query();
304
        $types = $query
305
            ->select('type_id')
306
            ->from('love_likes')
307
            ->groupBy('type_id')
308
            ->get()
309
            ->pluck('type_id');
310
311
        return $types;
312
    }
313
314
    private function getUserClass(): string
315
    {
316
        $guard = config('auth.defaults.guard');
317
        $provider = config("auth.guards.{$guard}.provider");
318
        $class = config("auth.providers.{$provider}.model") ?? '';
319
320
        return $class;
321
    }
322
323
    private function reactionTypeNameFromLikeTypeName(
324
        string $name
325
    ): string {
326
        return studly_case(strtolower($name));
327
    }
328
329
    private function deleteMigrationFiles(array $files): void
330
    {
331
        foreach ($files as $file) {
332
            $file = database_path("migrations/{$file}");
333
            if (File::exists($file)) {
334
                File::delete($file);
335
            }
336
        }
337
    }
338
}
339