Passed
Push — master ( 5e2a34...d842bb )
by Anton
02:48
created

src/Console/Commands/Recount.php (2 issues)

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\Services\ReactionCounterService;
21
use Cog\Laravel\Love\ReactionType\Models\ReactionType;
22
use Illuminate\Console\Command;
23
use Illuminate\Database\Eloquent\Relations\Relation;
24
25
final class Recount extends Command
26
{
27
    /**
28
     * The name and signature of the console command.
29
     *
30
     * @var string
31
     */
32
    protected $signature = 'love:recount {reactableType?} {type?}';
33
34
    /**
35
     * The console command description.
36
     *
37
     * @var string
38
     */
39
    protected $description = 'Recount reactions of the reactable models';
40
41
    /**
42
     * Execute the console command.
43
     *
44
     * @return void
45
     *
46
     * @throws \Cog\Contracts\Love\Reactable\Exceptions\ReactableInvalid
47
     */
48
    public function handle(): void
49
    {
50
        if ($reactableType = $this->argument('reactableType')) {
51
            $reactableType = $this->normalizeReactableModelType($reactableType);
0 ignored issues
show
It seems like $reactableType can also be of type string[]; however, parameter $modelType of Cog\Laravel\Love\Console...izeReactableModelType() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

51
            $reactableType = $this->normalizeReactableModelType(/** @scrutinizer ignore-type */ $reactableType);
Loading history...
52
        }
53
54
        if ($reactionType = $this->argument('type')) {
55
            $reactionType = ReactionType::fromName($reactionType);
0 ignored issues
show
It seems like $reactionType can also be of type string[]; however, parameter $name of Cog\Laravel\Love\Reactio...eactionType::fromName() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

55
            $reactionType = ReactionType::fromName(/** @scrutinizer ignore-type */ $reactionType);
Loading history...
56
        }
57
58
        $reactantsQuery = Reactant::query();
59
60
        if ($reactableType) {
61
            $reactantsQuery->where('type', $reactableType);
62
        }
63
64
        $reactants = $reactantsQuery->get();
65
        foreach ($reactants as $reactant) {
66
            /** @var \Illuminate\Database\Eloquent\Builder $query */
67
            $query = $reactant->reactions();
68
69
            if ($reactionType) {
70
                $query->where('reaction_type_id', $reactionType->getId());
71
            }
72
73
            $counters = $reactant->getReactionCounters();
74
75
            /** @var \Cog\Laravel\Love\Reactant\ReactionCounter\Models\ReactionCounter $counter */
76
            foreach ($counters as $counter) {
77
                if ($reactionType && !$counter->isReactionOfType($reactionType)) {
78
                    continue;
79
                }
80
81
                $counter->update([
82
                    'count' => 0,
83
                    'weight' => 0.0,
84
                ]);
85
            }
86
87
            $reactions = $query->get();
88
            $this->recountCounters($reactant, $reactions);
89
            $this->recountTotal($reactant);
90
        }
91
    }
92
93
    /**
94
     * Normalize reactable model type.
95
     *
96
     * @param string $modelType
97
     * @return string
98
     *
99
     * @throws \Cog\Contracts\Love\Reactable\Exceptions\ReactableInvalid
100
     */
101
    private function normalizeReactableModelType(
102
        string $modelType
103
    ): string {
104
        return $this
105
            ->reactableModelFromType($modelType)
106
            ->getMorphClass();
107
    }
108
109
    /**
110
     * Instantiate model from type or morph map value.
111
     *
112
     * @param string $modelType
113
     * @return \Cog\Contracts\Love\Reactable\Models\Reactable|\Illuminate\Database\Eloquent\Model
114
     *
115
     * @throws \Cog\Contracts\Love\Reactable\Exceptions\ReactableInvalid
116
     */
117
    private function reactableModelFromType(
118
        string $modelType
119
    ): ReactableContract {
120
        if (!class_exists($modelType)) {
121
            $modelType = $this->findModelTypeInMorphMap($modelType);
122
        }
123
124
        $model = new $modelType;
125
126
        if (!$model instanceof ReactableContract) {
127
            throw ReactableInvalid::notImplementInterface($modelType);
128
        }
129
130
        return $model;
131
    }
132
133
    /**
134
     * Find model type in morph mappings registry.
135
     *
136
     * @param string $modelType
137
     * @return string
138
     *
139
     * @throws \Cog\Contracts\Love\Reactable\Exceptions\ReactableInvalid
140
     */
141
    private function findModelTypeInMorphMap(
142
        string $modelType
143
    ): string {
144
        $morphMap = Relation::morphMap();
145
146
        if (!isset($morphMap[$modelType])) {
147
            throw ReactableInvalid::classNotExists($modelType);
148
        }
149
150
        return $morphMap[$modelType];
151
    }
152
153
    private function recountTotal(
154
        ReactantContract $reactant
155
    ): void {
156
        $counters = $reactant->getReactionCounters();
157
        $totalCount = 0;
158
        $totalWeight = 0.0;
159
        foreach ($counters as $counter) {
160
            $totalCount += $counter->getCount();
161
            $totalWeight += $counter->getWeight();
162
        }
163
164
        /** @var \Cog\Laravel\Love\Reactant\ReactionTotal\Models\ReactionTotal $total */
165
        $total = $reactant->getReactionTotal();
166
        $total->update([
167
            'count' => $totalCount,
168
            'weight' => $totalWeight,
169
        ]);
170
    }
171
172
    private function recountCounters(
173
        ReactantContract $reactant,
174
        iterable $reactions
175
    ): void {
176
        $service = new ReactionCounterService($reactant);
177
178
        foreach ($reactions as $reaction) {
179
            $service->addReaction($reaction);
180
        }
181
    }
182
}
183