UpdateSubscriptionsCommand::getUsersForUpdate()   C
last analyzed

Complexity

Conditions 9
Paths 13

Size

Total Lines 75

Duplication

Lines 9
Ratio 12 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 0
Metric Value
dl 9
loc 75
ccs 0
cts 58
cp 0
rs 6.9898
c 0
b 0
f 0
cc 9
nc 13
nop 0
crap 90

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Skobkin\Bundle\PointToolsBundle\Command;
4
5
use Doctrine\ORM\EntityManagerInterface;
6
use Psr\Log\LoggerInterface;
7
use Skobkin\Bundle\PointToolsBundle\Entity\{Subscription, User};
8
use Skobkin\Bundle\PointToolsBundle\Exception\Api\UserNotFoundException;
9
use Skobkin\Bundle\PointToolsBundle\Repository\UserRepository;
10
use Skobkin\Bundle\PointToolsBundle\Service\{SubscriptionsManager, Api\UserApi};
11
use Symfony\Component\Console\Command\Command;
12
use Symfony\Component\Console\Helper\ProgressBar;
13
use Symfony\Component\Console\Input\{InputInterface, InputOption};
14
use Symfony\Component\Console\Output\OutputInterface;
15
16
/**
17
 * @todo https://symfony.com/doc/current/console/lockable_trait.html
18
 */
19
class UpdateSubscriptionsCommand extends Command
20
{
21
    /** @var EntityManagerInterface */
22
    private $em;
23
24
    /** @var LoggerInterface */
25
    private $logger;
26
27
    /** @var UserRepository */
28
    private $userRepo;
29
30
    /** @var InputInterface */
31
    private $input;
32
33
    /** @var UserApi */
34
    private $api;
35
36
    /** @var int */
37
    private $apiDelay = 500000;
38
39
    /** @var int */
40
    private $appUserId;
41
42
    /** @var SubscriptionsManager */
43
    private $subscriptionManager;
44
45
    /** @var ProgressBar */
46
    private $progress;
47
48 View Code Duplication
    public function __construct(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
49
        EntityManagerInterface $em,
50
        LoggerInterface $logger,
51
        UserRepository $userRepo,
52
        UserApi $api,
53
        SubscriptionsManager $subscriptionManager,
54
        int $apiDelay,
55
        int $appUserId
56
    ) {
57
        parent::__construct();
58
59
        $this->em = $em;
60
        $this->logger = $logger;
61
        $this->userRepo = $userRepo;
62
        $this->api = $api;
0 ignored issues
show
Documentation Bug introduced by
It seems like $api of type object<Skobkin\Bundle\Po...le\Service\Api\UserApi> is incompatible with the declared type object<Api\UserApi> of property $api.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
63
        $this->subscriptionManager = $subscriptionManager;
0 ignored issues
show
Documentation Bug introduced by
It seems like $subscriptionManager of type object<Skobkin\Bundle\Po...e\SubscriptionsManager> is incompatible with the declared type object<SubscriptionsManager> of property $subscriptionManager.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
64
        $this->apiDelay = $apiDelay;
65
        $this->appUserId = $appUserId;
66
    }
67
68
    protected function configure()
69
    {
70
        $this
71
            ->setName('point:update:subscriptions')
72
            ->setDescription('Update subscriptions of users subscribed to service')
73
            ->addOption(
74
                'all-users',
75
                null,
76
                InputOption::VALUE_NONE,
77
                'If set, command will check subscribers of all service users instead of service subscribers only'
78
            )
79
            ->addOption(
80
                'check-only',
81
                null,
82
                InputOption::VALUE_NONE,
83
                'If set, command will not perform write operations in the database'
84
            )
85
        ;
86
    }
87
88
    protected function execute(InputInterface $input, OutputInterface $output)
89
    {
90
        $this->input = $input;
0 ignored issues
show
Documentation Bug introduced by
It seems like $input of type object<Symfony\Component...e\Input\InputInterface> is incompatible with the declared type object<InputInterface> of property $input.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
91
92
        $this->logger->debug('UpdateSubscriptionsCommand started.');
93
94
        $this->progress = new ProgressBar($output);
95
        $this->progress->setFormat('debug');
96
97
        if (!$input->getOption('check-only')) { // Beginning transaction for all changes
98
            $this->em->beginTransaction();
99
        }
100
101
        try {
102
            $usersForUpdate = $this->getUsersForUpdate();
103
        } catch (\Exception $e) {
104
            $this->logger->error('Error while getting service subscribers', ['exception' => get_class($e), 'message' => $e->getMessage()]);
105
106
            return 1;
107
        }
108
109
        if (0 === count($usersForUpdate)) {
110
            $this->logger->info('No local subscribers. Finishing.');
111
112
            return 0;
113
        }
114
115
        $this->logger->info('Processing users subscribers');
116
        $this->progress->start(count($usersForUpdate));
117
118 View Code Duplication
        foreach ($usersForUpdate as $user) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
119
            usleep($this->apiDelay);
120
121
            $this->progress->advance();
122
            $this->logger->info('Processing @'.$user->getLogin());
123
124
            $this->updateUser($user);
125
        }
126
127
        $this->progress->finish();
128
129
        // Flushing all changes at once to the database
130
        if (!$input->getOption('check-only')) {
131
            $this->em->flush();
132
            $this->em->commit();
133
        }
134
135
        $this->logger->debug('Finished');
136
137
        return 0;
138
    }
