Passed
Pull Request — master (#227)
by Anton
02:58
created

Recount   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 159
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 17
eloc 58
c 0
b 0
f 0
dl 0
loc 159
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A findModelTypeInMorphMap() 0 10 2
A normalizeReactableModelType() 0 6 1
A collectReactants() 0 10 2
A handle() 0 33 6
A warnProcessingStartedOn() 0 13 2
A reactableModelFromType() 0 14 3
A getOptions() 0 17 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\Exceptions\ReactableInvalid;
17
use Cog\Contracts\Love\Reactable\Models\Reactable as ReactableInterface;
18
use Cog\Laravel\Love\Reactant\Jobs\RebuildReactionAggregatesJob;
19
use Cog\Laravel\Love\Reactant\Models\Reactant;
20
use Cog\Laravel\Love\ReactionType\Models\ReactionType;
21
use Illuminate\Console\Command;
22
use Illuminate\Contracts\Bus\Dispatcher as DispatcherInterface;
23
use Illuminate\Database\Eloquent\Relations\Relation;
24
use Symfony\Component\Console\Attribute\AsCommand;
25
use Symfony\Component\Console\Input\InputOption;
26
27
#[AsCommand(name: 'love:recount', description: 'Recount reactions of the reactable models')]
28
final class Recount extends Command
29
{
30
    private DispatcherInterface $dispatcher;
31
32
    /**
33
     * Get the console command options.
34
     *
35
     * @return array<int, InputOption>
36
     */
37
    protected function getOptions(): array
38
    {
39
        return [
40
            new InputOption(
41
                name: 'model',
42
                mode: InputOption::VALUE_OPTIONAL,
43
                description: 'The name of the reactable model',
44
            ),
45
            new InputOption(
46
                name: 'type',
47
                mode: InputOption::VALUE_OPTIONAL,
48
                description: 'The name of the reaction type',
49
            ),
50
            new InputOption(
51
                name: 'queue-connection',
52
                mode: InputOption::VALUE_OPTIONAL,
53
                description: 'The name of the queue connection',
54
            ),
55
        ];
56
    }
57
58
    /**
59
     * Execute the console command.
60
     *
61
     * @throws \Cog\Contracts\Love\Reactable\Exceptions\ReactableInvalid
62
     */
63
    public function handle(
64
        DispatcherInterface $dispatcher,
65
    ): int {
66
        $this->dispatcher = $dispatcher;
67
68
        if ($reactableType = $this->option('model')) {
69
            $reactableType = $this->normalizeReactableModelType($reactableType);
70
        }
71
72
        if ($reactionType = $this->option('type')) {
73
            $reactionType = ReactionType::fromName($reactionType);
74
        }
75
76
        $queueConnectionName = $this->option('queue-connection');
77
        if ($queueConnectionName === null || $queueConnectionName === '') {
78
            $queueConnectionName = 'sync';
79
        }
80
81
        $reactants = $this->collectReactants($reactableType);
82
83
        $this->warnProcessingStartedOn($queueConnectionName);
84
        $this->getOutput()->progressStart($reactants->count());
85
        foreach ($reactants as $reactant) {
86
            $this->dispatcher->dispatch(
87
                (new RebuildReactionAggregatesJob($reactant, $reactionType))
88
                    ->onConnection($queueConnectionName)
0 ignored issues
show
Bug introduced by
It seems like $queueConnectionName can also be of type array; however, parameter $connection of Cog\Laravel\Love\Reactan...atesJob::onConnection() does only seem to accept null|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

88
                    ->onConnection(/** @scrutinizer ignore-type */ $queueConnectionName)
Loading history...
89
            );
90
91
            $this->getOutput()->progressAdvance();
92
        }
93
        $this->getOutput()->progressFinish();
94
95
        return self::SUCCESS;
96
    }
97
98
    /**
99
     * Normalize reactable model type.
100
     *
101
     * @throws \Cog\Contracts\Love\Reactable\Exceptions\ReactableInvalid
102
     */
103
    private function normalizeReactableModelType(
104
        string $modelType,
105
    ): string {
106
        return $this
107
            ->reactableModelFromType($modelType)
108
            ->getMorphClass();
0 ignored issues
show
Bug introduced by
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

108
            ->/** @scrutinizer ignore-call */ getMorphClass();
Loading history...
109
    }
110
111
    /**
112
     * Instantiate model from type or morph map value.
113
     *
114
     * @param string $modelType
115
     * @return \Cog\Contracts\Love\Reactable\Models\Reactable|\Illuminate\Database\Eloquent\Model
116
     *
117
     * @throws \Cog\Contracts\Love\Reactable\Exceptions\ReactableInvalid
118
     */
119
    private function reactableModelFromType(
120
        string $modelType,
121
    ): ReactableInterface {
122
        if (!class_exists($modelType)) {
123
            $modelType = $this->findModelTypeInMorphMap($modelType);
124
        }
125
126
        $model = new $modelType();
127
128
        if (!$model instanceof ReactableInterface) {
129
            throw ReactableInvalid::notImplementInterface($modelType);
130
        }
131
132
        return $model;
133
    }
134
135
    /**
136
     * Find model type in morph mappings registry.
137
     *
138
     * @throws \Cog\Contracts\Love\Reactable\Exceptions\ReactableInvalid
139
     */
140
    private function findModelTypeInMorphMap(
141
        string $modelType,
142
    ): string {
143
        $morphMap = Relation::morphMap();
144
145
        if (!isset($morphMap[$modelType])) {
146
            throw ReactableInvalid::classNotExists($modelType);
147
        }
148
149
        return $morphMap[$modelType];
150
    }
151
152
    /**
153
     * Collect all reactants we want to affect.
154
     *
155
     * @param string|null $reactableType
156
     * @return \Cog\Contracts\Love\Reactant\Models\Reactant[]|\Illuminate\Database\Eloquent\Collection
157
     */
158
    private function collectReactants(
159
        ?string $reactableType = null,
160
    ): iterable {
161
        $reactantsQuery = Reactant::query();
162
163
        if ($reactableType !== null) {
164
            $reactantsQuery->where('type', $reactableType);
165
        }
166
167
        return $reactantsQuery->get();
168
    }
169
170
    /**
171
     * Write warning output that processing has been started.
172
     */
173
    private function warnProcessingStartedOn(
174
        ?string $queueConnectionName,
175
    ): void {
176
        if ($queueConnectionName === 'sync') {
177
            $message = 'Rebuilding reaction aggregates synchronously.';
178
        } else {
179
            $message = sprintf(
180
                'Adding rebuild reaction aggregates to the `%s` queue connection.',
181
                $queueConnectionName
182
            );
183
        }
184
185
        $this->warn($message);
186
    }
187
}
188