Passed
Push — master ( 0cf9a8...478c1f )
by Anton
03:37
created

src/Console/Commands/Recount.php (1 issue)

Labels
Severity
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\Exceptions\ReactableInvalid;
17
use Cog\Contracts\Love\Reactable\Models\Reactable as ReactableContract;
18
use Cog\Contracts\Love\Reactant\Models\Reactant as ReactantContract;
19
use Cog\Laravel\Love\Reactant\Models\Reactant;
20
use Cog\Laravel\Love\Reactant\ReactionCounter\Models\ReactionCounter;
21
use Cog\Laravel\Love\Reactant\ReactionCounter\Services\ReactionCounterService;
22
use Cog\Laravel\Love\Reactant\ReactionTotal\Models\ReactionTotal;
23
use Cog\Laravel\Love\ReactionType\Models\ReactionType;
24
use Illuminate\Console\Command;
25
use Illuminate\Database\Eloquent\Relations\Relation;
26
27
final class Recount extends Command
28
{
29
    /**
30
     * The name and signature of the console command.
31
     *
32
     * @var string
33
     */
34
    protected $signature = 'love:recount
35
        {--model= : The name of the reactable model}
36
        {--type= : The name of the reaction type}';
37
38
    /**
39
     * The console command description.
40
     *
41
     * @var string
42
     */
43
    protected $description = 'Recount reactions of the reactable models';
44
45
    /**
46
     * Execute the console command.
47
     *
48
     * @return void
49
     *
50
     * @throws \Cog\Contracts\Love\Reactable\Exceptions\ReactableInvalid
51
     */
52
    public function handle(): void
53
    {
54
        if ($reactableType = $this->option('model')) {
55
            $reactableType = $this->normalizeReactableModelType($reactableType);
56
        }
57
58
        if ($reactionType = $this->option('type')) {
59
            $reactionType = ReactionType::fromName($reactionType);
60
        }
61
62
        $reactantsQuery = Reactant::query();
63
64
        if ($reactableType) {
65
            $reactantsQuery->where('type', $reactableType);
66
        }
67
68
        $reactants = $reactantsQuery->get();
69
        $this->getOutput()->progressStart($reactants->count());
70
        foreach ($reactants as $reactant) {
71
            /** @var \Illuminate\Database\Eloquent\Builder $query */
72
            $query = $reactant->reactions();
73
74
            if ($reactionType) {
75
                $query->where('reaction_type_id', $reactionType->getId());
76
            }
77
78
            $counters = $reactant->getReactionCounters();
79
80
            /** @var \Cog\Laravel\Love\Reactant\ReactionCounter\Models\ReactionCounter $counter */
81
            foreach ($counters as $counter) {
82
                if ($reactionType && $counter->isNotReactionOfType($reactionType)) {
83
                    continue;
84
                }
85
86
                $counter->update([
87
                    'count' => ReactionCounter::COUNT_DEFAULT,
88
                    'weight' => ReactionCounter::WEIGHT_DEFAULT,
89
                ]);
90
            }
91
92
            $reactions = $query->get();
93
            $this->recountCounters($reactant, $reactions);
94
            $this->recountTotal($reactant);
95
            $this->getOutput()->progressAdvance();
96
        }
97
        $this->getOutput()->progressFinish();
98
    }
99
100
    /**
101
     * Normalize reactable model type.
102
     *
103
     * @param string $modelType
104
     * @return string
105
     *
106
     * @throws \Cog\Contracts\Love\Reactable\Exceptions\ReactableInvalid
107
     */
108
    private function normalizeReactableModelType(
109
        string $modelType
110
    ): string {
111
        return $this
112
            ->reactableModelFromType($modelType)
113
            ->getMorphClass();
0 ignored issues
show
The method getMorphClass() does not exist on Cog\Contracts\Love\Reactable\Models\Reactable. Since it exists in all sub-types, consider adding an abstract or default implementation to Cog\Contracts\Love\Reactable\Models\Reactable. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

113
            ->/** @scrutinizer ignore-call */ getMorphClass();
Loading history...
114
    }
115
116
    /**
117
     * Instantiate model from type or morph map value.
118
     *
119
     * @param string $modelType
120
     * @return \Cog\Contracts\Love\Reactable\Models\Reactable|\Illuminate\Database\Eloquent\Model
121
     *
122
     * @throws \Cog\Contracts\Love\Reactable\Exceptions\ReactableInvalid
123
     */
124
    private function reactableModelFromType(
125
        string $modelType
126
    ): ReactableContract {
127
        if (!class_exists($modelType)) {
128
            $modelType = $this->findModelTypeInMorphMap($modelType);
129
        }
130
131
        $model = new $modelType;
132
133
        if (!$model instanceof ReactableContract) {
134
            throw ReactableInvalid::notImplementInterface($modelType);
135
        }
136
137
        return $model;
138
    }
139
140
    /**
141
     * Find model type in morph mappings registry.
142
     *
143
     * @param string $modelType
144
     * @return string
145
     *
146
     * @throws \Cog\Contracts\Love\Reactable\Exceptions\ReactableInvalid
147
     */
148
    private function findModelTypeInMorphMap(
149
        string $modelType
150
    ): string {
151
        $morphMap = Relation::morphMap();
152
153
        if (!isset($morphMap[$modelType])) {
154
            throw ReactableInvalid::classNotExists($modelType);
155
        }
156
157
        return $morphMap[$modelType];
158
    }
159
160
    private function recountTotal(
161
        ReactantContract $reactant
162
    ): void {
163
        $counters = $reactant->getReactionCounters();
164
165
        if (count($counters) === 0) {
166
            return;
167
        }
168
169
        $totalCount = ReactionTotal::COUNT_DEFAULT;
170
        $totalWeight = ReactionTotal::WEIGHT_DEFAULT;
171
172
        foreach ($counters as $counter) {
173
            $totalCount += $counter->getCount();
174
            $totalWeight += $counter->getWeight();
175
        }
176
177
        /** @var \Cog\Laravel\Love\Reactant\ReactionTotal\Models\ReactionTotal $total */
178
        $total = $reactant->getReactionTotal();
179
        $total->update([
180
            'count' => $totalCount,
181
            'weight' => $totalWeight,
182
        ]);
183
    }
184
185
    private function recountCounters(
186
        ReactantContract $reactant,
187
        iterable $reactions
188
    ): void {
189
        $service = new ReactionCounterService($reactant);
190
191
        foreach ($reactions as $reaction) {
192
            $service->addReaction($reaction);
193
        }
194
    }
195
}
196