139
140
    private function updateUser(User $user): void
141
    {
142
        try {
143
            $userCurrentSubscribers = $this->api->getUserSubscribersById($user->getId());
144
        } catch (UserNotFoundException $e) {
145
            $this->logger->warning('User not found. Marking as removed.', ['login' => $user->getLogin(), 'user_id' => $user->getId()]);
146
147
            $user->markAsRemoved();
148
149
            return;
150
        } catch (\Exception $e) {
151
            $this->logger->error(
152
                'Error while getting subscribers. Skipping.',
153
                [
154
                    'user_login' => $user->getLogin(),
155
                    'user_id' => $user->getId(),
156
                    'message' => $e->getMessage(),
157
                    'file' => $e->getFile(),
158
                    'line' => $e->getLine(),
159
                ]
160
            );
161
162
            return;
163
        }
164
165
        $this->logger->debug('Updating user subscribers');
166
167
        try {
168
            // Updating user subscribers
169
            $this->subscriptionManager->updateUserSubscribers($user, $userCurrentSubscribers);
170
        } catch (\Exception $e) {
171
            $this->logger->error(
172
                'Error while updating user subscribers',
173
                [
174
                    'user_login' => $user->getLogin(),
175
                    'user_id' => $user->getId(),
176
                    'message' => $e->getMessage(),
177
                    'file' => $e->getFile(),
178
                    'line' => $e->getLine(),
179
                ]
180
            );
181
        }
182
    }
183
184
    private function getUsersForUpdate(): array
185
    {
186
        $usersForUpdate = [];
187
188
        if ($this->input->getOption('all-users')) {
189
            $usersForUpdate = $this->userRepo->findBy(['removed' => false]);
190
        } else {
191
            /** @var User $serviceUser */
192
            try {
193
                $serviceUser = $this->userRepo->findActiveUserWithSubscribers($this->appUserId);
194
            } catch (\Exception $e) {
195
                $this->logger->error('Error while getting active user with subscribers', ['app_user_id' => $this->appUserId]);
196
197
                throw $e;
198
            }
199
200 View Code Duplication
            if (!$serviceUser) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
201
                $this->logger->warning('Service user not found or marked as removed. Falling back to API.');
202
203
                try {
204
                    $serviceUser = $this->api->getUserById($this->appUserId);
205
                } catch (UserNotFoundException $e) {
206
                    throw new \RuntimeException('Service user not found in the database and could not be retrieved from API.');
207
                }
208
            }
209
210
            $this->logger->info('Getting service subscribers');
211
212
            try {
213
                $usersForUpdate = $this->api->getUserSubscribersById($this->appUserId);
214
            } catch (UserNotFoundException $e) {
215
                $this->logger->critical('Service user deleted or API response is invalid');
216
217
                throw $e;
218
            } catch (\Exception $e) {
219
                $this->logger->warning(
220
                    'Error while getting service subscribers. Fallback to local list.',
221
                    [
222
                        'user_login' => $serviceUser->getLogin(),
223
                        'user_id' => $serviceUser->getId(),
224
                        'message' => $e->getMessage(),
225
                        'file' => $e->getFile(),
226
                        'line' => $e->getLine(),
227
                    ]
228
                );
229
230
                /** @var Subscription $subscription */
231
                foreach ($serviceUser->getSubscribers() as $subscription) {
232
                    $usersForUpdate[] = $subscription->getSubscriber();
233
                }
234
            }
235
236
            $this->logger->debug('Updating service subscribers');
237
238
            // Updating service subscribers
239
            try {
240
                $this->subscriptionManager->updateUserSubscribers($serviceUser, $usersForUpdate);
241
            } catch (\Exception $e) {
242
                $this->logger->error(
243
                    'Error while updating service subscribers',
244
                    [
245
                        'user_login' => $serviceUser->getLogin(),
246
                        'user_id' => $serviceUser->getId(),
247
                        'message' => $e->getMessage(),
248
                        'file' => $e->getFile(),
249
                        'line' => $e->getLine(),
250
                    ]
251
                );
252
253
                throw $e;
254
            }
255
        }
256
257
        return $usersForUpdate;
258
    }
259
}