Test Setup Failed
Push — master ( 013fb3...ad1945 )
by Alexey
13:09
created

UpdateSubscriptionsCommand::updateUser()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 43
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 43
ccs 0
cts 29
cp 0
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 27
nc 4
nop 1
crap 20
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\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
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 ContainerAwareCommand
20
{
21
    /**
22
     * @var EntityManagerInterface
23
     */
24
    private $em;
25
26
    /**
27
     * @var LoggerInterface
28
     */
29
    private $logger;
30
31
    /**
32
     * @var UserRepository
33
     */
34
    private $userRepo;
35
36
    /**
37
     * @var InputInterface
38
     */
39
    private $input;
40
41
    /**
42
     * @var UserApi
43
     */
44
    private $api;
45
46
    /**
47
     * @var int
48
     */
49
    private $apiDelay = 500000;
50
51
    /**
52
     * @var int
53
     */
54
    private $appUserId;
55
56
    /**
57
     * @var SubscriptionsManager
58
     */
59
    private $subscriptionManager;
60
61
    /**
62
     * @var ProgressBar
63
     */
64
    private $progress;
65
66 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...
67
        EntityManagerInterface $em,
68
        LoggerInterface $logger,
69
        UserRepository $userRepo,
70
        UserApi $api,
71
        SubscriptionsManager $subscriptionManager,
72
        int $apiDelay,
73
        int $appUserId
74
    ) {
75
        parent::__construct();
76
77
        $this->em = $em;
78
        $this->logger = $logger;
79
        $this->userRepo = $userRepo;
80
        $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...
81
        $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...
82
        $this->apiDelay = $apiDelay;
83
        $this->appUserId = $appUserId;
84
    }
85
86
    protected function configure()
87
    {
88
        $this
89
            ->setName('point:update:subscriptions')
90
            ->setDescription('Update subscriptions of users subscribed to service')
91
            ->addOption(
92
                'all-users',
93
                null,
94
                InputOption::VALUE_NONE,
95
                'If set, command will check subscribers of all service users instead of service subscribers only'
96
            )
97
            ->addOption(
98
                'check-only',
99
                null,
100
                InputOption::VALUE_NONE,
101
                'If set, command will not perform write operations in the database'
102
            )
103
        ;
104
    }
105
106
    protected function execute(InputInterface $input, OutputInterface $output)
107
    {
108
        $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...
109
110
        $this->logger->debug('UpdateSubscriptionsCommand started.');
111
112
        $this->progress = new ProgressBar($output);
113
        $this->progress->setFormat('debug');
114
115
        if ($input->getOption('check-only')) { // Beginning transaction for all changes
116
            $this->em->beginTransaction();
117
        }
118
119
        try {
120
            $usersForUpdate = $this->getUsersForUpdate();
121
        } catch (\Exception $e) {
122
            $this->logger->error('Error while getting service subscribers', ['exception' => get_class($e), 'message' => $e->getMessage()]);
123
124
            return 1;
125
        }
126
127
        if (0 === count($usersForUpdate)) {
128
            $this->logger->info('No local subscribers. Finishing.');
129
130
            return 0;
131
        }
132
133
        $this->logger->info('Processing users subscribers');
134
        $this->progress->start(count($usersForUpdate));
135
136
        foreach ($usersForUpdate as $user) {
137
            usleep($this->apiDelay);
138
139
            $this->progress->advance();
140
            $this->logger->info('Processing @'.$user->getLogin());
141
142
            $this->updateUser($user);
143
        }
144
145
        $this->progress->finish();
146
147
148
        if ($input->getOption('check-only')) { // Flushing all changes at once to the database
149
            $this->em->flush();
150
            $this->em->commit();
151
        }
152
153
        $this->logger->debug('Finished');
154
155
        return 0;
156
    }
157
158
    private function updateUser(User $user): void
