Passed
Push — master ( fa7644...a6ac37 )
by Alexey
03:26
created

UpdateSubscriptionsCommand::execute()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 43
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 3
Bugs 1 Features 0
Metric Value
c 3
b 1
f 0
dl 0
loc 43
ccs 0
cts 29
cp 0
rs 8.5806
cc 4
eloc 24
nc 4
nop 2
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;
8
use Skobkin\Bundle\PointToolsBundle\Entity\User;
9
use Skobkin\Bundle\PointToolsBundle\Repository\UserRepository;
10
use Skobkin\Bundle\PointToolsBundle\Service\SubscriptionsManager;
11
use Skobkin\Bundle\PointToolsBundle\Service\UserApi;
12
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
13
use Symfony\Component\Console\Input\InputInterface;
14
use Symfony\Component\Console\Input\InputOption;
15
use Symfony\Component\Console\Output\OutputInterface;
16
17
class UpdateSubscriptionsCommand extends ContainerAwareCommand
18
{
19
    /**
20
     * @var EntityManagerInterface
21
     */
22
    private $em;
23
24
    /**
25
     * @var LoggerInterface
26
     */
27
    private $logger;
28
29
    /**
30
     * @var UserRepository
31
     */
32
    private $userRepo;
33
34
    /**
35
     * @var InputInterface
36
     */
37
    private $input;
38
39
    /**
40
     * @var UserApi
41
     */
42
    private $api;
43
44
    /**
45
     * @var int
46
     */
47
    private $apiDelay = 500000;
48
49
    /**
50
     * @var SubscriptionsManager
51
     */
52
    private $subscriptionManager;
53
54
55
    public function setLogger(LoggerInterface $logger)
56
    {
57
        $this->logger = $logger;
58
    }
59
60
    public function setEntityManager(EntityManagerInterface $em)
61
    {
62
        $this->em = $em;
63
    }
64
65
    public function setApiClient(UserApi $userApi)
66
    {
67
        $this->api = $userApi;
68
    }
69
70
    public function setApiDelay(int $microSecs)
71
    {
72
        $this->apiDelay = $microSecs;
73
    }
74
75
    public function setSubscriptionManager(SubscriptionsManager $subscriptionsManager)
76
    {
77
        $this->subscriptionManager = $subscriptionsManager;
78
    }
79
80
    protected function configure()
81
    {
82
        $this
83
            ->setName('point:update:subscriptions')
84
            ->setDescription('Update subscriptions of users subscribed to service')
85
            ->addOption(
86
                'all-users',
87
                null,
88
                InputOption::VALUE_NONE,
89
                'If set, command will check subscribers of all service users instead of service subscribers only'
90
            )
91
            ->addOption(
92
                'check-only',
93
                null,
94
                InputOption::VALUE_NONE,
95
                'If set, command will not perform write operations in the database'
96
            )
97
            // @todo add option for checking only selected user
98
        ;
99
    }
100
101
    /**
102
     * @param InputInterface $input
103
     * @param OutputInterface $output
104
     *
105
     * @return bool
106
     */
107
    protected function execute(InputInterface $input, OutputInterface $output)
108
    {
109
        $this->input = $input;
110
        $this->userRepo = $this->em->getRepository('SkobkinPointToolsBundle:User');
111
112
        $this->logger->debug('UpdateSubscriptionsCommand started.');
113
114
        try {
115
            $appUserId = $this->getContainer()->getParameter('point_id');
116
        } catch (\InvalidArgumentException $e) {
117
            $this->logger->alert('Could not get point_id parameter from config file', ['exception_message' => $e->getMessage()]);
118
            return 1;
119
        }
120
121
        // Beginning transaction for all changes
122
        $this->em->beginTransaction();
123
124
        try {
125
            $usersForUpdate = $this->getUsersForUpdate($appUserId);
126
        } catch (\Exception $e) {
127
            $this->logger->error('Error while getting service subscribers', ['exception' => get_class($e), 'message' => $e->getMessage()]);
128
129
            return 1;
130
        }
131
132
        if (0 === count($usersForUpdate)) {
133
            $this->logger->info('No local subscribers. Finishing.');
134
135
            return 0;
136
        }
137
138
        $this->logger->info('Processing users subscribers');
139
140
        $this->updateUsersSubscribers($usersForUpdate);
141
142
        // Flushing all changes at once to database
143
        $this->em->flush();
144
        $this->em->commit();
145
146
        $this->logger->debug('Finished');
147
148
        return 0;
149
    }
150
151
    /**
152
     * @param User[] $users
153
     */
154
    private function updateUsersSubscribers(array $users)
155
    {
156
        // Updating users subscribers
157
        foreach ($users as $user) {
158
            $this->logger->info('Processing @'.$user->getLogin());
159
160
            try {
161
                $userCurrentSubscribers = $this->api->getUserSubscribersById($user->getId());
162
            } catch (\Exception $e) {
163
                $this->logger->error(
164
                    'Error while getting subscribers. Skipping.',
165
                    [
166
                        'user_login' => $user->getLogin(),
167
                        'user_id' => $user->getId(),
168
                        'message' => $e->getMessage(),
169
                        'file' => $e->getFile(),
170
                        'line' => $e->getLine(),
171
                    ]
172
                );
173
174
                continue;
175
            }
176
177
            $this->logger->debug('Updating user subscribers');
178
179
            try {
180
                // Updating user subscribers
181
                $this->subscriptionManager->updateUserSubscribers($user, $userCurrentSubscribers);
182
            } catch (\Exception $e) {
183
                $this->logger->error(
184
                    'Error while updating user subscribers',
185
                    [
186
                        'user_login' => $user->getLogin(),
187
                        'user_id' => $user->getId(),
188
                        'message' => $e->getMessage(),
189
                        'file' => $e->getFile(),
190
                        'line' => $e->getLine(),
191
                    ]
192
                );
193
            }
194
195
            usleep($this->apiDelay);
196
        }
197
    }
198
199
    private function getUsersForUpdate(int $appUserId): array
200
    {
201
        if ($this->input->getOption('all-users')) {
202
            $usersForUpdate = $this->userRepo->findAll();
203
        } else {
204
            /** @var User $serviceUser */
205
            $serviceUser = $this->userRepo->find($appUserId);
206
207
            if (!$serviceUser) {
208
                $this->logger->info('Service user not found');
209
                // @todo Retrieving user
210
211
                throw new \RuntimeException('Service user not found in the database');
212
            }
213
214
            $this->logger->info('Getting service subscribers');
215
216
            try {
217
                $usersForUpdate = $this->api->getUserSubscribersById($appUserId);
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
                $usersForUpdate = [];
231
232
                /** @var Subscription $subscription */
233
                foreach ($serviceUser->getSubscribers() as $subscription) {
0 ignored issues
show
Bug introduced by
The expression $serviceUser->getSubscribers() of type object<Skobkin\Bundle\Po...ctions\ArrayCollection> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
234
                    $usersForUpdate[] = $subscription->getSubscriber();
235
                }
236
            }
237
238
            $this->logger->debug('Updating service subscribers');
239
240
            // Updating service subscribers
241
            try {
242
                $this->subscriptionManager->updateUserSubscribers($serviceUser, $usersForUpdate);
243
            } catch (\Exception $e) {
244
                $this->logger->error(
245
                    'Error while updating service subscribers',
246
                    [
247
                        'user_login' => $serviceUser->getLogin(),
248
                        'user_id' => $serviceUser->getId(),
249
                        'message' => $e->getMessage(),
250
                        'file' => $e->getFile(),
251
                        'line' => $e->getLine(),
252
                    ]
253
                );
254
255
                throw $e;
256
            }
257
        }
258
259
        return $usersForUpdate;
260
    }
261
}