Passed
Pull Request — master (#159)
by
unknown
04:58
created

UpgradeV5ToV8::dbMigrate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 1
c 1
b 0
f 1
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
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 Doctrine\DBAL\Driver as DoctrineDbalDriver;
0 ignored issues
show
Bug introduced by
The type Doctrine\DBAL\Driver was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
21
use Illuminate\Console\Command;
22
use Illuminate\Database\ConnectionInterface;
23
use Illuminate\Database\Eloquent\Relations\Relation;
24
use Illuminate\Database\Schema\Blueprint;
25
use Illuminate\Database\Schema\Builder;
26
use Illuminate\Support\Facades\Config;
27
use Illuminate\Support\Facades\DB;
28
use Illuminate\Support\Facades\File;
29
use Illuminate\Support\Facades\Schema;
30
use Illuminate\Support\Str;
31
32
final class UpgradeV5ToV8 extends Command
33
{
34
    /**
35
     * The console command name.
36
     *
37
     * @var string
38
     */
39
    protected $name = 'love:upgrade-v5-to-v8';
40
41
    /**
42
     * The console command description.
43
     *
44
     * @var string
45
     */
46
    protected $description = 'Upgrade Love package from v5 to v8';
47
48
    /**
49
     * Execute the console command.
50
     *
51
     * @return void
52
     */
53
    public function handle(): void
54
    {
55
        $this->assertRequirements();
56
        $this->warn('Started Laravel Love v5 to v8 upgrade process.');
57
        # v5 to v6
58
        $this->dbMigrate();
59
        $this->populateReactionTypes();
60
        $this->populateReacters();
61
        $this->populateReactants();
62
        $this->populateReactions();
63
64
        # v7 to v8 change
65
        $this->dbChangeReactions();
66
67
        # clean v5 files and tables
68
        $this->dbCleanup();
69
        $this->filesystemCleanup();
70
71
        $this->info('Completed Laravel Love v5 to v8 upgrade process.');
72
    }
73
74
    private function assertRequirements(): void
75
    {
76
        if (interface_exists(DoctrineDbalDriver::class)) {
77
            return;
78
        }
79
80
        $this->error('Doctrine DBAL is missing!');
81
        $this->info('<comment>Install it with Composer:</comment> composer require doctrine/dbal');
82
        exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
83
    }
84
85
    private function dbMigrate(): void
86
    {
87
        $this->call('migrate');
88
    }
89
90
    private function populateReactionTypes(): void
91
    {
92
        $this->info('Populating Reaction Types');
93
        $names = $this->collectLikeTypes();
94
        $mass = [
95
            'Like' => 1,
96
            'Dislike' => -1,
97
        ];
98
99
        foreach ($names as $name) {
100
            $name = $this->reactionTypeNameFromLikeTypeName($name);
101
102
            if (!isset($mass[$name])) {
103
                $this->warn("Reaction mass for type `{$name}` not found.");
104
                continue;
105
            }
106
107
            if (ReactionType::query()->where('name', $name)->exists()) {
108
                continue;
109
            }
110
111
            ReactionType::query()->create([
112
                'name' => $name,
113
                'mass' => $mass[$name],
114
            ]);
115
        }
116
    }
117
118
    private function populateReacters(): void
119
    {
120
        $this->info('Populating Reacters');
121
        $classes = $this->collectLikerTypes();
122
123
        $reacterableClasses = [];
124
        foreach ($classes as $class) {
125
            if (!class_exists($class)) {
126
                $this->warn("Class `{$class}` is not found.");
127
                continue;
128
            }
129
130
            if (!in_array(ReacterableContract::class, class_implements($class))) {
131
                $this->warn("Class `{$class}` need to implement Reacterable contract.");
132
                continue;
133
            }
134
135
            $reacterableClasses[] = $class;
136
        }
137
138
        foreach ($reacterableClasses as $class) {
139
            /** @var \Illuminate\Database\Eloquent\Model[] $reacterables */
140
            $reacterables = $class::query()->get();
141
            $progress = $this->output->createProgressBar($reacterables->count());
142
            foreach ($reacterables as $reacterable) {
143
                if ($reacterable->getAttributeValue('love_reacter_id') > 0) {
144
                    $progress->advance();
145
                    continue;
146
                }
147
148
                $reacter = $reacterable->loveReacter()->create([
149
                    'type' => $reacterable->getMorphClass(),
150
                ]);
151
                $reacterable->setAttribute('love_reacter_id', $reacter->getId());
152
                $reacterable->save();
153
                $progress->advance();
154
            }
155
            $progress->finish();
156
        }
157
        $this->info('');
158
    }
159
160
    private function populateReactants(): void
161
    {
162
        $this->info('Populating Reactants');
163
        $classes = $this->collectLikeableTypes();
164
165
        $reactableClasses = [];
166
        foreach ($classes as $class) {
167
            $actualClass = Relation::getMorphedModel($class);
168
            if ($actualClass !== null) {
169
                $class = $actualClass;
170
            }
171
172
            if (!class_exists($class)) {
173
                $this->warn("Class `{$class}` is not found.");
174
                continue;
175
            }
176
177
            if (!in_array(ReactableContract::class, class_implements($class))) {
178
                $this->warn("Class `{$class}` need to implement Reactable contract.");
179
                continue;
180
            }
181
182
            $reactableClasses[] = $class;
183
        }
184
185
        foreach ($reactableClasses as $class) {
186
            /** @var \Illuminate\Database\Eloquent\Model[] $reactables */
187
            $reactables = $class::query()->get();
188
            $progress = $this->output->createProgressBar($reactables->count());
189
            foreach ($reactables as $reactable) {
190
                if ($reactable->getAttributeValue('love_reactant_id') > 0) {
191
                    $progress->advance();
192
                    continue;
193
                }
194
195
                $reactant = $reactable->loveReactant()->create([
196
                    'type' => $reactable->getMorphClass(),
197
                ]);
198
                $reactable->setAttribute('love_reactant_id', $reactant->getId());
199
                $reactable->save();
200
                $progress->advance();
201
            }
202
            $progress->finish();
203
        }
204
        $this->info('');
205
    }
206
207
    private function populateReactions(): void
208
    {
209
        $this->info('Converting Likes & Dislikes to Reactions');
210
        /** @var \Illuminate\Database\Query\Builder $query */
211
        $query = DB::query();
212
        $likes = $query
213
            ->select('*')
214
            ->from('love_likes')
215
            ->orderBy('created_at', 'asc')
216
            ->get();
217
218
        $progress = $this->output->createProgressBar($likes->count());
219
        foreach ($likes as $like) {
220
            $class = $like->likeable_type;
221
            $actualClass = Relation::getMorphedModel($class);
222
            if ($actualClass !== null) {
223
                $class = $actualClass;
224
            }
225
226
            if (!class_exists($class)) {
227
                $this->warn("Class `{$class}` is not found.");
228
                $progress->advance();
229
                continue;
230
            }
231
232
            /** @var \Cog\Contracts\Love\Reactable\Models\Reactable $reactable */
233
            $reactable = $class::whereKey($like->likeable_id)->firstOrFail();
234
235
            $userClass = $this->getUserClass();
236
237
            if (!class_exists($class)) {
238
                $this->warn("Class `{$userClass}` is not found.");
239
                $progress->advance();
240
                continue;
241
            }
242
243
            /** @var \Cog\Contracts\Love\Reacterable\Models\Reacterable $reacterable */
244
            $reacterable = $userClass::whereKey($like->user_id)->firstOrFail();
245
            $reactionTypeName = $this->reactionTypeNameFromLikeTypeName($like->type_id);
246
247
            $reactionTypeId = ReactionType::fromName($reactionTypeName)->getId();
248
            $reactantId = $reactable->getLoveReactant()->getId();
249
            $reacterId = $reacterable->getLoveReacter()->getId();
250
251
            $isReactionExists = Reaction::query()
252
                ->where('reaction_type_id', $reactionTypeId)
253
                ->where('reactant_id', $reactantId)
254
                ->where('reacter_id', $reacterId)
255
                ->exists();
256
257
            if ($isReactionExists) {
258
                $progress->advance();
259
                continue;
260
            }
261
262
            $reaction = new Reaction();
263
            $reaction->forceFill([
264
                'reaction_type_id' => $reactionTypeId,
265
                'reactant_id' => $reactantId,
266
                'reacter_id' => $reacterId,
267
                'created_at' => $like->created_at,
268
                'updated_at' => $like->updated_at,
269
            ]);
270
            $reaction->save();
271
            $progress->advance();
272
        }
273
        $progress->finish();
274
        $this->info('');
275
    }
276
277
    private function dbChangeReactions(): void
278
    {
279
        $this->warn('DB: Updating reaction rate column values for existing records.');
280
        $this
281
            ->getDbQuery()
282
            ->table('love_reactions')
283
            ->where('rate', 0.0)
284
            ->update([
285
                'rate' => 1.0,
286
            ]);
287
        $this->info('DB: Updated reaction rate column values for existing records.');
288
    }
289
290
    private function getDbQuery(): ConnectionInterface
291
    {
292
        return DB::connection($this->getDatabaseConnection());
293
    }
294
295
    private function getDatabaseConnection(): ?string
296
    {
297
        return Config::get('love.storage.database.connection');
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')->map(function($type) {
310
                return ucfirst(strtolower($type));
311
            });
312
        return $types;
313
    }
