UpgradeV5ToV6::populateReactions()   B
last analyzed

Complexity

Conditions 6
Paths 9

Size

Total Lines 67
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 47
c 1
b 0
f 0
dl 0
loc 67
rs 8.5341
cc 6
nc 9
nop 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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