Completed
Push — master ( f08741...7d0aeb )
by Anton
09:22
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\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;
23
use Illuminate\Database\Eloquent\Relations\Relation;
24
use Symfony\Component\Console\Input\InputOption;
25
26
final class Recount extends Command
27
{
28
    /**
29
     * The console command name.
30
     *
31
     * @var string
32
     */
33
    protected $name = 'love:recount';
34
35
    /**
36
     * The console command description.
37
     *
38
     * @var string
39
     */
40
    protected $description = 'Recount reactions of the reactable models';
41
42
    /**
43
     * @var \Illuminate\Contracts\Bus\Dispatcher
44
     */
45
    private $dispatcher;
46
47
    /**
48
     * Get the console command options.
49
     *
50
     * @return array
51
     */
52
    protected function getOptions(
53
    ): array {
54
        return [
55
            ['model', null, InputOption::VALUE_OPTIONAL, 'The name of the reactable model'],
56
            ['type', null, InputOption::VALUE_OPTIONAL, 'The name of the reaction type'],
57
            ['queue-connection', null, InputOption::VALUE_OPTIONAL, 'The name of the queue connection'],
58
        ];
59
    }
60
61
    public function __construct(
62
        Dispatcher $dispatcher
63
    ) {
64
        parent::__construct();
65
        $this->dispatcher = $dispatcher;
66
    }
67
68
    /**
69
     * Execute the console command.
70
     *
71
     * @return void
72
     *
73
     * @throws \Cog\Contracts\Love\Reactable\Exceptions\ReactableInvalid
74
     */
75
    public function handle(
76
    ): void {
77
        if ($reactableType = $this->option('model')) {
78
            $reactableType = $this->normalizeReactableModelType($reactableType);
79
        }
80
81
        if ($reactionType = $this->option('type')) {
82
            $reactionType = ReactionType::fromName($reactionType);
83
        }
84
85
        $queueConnectionName = $this->option('queue-connection');
86
        if ($queueConnectionName === null || $queueConnectionName === '') {
87
            $queueConnectionName = 'sync';
88
        }
89
90
        $reactants = $this->collectReactants($reactableType);
91
92
        $this->warnProcessingStartedOn($queueConnectionName);
93
        $this->getOutput()->progressStart($reactants->count());
94
        foreach ($reactants as $reactant) {
95
            $this->dispatcher->dispatch(
96
                (new RebuildReactionAggregatesJob($reactant, $reactionType))
97
                    ->onConnection($queueConnectionName)
0 ignored issues
show
It seems like $queueConnectionName can also be of type string[]; 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

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