Passed
Push — master ( c037dd...a7efe5 )
by Anton
02:50
created

UpgradeV5ToV6::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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