314
315
    private function collectLikerTypes(): iterable
316
    {
317
        return [
318
            $this->getUserClass(),
319
        ];
320
    }
321
322
    private function collectLikeableTypes(): iterable
323
    {
324
        /** @var \Illuminate\Database\Query\Builder $query */
325
        $query = DB::query();
326
        $types = $query
327
            ->select('likeable_type')
328
            ->from('love_likes')
329
            ->groupBy('likeable_type')
330
            ->get()
331
            ->pluck('likeable_type');
332
333
        return $types;
334
    }
335
336
    private function getUserClass(): string
337
    {
338
        $guard = config('auth.defaults.guard');
339
        $provider = config("auth.guards.{$guard}.provider");
340
        $class = config("auth.providers.{$provider}.model") ?? '';
341
342
        return $class;
343
    }
344
345
    private function reactionTypeNameFromLikeTypeName(string $name): string
346
    {
347
        return Str::studly(strtolower($name));
348
    }
349
350
    private function dbCleanup(): void
351
    {
352
        $this->info('Deleting old database tables');
353
        DB::statement('
354
            DROP TABLE `love_like_counters`;
355
        ');
356
        DB::statement('
357
            DROP TABLE `love_likes`;
358
        ');
359
        DB::statement('
360
            DELETE FROM `migrations`
361
            WHERE `migration` = "2016_09_02_153301_create_love_likes_table"
362
            LIMIT 1;
363
        ');
364
        DB::statement('
365
            DELETE FROM `migrations`
366
            WHERE `migration` = "2016_09_02_163301_create_love_like_counters_table"
367
            LIMIT 1;
368
        ');
369
    }
370
371
    private function filesystemCleanup(): void
372
    {
373
        $this->info('Deleting old database migration files');
374
        $this->deleteMigrationFiles([
375
            '2016_09_02_153301_create_love_likes_table.php',
376
            '2016_09_02_163301_create_love_like_counters_table.php',
377
        ]);
378
    }
379
380
    private function deleteMigrationFiles(array $files): void
381
    {
382
        foreach ($files as $file) {
383
            $file = database_path("migrations/{$file}");
384
            if (File::exists($file)) {
385
                File::delete($file);
386
            }
387
        }
388
    }
389
}
390