Test Setup Failed
Push — master ( 76b332...c83833 )
by Alexey
03:28
created

UpdateSubscriptionsCommand::getUsersForUpdate()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 72
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 72
ccs 0
cts 40
cp 0
rs 6.3883
cc 8
eloc 42
nc 8
nop 1
crap 72

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;
8
use Skobkin\Bundle\PointToolsBundle\Entity\User;
9
use Skobkin\Bundle\PointToolsBundle\Exception\Api\UserNotFoundException;
10
use Skobkin\Bundle\PointToolsBundle\Repository\UserRepository;
11
use Skobkin\Bundle\PointToolsBundle\Service\SubscriptionsManager;
12
use Skobkin\Bundle\PointToolsBundle\Service\Api\UserApi;
13
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
14
use Symfony\Component\Console\Helper\ProgressBar;
15
use Symfony\Component\Console\Input\InputInterface;
16
use Symfony\Component\Console\Input\InputOption;
17
use Symfony\Component\Console\Output\OutputInterface;
18
19
/**
20
 * @todo https://symfony.com/doc/current/console/lockable_trait.html
21
 */
22
class UpdateSubscriptionsCommand extends ContainerAwareCommand
23
{
24
    /**
25
     * @var EntityManagerInterface
26
     */
27
    private $em;
28
29
    /**
30
     * @var LoggerInterface
31
     */
32
    private $logger;
33
34
    /**
35
     * @var UserRepository
36
     */
37
    private $userRepo;
38
39
    /**
40
     * @var InputInterface
41
     */
42
    private $input;
43
44
    /**
45
     * @var UserApi
46
     */
47
    private $api;
48
49
    /**
50
     * @var int
51
     */
52
    private $apiDelay = 500000;
53
54
    /**
55
     * @var SubscriptionsManager
56
     */
57
    private $subscriptionManager;
58
59
    /**
60
     * @var ProgressBar
61
     */
62
    private $progress;
63
64
65
    public function setLogger(LoggerInterface $logger): void
66
    {
67
        $this->logger = $logger;
68
    }
69
70
    public function setEntityManager(EntityManagerInterface $em): void
71
    {
72
        $this->em = $em;
73
    }
74
75
    public function setUserRepository(UserRepository $repository): void
76
    {
77
        $this->userRepo = $repository;
78
    }
79
80
    public function setApiClient(UserApi $userApi): void
81
    {
82
        $this->api = $userApi;
83
    }
84
85
    public function setApiDelay(int $microSecs): void
86
    {
87
        $this->apiDelay = $microSecs;
88
    }
89
90
    public function setSubscriptionManager(SubscriptionsManager $subscriptionsManager): void
91
    {
92
        $this->subscriptionManager = $subscriptionsManager;
93
    }
94
95
    protected function configure()
96
    {
97
        $this
98
            ->setName('point:update:subscriptions')
99
            ->setDescription('Update subscriptions of users subscribed to service')
100
            ->addOption(
101
                'all-users',
102
                null,
103
                InputOption::VALUE_NONE,
104
                'If set, command will check subscribers of all service users instead of service subscribers only'
105
            )
106
            ->addOption(
107
                'check-only',
108
                null,
109
                InputOption::VALUE_NONE,
110
                'If set, command will not perform write operations in the database'
111
            )
112
            // @todo add option for checking only selected user
113
        ;
114
    }
115
116
    /**
117
     * @param InputInterface $input
118
     * @param OutputInterface $output
119
     *
120
     * @return int
121
     */
122
    protected function execute(InputInterface $input, OutputInterface $output)
123
    {
124
        $this->input = $input;
125
126
        $this->logger->debug('UpdateSubscriptionsCommand started.');
127
128
        try {
129
            $appUserId = $this->getContainer()->getParameter('point_id');
130
        } catch (\InvalidArgumentException $e) {
131
            $this->logger->alert('Could not get point_id parameter from config file', ['exception_message' => $e->getMessage()]);
132
            return 1;
133
        }
134
135
        $this->progress = new ProgressBar($output);
136
        $this->progress->setFormat('debug');
137
138
        // Beginning transaction for all changes
139
        $this->em->beginTransaction();
140
141
        $this->progress->setMessage('Getting service subscribers');
142
143
        try {
144
            $usersForUpdate = $this->getUsersForUpdate($appUserId);
145
        } catch (\Exception $e) {
146
            $this->logger->error('Error while getting service subscribers', ['exception' => get_class($e), 'message' => $e->getMessage()]);
147
148
            return 1;
149
        }
150
151
        if (0 === count($usersForUpdate)) {
152
            $this->logger->info('No local subscribers. Finishing.');
153
154
            return 0;
155
        }
156
157
        $this->logger->info('Processing users subscribers');
158
        $this->progress->setMessage('Processing users subscribers');
159
        $this->progress->start(count($usersForUpdate));
160
161
        $this->updateUsersSubscribers($usersForUpdate);
162
163
        $this->progress->finish();
164
165
        // Flushing all changes at once to database
166
        $this->em->flush();
167
        $this->em->commit();
168
169
        $this->logger->debug('Finished');
170
171
        return 0;
172
    }
173
174
    /**
175
     * @param User[] $users
176
     */
177
    private function updateUsersSubscribers(array $users): void
178
    {
179
        // Updating users subscribers
180
        foreach ($users as $user) {
181
            usleep($this->apiDelay);
182
183
            $this->progress->advance();
184
185
            $this->logger->info('Processing @'.$user->getLogin());
186
187
            try {
188
                $userCurrentSubscribers = $this->api->getUserSubscribersById($user->getId());
189
            } catch (UserNotFoundException $e) {
190
                $this->logger->warning('User not found. Marking as removed', ['login' => $user->getLogin(), 'user_id' => $user->getId()]);
191
                $user->markAsRemoved();
192
193
                continue;
194
            } catch (\Exception $e) {
195
                $this->logger->error(
196
                    'Error while getting subscribers. Skipping.',
197
                    [
198
                        'user_login' => $user->getLogin(),
199
                        'user_id' => $user->getId(),
200
                        'message' => $e->getMessage(),
201
                        'file' => $e->getFile(),
202
                        'line' => $e->getLine(),
203
                    ]
204
                );
205
206
                continue;
207
            }
208
209
            $this->logger->debug('Updating user subscribers');
210
211
            try {
212
                // Updating user subscribers
213
                $this->subscriptionManager->updateUserSubscribers($user, $userCurrentSubscribers);
214
            } catch (\Exception $e) {
215
                $this->logger->error(
216
                    'Error while updating user subscribers',
217
                    [
218
                        'user_login' => $user->getLogin(),
219
                        'user_id' => $user->getId(),
220
                        'message' => $e->getMessage(),
221
                        'file' => $e->getFile(),
222
                        'line' => $e->getLine(),
223
                    ]
224
                );
225
            }
226
        }
227
    }
228
229
    private function getUsersForUpdate(int $appUserId): array
230
    {
231
        $usersForUpdate = [];
232
233
        if ($this->input->getOption('all-users')) {
234
            $usersForUpdate = $this->userRepo->findBy(['removed' => false]);
235
        } else {
236
            /** @var User $serviceUser */
237
            try {
238
                $serviceUser = $this->userRepo->findActiveUserWithSubscribers($appUserId);
239
            } catch (\Exception $e) {
240
                $this->logger->error('Error while getting active user with subscribers', ['app_user_id' => $appUserId]);
241
242
                throw $e;
243
            }
244
245
            if (!$serviceUser) {
246
                $this->logger->critical('Service user not found or marked as removed');
247
                // @todo Retrieving user
248
249
                throw new \RuntimeException('Service user not found in the database');
250
            }
251
252
            $this->logger->info('Getting service subscribers');
253
254
            try {
255
                $usersForUpdate = $this->api->getUserSubscribersById($appUserId);
256
            } catch (UserNotFoundException $e) {
257
                $this->logger->critical('Service user deleted or API response is invalid');
258
259
                throw $e;
260
            } catch (\Exception $e) {
261
                $this->logger->warning(
262
                    'Error while getting service subscribers. Fallback to local list.',
263
                    [
264
                        'user_login' => $serviceUser->getLogin(),
265
                        'user_id' => $serviceUser->getId(),
266
                        'message' => $e->getMessage(),
267
                        'file' => $e->getFile(),
268
                        'line' => $e->getLine(),
269
                    ]
270
                );
271
272
                /** @var Subscription $subscription */
273
                foreach ((array) $serviceUser->getSubscribers() as $subscription) {
274
                    $usersForUpdate[] = $subscription->getSubscriber();
275
                }
276
            }
277
278
            $this->logger->debug('Updating service subscribers');
279
280
            // Updating service subscribers
281
            try {
282
                $this->subscriptionManager->updateUserSubscribers($serviceUser, $usersForUpdate);
283
            } catch (\Exception $e) {
284
                $this->logger->error(
285
                    'Error while updating service subscribers',
286
                    [
287
                        'user_login' => $serviceUser->getLogin(),
288
                        'user_id' => $serviceUser->getId(),
289
                        'message' => $e->getMessage(),
290
                        'file' => $e->getFile(),
291
                        'line' => $e->getLine(),
292
                    ]
293
                );
294
295
                throw $e;
296
            }
297
        }
298
299
        return $usersForUpdate;
300
    }
301
}