Passed
Push — master ( 51f3d0...c27fb2 )
by Anton
07:09 queued 04:17
created

Recount::warnProcessingStartedOn()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 13
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 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\Contracts\Config\Repository as AppConfigRepositoryInterface;
24
use Illuminate\Database\Eloquent\Relations\Relation;
25
use Symfony\Component\Console\Attribute\AsCommand;
26
use Symfony\Component\Console\Input\InputOption;
27
28
#[AsCommand(name: 'love:recount', description: 'Recount reactions of the reactable models')]
29
final class Recount extends Command
30
{
31
    private DispatcherInterface $dispatcher;
32
33
    /**
34
     * Get the console command options.
35
     *
36
     * @return array<int, InputOption>
37
     */
38
    protected function getOptions(): array
39
    {
40
        return [
41
            new InputOption(
42
                name: 'model',
43
                mode: InputOption::VALUE_OPTIONAL,
44
                description: 'The name of the reactable model',
45
            ),
46
            new InputOption(
47
                name: 'type',
48
                mode: InputOption::VALUE_OPTIONAL,
49
                description: 'The name of the reaction type',
50
            ),
51
            new InputOption(
52
                name: 'queue-connection',
53
                mode: InputOption::VALUE_OPTIONAL,
54
                description: 'The name of the queue connection',
55
            ),
56
        ];
57
    }
58
59
    /**
60
     * Execute the console command.
61
     *
62
     * @throws \Cog\Contracts\Love\Reactable\Exceptions\ReactableInvalid
63
     */
64
    public function handle(
65
        DispatcherInterface $dispatcher,
66
        AppConfigRepositoryInterface $appConfigRepository,
67
    ): int {
68
        $this->dispatcher = $dispatcher;
69
70
        if ($reactableType = $this->option('model')) {
71
            $reactableType = $this->normalizeReactableModelType($reactableType);
72
        }
73
74
        if ($reactionType = $this->option('type')) {
75
            $reactionType = ReactionType::fromName($reactionType);
76
        }
77
78
        $queueConnectionName = $this->option('queue-connection');
79
        if ($queueConnectionName === null || $queueConnectionName === '') {
80
            $queueConnectionName = $appConfigRepository->get('queue.default');
81
        }
82
83
        $this->warn(
84
            "Rebuilding reaction aggregates using `$queueConnectionName` queue connection."
85
        );
86
87
        $reactants = $this->collectReactants($reactableType);
88
89
        $this->getOutput()->progressStart($reactants->count());
90
        foreach ($reactants as $reactant) {
91
            $this->dispatcher->dispatch(
92
                (new RebuildReactionAggregatesJob($reactant, $reactionType))
93
                    ->onConnection($queueConnectionName)
94
            );
95
96
            $this->getOutput()->progressAdvance();
97
        }
98
        $this->getOutput()->progressFinish();
99
100
        return self::SUCCESS;
101
    }
102
103
    /**
104
     * Normalize reactable model type.
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
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

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
    ): ReactableInterface {
127
        if (!class_exists($modelType)) {
128
            $modelType = $this->findModelTypeInMorphMap($modelType);
129
        }
130
131
        $model = new $modelType();
132
133
        if (!$model instanceof ReactableInterface) {
134
            throw ReactableInvalid::notImplementInterface($modelType);
135
        }
136
137
        return $model;
138
    }
139
140
    /**
141
     * Find model type in morph mappings registry.
142
     *
143
     * @throws \Cog\Contracts\Love\Reactable\Exceptions\ReactableInvalid
144
     */
145
    private function findModelTypeInMorphMap(
146
        string $modelType,
147
    ): string {
148
        $morphMap = Relation::morphMap();
149
150
        if (!isset($morphMap[$modelType])) {
151
            throw ReactableInvalid::classNotExists($modelType);
152
        }
153
154
        return $morphMap[$modelType];
155
    }
156
157
    /**
158
     * Collect all reactants we want to affect.
159
     *
160
     * @param string|null $reactableType
161
     * @return \Cog\Contracts\Love\Reactant\Models\Reactant[]|\Illuminate\Database\Eloquent\Collection
162
     */
163
    private function collectReactants(
164
        ?string $reactableType = null,
165
    ): iterable {
166
        $reactantsQuery = Reactant::query();
167
168
        if ($reactableType !== null) {
169
            $reactantsQuery->where('type', $reactableType);
170
        }
171
172
        return $reactantsQuery->get();
173
    }
174
}
175