PrivateMessageProcessor::process()   F
last analyzed

Complexity

Conditions 21
Paths 211

Size

Total Lines 77

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 462

Importance

Changes 0
Metric Value
dl 0
loc 77
ccs 0
cts 49
cp 0
rs 3.1958
c 0
b 0
f 0
cc 21
nc 211
nop 1
crap 462

How to fix   Long Method    Complexity   

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\Service\Telegram;
4
5
use Doctrine\ORM\EntityManagerInterface;
6
use Skobkin\Bundle\PointToolsBundle\Entity\{Telegram\Account, User};
7
use Skobkin\Bundle\PointToolsBundle\Exception\Telegram\CommandProcessingException;
8
use Skobkin\Bundle\PointToolsBundle\Repository\{SubscriptionEventRepository, SubscriptionRepository, Telegram\AccountRepository, UserRepository};
9
use Skobkin\Bundle\PointToolsBundle\Service\Factory\Telegram\AccountFactory;
10
use Skobkin\Bundle\PointToolsBundle\Service\Api\UserApi;
11
use unreal4u\TelegramAPI\Telegram\Types\{Message, ReplyKeyboardMarkup, ReplyKeyboardRemove};
12
13
/** Processes all private messages */
14
class PrivateMessageProcessor
15
{
16
    /** @var MessageSender */
17
    private $messenger;
18
19
    /** @var UserApi */
20
    private $userApi;
21
22
    /** @var AccountFactory */
23
    private $accountFactory;
24
25
    /** @var EntityManagerInterface */
26
    private $em;
27
28
    /** @var UserRepository */
29
    private $userRepo;
30
31
    /** @var AccountRepository */
32
    private $accountRepo;
33
34
    /** @var SubscriptionRepository */
35
    private $subscriptionRepo;
36
37
    /** @var SubscriptionEventRepository */
38
    private $subscriptionEventRepo;
39
40
    /** @var int */
41
    private $pointUserId;
42
43
44
    public function __construct(
45
        EntityManagerInterface $em,
46
        UserRepository $userRepository,
47
        AccountRepository $accountRepository,
48
        SubscriptionRepository $subscriptionRepository,
49
        SubscriptionEventRepository $subscriptionRecordRepository,
50
        MessageSender $messageSender,
51
        UserApi $userApi,
52
        AccountFactory $accountFactory,
53
        int $appUserId
54
    ) {
55
        $this->em = $em;
56
        $this->userRepo = $userRepository;
0 ignored issues
show
Documentation Bug introduced by
It seems like $userRepository of type object<Skobkin\Bundle\Po...ository\UserRepository> is incompatible with the declared type object<UserRepository> of property $userRepo.

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...
57
        $this->accountRepo = $accountRepository;
0 ignored issues
show
Documentation Bug introduced by
It seems like $accountRepository of type object<Skobkin\Bundle\Po...gram\AccountRepository> is incompatible with the declared type object<Telegram\AccountRepository> of property $accountRepo.

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...
58
        $this->subscriptionRepo = $subscriptionRepository;
0 ignored issues
show
Documentation Bug introduced by
It seems like $subscriptionRepository of type object<Skobkin\Bundle\Po...SubscriptionRepository> is incompatible with the declared type object<SubscriptionRepository> of property $subscriptionRepo.

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...
59
        $this->subscriptionEventRepo = $subscriptionRecordRepository;
0 ignored issues
show
Documentation Bug introduced by
It seems like $subscriptionRecordRepository of type object<Skobkin\Bundle\Po...riptionEventRepository> is incompatible with the declared type object<SubscriptionEventRepository> of property $subscriptionEventRepo.

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...
60
        $this->messenger = $messageSender;
61
        $this->userApi = $userApi;
62
        $this->accountFactory = $accountFactory;
63
        $this->pointUserId = $appUserId;
64
    }
65
66
    public function process(Message $message): void
67
    {
68
        if (!IncomingUpdateDispatcher::CHAT_TYPE_PRIVATE === $message->chat->type) {
69
            throw new \LogicException('This service can process only private chat messages');
70
        }
71
72
        try {
73
            // Registering Telegram user
74
            /** @var Account $account */
75
            $account = $this->accountFactory->findOrCreateFromMessage($message);
76
            $this->em->flush();
77
        } catch (\Exception $e) {
78
            // Low-level message in case of incorrect $account
79
            $this->messenger->sendMessageToChat($message->chat->id, 'There was an error during your Telegram account registration. Try again or report the bug.');
80
        }
81
82
        try {
83
            $words = explode(' ', $message->text, 10);
84
85
            if (0 === count($words)) {
86
                return;
87
            }
88
89
            switch ($words[0]) {
90
                case '/link':
91
                case 'link':
92
                    $this->processLink($account, $words);
93
                    break;
94
95
                case '/me':
96
                case 'me':
97
                    $this->processMe($account);
98
                    break;
99
100
                case '/last':
101
                case 'last':
102
                    $this->processLast($account, $words);
103
                    break;
104
105
                case '/sub':
106
                case 'sub':
107
                    $this->processSub($account, $words);
108
                    break;
109
110
                case '/stats':
111
                case 'stats':
112
                    $this->processStats($account);
113
                    break;
114
115
                // Settings menu
116
                case '/set':
117
                    $this->processSet($account, $words);
118
                    break;
119
120
                // Exit from any menu and remove keyboard
121
                case '/exit':
122
                case 'exit':
123
                    $this->processExit($account);
124
                    break;
125
126
                case '/help':
127
                default:
128
                    $this->processHelp($account);
129
                    break;
130
            }
131
        } catch (CommandProcessingException $e) {
132
            $this->sendError($account, 'Processing error', $e->getMessage());
133
134
            if ($e->getPrevious()) {
135
                throw $e->getPrevious();
136
            }
137
        } catch (\Exception $e) {
138
            $this->sendError($account, 'Unknown error');
139
140
            throw $e;
141
        }
142
    }
143
144
    private function linkAccount(Account $account, string $login, string $password): bool
145
    {
146
        /** @var User $user */
147
        if (null === $user = $this->userRepo->findUserByLogin($login)) {
148
            throw new CommandProcessingException('User not found in Point Tools database. Please try again later.');
149
        }
150
151
        if ($this->userApi->isLoginAndPasswordValid($login, $password)) {
152
            $account->setUser($user);
153
154
            return true;
155
        }
156
157
        return false;
158
    }
159
160
    private function processLink(Account $account, array $words): void
161
    {
162
        if (array_key_exists(2, $words)) {
163
            if ($this->linkAccount($account, $words[1], $words[2])) {
164
                // Saving linking status
165
                $this->em->flush();
166
                $this->sendAccountLinked($account);
167
            } else {
168
                $this->sendError($account, 'Account linking error', 'Check login and password or try again later.');
169
            }
170
        } else {
171
            $this->sendError($account, 'Login/Password error', 'You need to specify login and password separated by space after /link (example: `/link mylogin MypASSw0rd`)');
172
        }
173
    }
174
175
    private function processMe(Account $account): void
176
    {
177
        if ($user = $account->getUser()) {
178
            $this->sendUserEvents($account, $user);
179
        } else {
180
            $this->sendError($account, 'Account not linked', 'You must /link your account first to be able to use this command.');
181
        }
182
    }
183
184
    private function processLast(Account $account, array $words): void
185
    {
186
        if (array_key_exists(1, $words)) {
187 View Code Duplication
            if (null !== $user = $this->userRepo->findUserByLogin($words[1])) {
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...
188
                $this->sendUserEvents($account, $user);
189
            } else {
190
                $this->sendError($account, 'User not found');
191
            }
192
        } else {
193
            $this->sendGlobalEvents($account);
194
        }
195
    }
196
197
    private function processSub(Account $account, array $words): void
198
    {
199
        if (array_key_exists(1, $words)) {
200 View Code Duplication
            if (null !== $user = $this->userRepo->findUserByLogin($words[1])) {
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->sendUserSubscribers($account, $user);
202
            } else {
203
                $this->sendError($account, 'User not found');
204
            }
205
        } else {
206
            if ($user = $account->getUser()) {
207
                $this->sendUserSubscribers($account, $user);
208
            } else {
209
                $this->sendError($account, 'Account not linked', 'You must /link your account first to be able to use this command.');
210
            }
211
        }
212
    }
213
214
    private function processStats(Account $account): void
215
    {
216
        $this->sendStats($account);
217
    }
218
219
    private function processSet(Account $account, array $words): void
220
    {
221
        $keyboard = new ReplyKeyboardMarkup();
222
223
        if (array_key_exists(1, $words)) {
224
            if (array_key_exists(2, $words)) {
225
                if ('renames' === $words[2]) {
226
                    $account->toggleRenameNotification();
227
                    $this->em->flush();
228
229
                    $this->messenger->sendMessage($account, 'Renaming notifications are turned '.($account->isRenameNotification() ? 'on' : 'off'));
230
                } elseif ('subscribers' === $words[2]) {
231
                    $account->toggleSubscriberNotification();
232
                    $this->em->flush();
233
234
                    $this->messenger->sendMessage($account, 'Subscribers notifications are turned '.($account->isSubscriberNotification() ? 'on' : 'off'));
235
236
                    if ($account->isSubscriberNotification() && null === $account->getUser()) {
237
                        $this->messenger->sendMessage($account, 'You need to /link you account to receive these notifications.');
238
                    }
239
                } else {
240
                    $this->sendError($account, 'Notification type does not exist.');
241
                }
242
            } else {
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...
243
                $keyboard->keyboard = [
244
                    ['/set notifications renames'],
245
                    ['/set notifications subscribers'],
246
                    ['exit'],
247
                ];
248
249
                $this->messenger->sendMessage($account, 'Choose which notification type to toggle', MessageSender::PARSE_PLAIN, $keyboard);
250
            }
251
252
        } else {
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...
253
            $keyboard->keyboard = [
254
                ['/set notifications'],
255
                ['exit'],
256
            ];
257
258
            $this->messenger->sendTemplatedMessage($account, '@SkobkinPointTools/Telegram/settings.md.twig', ['account' => $account], $keyboard);
259
        }
260
    }
261
262
    /**
263
     * Processes exit from keyboard menus and removes the keyboard
264
     */
265
    private function processExit(Account $account): void
266
    {
267
        $keyboardRemove = new ReplyKeyboardRemove();
268
269
        $this->messenger->sendMessage($account, 'Done', MessageSender::PARSE_PLAIN, $keyboardRemove);
270
    }
271
272
    private function processHelp(Account $account): void
273
    {
274
        $this->sendHelp($account);
275
    }
276
277
    private function sendAccountLinked(Account $account): void
278
    {
279
        $this->messenger->sendMessage($account, 'Account linked. Try using /me now.');
280
    }
281
282
    private function sendUserSubscribers(Account $account, User $user): void
283
    {
284
        $subscribers = [];
285
        foreach ($user->getSubscribers() as $subscription) {
286
            $subscribers[] = '@'.$subscription->getSubscriber()->getLogin();
287
        }
288
289
        $this->messenger->sendTemplatedMessage(
290
            $account,
291
            '@SkobkinPointTools/Telegram/user_subscribers.md.twig',
292
            [
293
                'user' => $user,
294
                'subscribers' => $subscribers,
295
            ]
296
        );
297
    }
298
299
    private function sendUserEvents(Account $account, User $user): void
300
    {
301
        $events = $this->subscriptionEventRepo->getUserLastSubscribersEvents($user, 10);
302
303
        $this->messenger->sendTemplatedMessage(
304
            $account,
305
            '@SkobkinPointTools/Telegram/last_user_subscriptions.md.twig',
306
            [
307
                'user' => $user,
308
                'events' => $events,
309
            ]
310
        );
311
    }
312
313
    private function sendGlobalEvents(Account $account): void
314
    {
315
        $events = $this->subscriptionEventRepo->getLastSubscriptionEvents(10);
316
317
        $this->messenger->sendTemplatedMessage($account, '@SkobkinPointTools/Telegram/last_global_subscriptions.md.twig', ['events' => $events]);
318
    }
319
320
    private function sendStats(Account $account): void
321
    {
322
        $this->messenger->sendTemplatedMessage(
323
            $account,
324
            '@SkobkinPointTools/Telegram/stats.md.twig',
325
            [
326
                'total_users' => $this->userRepo->getUsersCount(),
327
                'active_users' => $this->subscriptionRepo->getUserSubscribersCountById($this->pointUserId),
328
                'telegram_accounts' => $this->accountRepo->getAccountsCount(),
329
                'telegram_linked_accounts' => $this->accountRepo->getLinkedAccountsCount(),
330
                'today_events' => $this->subscriptionEventRepo->getLastDayEventsCount(),
331
            ]
332
        );
333
    }
334
335
    private function sendHelp(Account $account): void
336
    {
337
        $this->messenger->sendTemplatedMessage($account, '@SkobkinPointTools/Telegram/help.md.twig');
338
    }
339
340
    private function sendError(Account $account, string $title, string $text = ''): void
341
    {
342
        $this->messenger->sendTemplatedMessage(
343
            $account,
344
            '@SkobkinPointTools/Telegram/error.md.twig',
345
            [
346
                'title' => $title,
347
                'text' => $text,
348
            ]
349
        );
350
    }
351
352
353
}