Passed
Push — master ( f75c84...6aec09 )
by Anton
02:54
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
use Symfony\Component\Console\Input\InputOption;
27
28
final class Recount extends Command
29
{
30
    /**
31
     * The console command name.
32
     *
33
     * @var string
34
     */
35
    protected $name = 'love:recount';
36
37
    /**
38
     * The console command description.
39
     *
40
     * @var string
41
     */
42
    protected $description = 'Recount reactions of the reactable models';
43
44
    /**
45
     * Execute the console command.
46
     *
47
     * @return void
48
     *
49
     * @throws \Cog\Contracts\Love\Reactable\Exceptions\ReactableInvalid
50
     */
51
    public function handle(): void
52
    {
53
        if ($reactableType = $this->option('model')) {
54
            $reactableType = $this->normalizeReactableModelType($reactableType);
55
        }
56
57
        if ($reactionType = $this->option('type')) {
58
            $reactionType = ReactionType::fromName($reactionType);
59
        }
60
61
        $reactantsQuery = Reactant::query();
62
63
        if ($reactableType) {
64
            $reactantsQuery->where('type', $reactableType);
65
        }
66
67
        $reactants = $reactantsQuery->get();
68
        $this->getOutput()->progressStart($reactants->count());
69
        foreach ($reactants as $reactant) {
70
            /** @var \Illuminate\Database\Eloquent\Builder $query */
71
            $query = $reactant->reactions();
72
73
            if ($reactionType) {
74
                $query->where('reaction_type_id', $reactionType->getId());
75
            }
76
77
            $counters = $reactant->getReactionCounters();
78
79
            /** @var \Cog\Laravel\Love\Reactant\ReactionCounter\Models\ReactionCounter $counter */
80
            foreach ($counters as $counter) {
81
                if ($reactionType && $counter->isNotReactionOfType($reactionType)) {
82
                    continue;
83
                }
84
85
                $counter->update([
86
                    'count' => ReactionCounter::COUNT_DEFAULT,
87
                    'weight' => ReactionCounter::WEIGHT_DEFAULT,
88
                ]);
89
            }
90
91
            $reactions = $query->get();
92
            $this->recountCounters($reactant, $reactions);
93
            $this->recountTotal($reactant);
94
            $this->getOutput()->progressAdvance();
95
        }
96
        $this->getOutput()->progressFinish();
97
    }
98
99
    protected function getOptions(): array
100
    {
101
        return [
102
            ['model', null, InputOption::VALUE_OPTIONAL, 'The name of the reactable model'],
103
            ['type', null, InputOption::VALUE_OPTIONAL, 'The name of the reaction type'],
104
        ];
105
    }
106
107
    /**
108
     * Normalize reactable model type.
109
     *
110
     * @param string $modelType
111
     * @return string
112
     *
113
     * @throws \Cog\Contracts\Love\Reactable\Exceptions\ReactableInvalid
114
     */
115
    private function normalizeReactableModelType(
116
        string $modelType
117
    ): string {
118
        return $this
119
            ->reactableModelFromType($modelType)
120
            ->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

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