159
    {
160
        try {
161
            $userCurrentSubscribers = $this->api->getUserSubscribersById($user->getId());
162
        } catch (UserNotFoundException $e) {
163
            $this->logger->warning('User not found. Marking as removed.', ['login' => $user->getLogin(), 'user_id' => $user->getId()]);
164
165
            $user->markAsRemoved();
166
167
            return;
168
        } catch (\Exception $e) {
169
            $this->logger->error(
170
                'Error while getting subscribers. Skipping.',
171
                [
172
                    'user_login' => $user->getLogin(),
173
                    'user_id' => $user->getId(),
174
                    'message' => $e->getMessage(),
175
                    'file' => $e->getFile(),
176
                    'line' => $e->getLine(),
177
                ]
178
            );
179
180
            return;
181
        }
182
183
        $this->logger->debug('Updating user subscribers');
184
185
        try {
186
            // Updating user subscribers
187
            $this->subscriptionManager->updateUserSubscribers($user, $userCurrentSubscribers);
188
        } catch (\Exception $e) {
189
            $this->logger->error(
190
                'Error while updating user subscribers',
191
                [
192
                    'user_login' => $user->getLogin(),
193
                    'user_id' => $user->getId(),
194
                    'message' => $e->getMessage(),
195
                    'file' => $e->getFile(),
196
                    'line' => $e->getLine(),
197
                ]
198
            );
199
        }
200
    }
201
202
    private function getUsersForUpdate(): array
203
    {
204
        $usersForUpdate = [];
205
206
        if ($this->input->getOption('all-users')) {
207
            $usersForUpdate = $this->userRepo->findBy(['removed' => false]);
208
        } else {
209
            /** @var User $serviceUser */
210
            try {
211
                $serviceUser = $this->userRepo->findActiveUserWithSubscribers($this->appUserId);
212
            } catch (\Exception $e) {
213
                $this->logger->error('Error while getting active user with subscribers', ['app_user_id' => $appUserId]);
0 ignored issues
show
Bug introduced by
The variable $appUserId does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
214
215
                throw $e;
216
            }
217
218
            if (!$serviceUser) {
219
                $this->logger->critical('Service user not found or marked as removed');
220
                // @todo Retrieving user
221
222
                throw new \RuntimeException('Service user not found in the database');
223
            }
224
225
            $this->logger->info('Getting service subscribers');
226
227
            try {
228
                $usersForUpdate = $this->api->getUserSubscribersById($this->appUserId);
229
            } catch (UserNotFoundException $e) {
230
                $this->logger->critical('Service user deleted or API response is invalid');
231
232
                throw $e;
233
            } catch (\Exception $e) {
234
                $this->logger->warning(
235
                    'Error while getting service subscribers. Fallback to local list.',
236
                    [
237
                        'user_login' => $serviceUser->getLogin(),
238
                        'user_id' => $serviceUser->getId(),
239
                        'message' => $e->getMessage(),
240
                        'file' => $e->getFile(),
241
                        'line' => $e->getLine(),
242
                    ]
243
                );
244
245
                /** @var Subscription $subscription */
246
                foreach ((array) $serviceUser->getSubscribers() as $subscription) {
247
                    $usersForUpdate[] = $subscription->getSubscriber();
248
                }
249
            }
250
251
            $this->logger->debug('Updating service subscribers');
252
253
            // Updating service subscribers
254
            try {
255
                $this->subscriptionManager->updateUserSubscribers($serviceUser, $usersForUpdate);
256
            } catch (\Exception $e) {
257
                $this->logger->error(
258
                    'Error while updating service subscribers',
259
                    [
260
                        'user_login' => $serviceUser->getLogin(),
261
                        'user_id' => $serviceUser->getId(),
262
                        'message' => $e->getMessage(),
263
                        'file' => $e->getFile(),
264
                        'line' => $e->getLine(),
265
                    ]
266
                );
267
268
                throw $e;
269
            }
270
        }
271
272
        return $usersForUpdate;
273
    }
274
}