NotifyCommand::printNotifyResult()   A
last analyzed

Complexity

Conditions 5
Paths 6

Size

Total Lines 28
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 5

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 17
c 2
b 0
f 0
dl 0
loc 28
ccs 18
cts 18
cp 1
rs 9.3888
cc 5
nc 6
nop 2
crap 5
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Chemaclass\StockTicker\Infrastructure\Command;
6
7
use Chemaclass\StockTicker\Domain\Notifier\Channel\Email\EmailChannel;
8
use Chemaclass\StockTicker\Domain\Notifier\Channel\Fake\FakeChannel;
9
use Chemaclass\StockTicker\Domain\Notifier\Channel\Slack\SlackChannel;
10
use Chemaclass\StockTicker\Domain\Notifier\NotifierPolicy;
11
use Chemaclass\StockTicker\Domain\Notifier\NotifyResult;
12
use Chemaclass\StockTicker\Domain\Notifier\Policy\Condition\RecentNewsWasFound;
13
use Chemaclass\StockTicker\Domain\Notifier\Policy\PolicyGroup;
14
use Chemaclass\StockTicker\Infrastructure\Command\ReadModel\NotifyCommandInput;
15
use Chemaclass\StockTicker\StockTickerFacade;
16
use Symfony\Component\Console\Command\Command;
17
use Symfony\Component\Console\Input\InputArgument;
18
use Symfony\Component\Console\Input\InputInterface;
19
use Symfony\Component\Console\Output\OutputInterface;
20
21
final class NotifyCommand extends Command
22
{
23
    private const DEFAULT_MAX_NEWS_PER_CRAWLED_SITE = 3;
24
    private const DEFAULT_MAX_REPETITIONS = PHP_INT_MAX;
25
    private const DEFAULT_SLEEPING_TIME_IN_SECONDS = 60;
26
    private const DEFAULT_CHANNEL = 'email';
27
28
    private const MAP_POSSIBLE_CHANNELS = [
29
        'email' => EmailChannel::class,
30
        'slack' => SlackChannel::class,
31
        'fake' => FakeChannel::class,
32
    ];
33
34 2
    protected function configure(): void
35
    {
36
        $this
37 2
            ->setName('notify')
38 2
            ->setDescription('It notifies based on conditions applied to the crawled result.')
39 2
            ->setHelp('Example: php bin/console --channels=email --maxNews=3 --maxRepetitions=10 --sleepingTime=300 YM GC EA')
40 2
            ->addArgument(
41 2
                'symbols',
42 2
                InputArgument::IS_ARRAY|InputArgument::REQUIRED,
43 2
                'Which stock symbols do you want to crawl?',
44
            )
45 2
            ->addOption(
46 2
                'channels',
47 2
                'c',
48 2
                InputArgument::OPTIONAL,
49 2
                'Channels to notify separated by comma [email,slack]',
50 2
                self::DEFAULT_CHANNEL,
51
            )
52 2
            ->addOption(
53 2
                'maxNews',
54 2
                'm',
55 2
                InputArgument::OPTIONAL,
56 2
                'Max number of news to fetch per crawled site',
57 2
                self::DEFAULT_MAX_NEWS_PER_CRAWLED_SITE,
58
            )
59 2
            ->addOption(
60 2
                'maxRepetitions',
61 2
                'r',
62 2
                InputArgument::OPTIONAL,
63 2
                'Max number repetitions for the loop',
64 2
                self::DEFAULT_MAX_REPETITIONS,
65
            )
66 2
            ->addOption(
67 2
                'sleepingTime',
68 2
                's',
69 2
                InputArgument::OPTIONAL,
70 2
                'Sleeping time in seconds',
71 2
                self::DEFAULT_SLEEPING_TIME_IN_SECONDS,
72
            );
73 2
    }
74
75 2
    protected function execute(InputInterface $input, OutputInterface $output): int
76
    {
77 2
        $commandInput = NotifyCommandInput::createFromInput($input);
78 2
        $channelNames = $this->mapChannelNames($commandInput->getChannelsAsString());
79
80 2
        $policy = $this->createPolicyForSymbols($commandInput->getSymbols());
81 2
        $facade = new StockTickerFacade();
82
83 2
        for ($i = 0; $i < $commandInput->getMaxRepetitions(); ++$i) {
84 2
            $this->printStartingIteration($output, $commandInput, $i);
85
86 2
            $notifyResult = $facade->sendNotifications(
87 2
                $channelNames,
88
                $policy,
89 2
                $commandInput->getMaxNews(),
90
            );
91
92 2
            $this->printNotifyResult($output, $notifyResult);
93 2
            $this->sleepWithPrompt($output, $commandInput->getSleepingTime());
94
        }
95
96 2
        $output->writeln('Already reached the max repetition limit of ' . $commandInput->getMaxRepetitions());
97
98 2
        return Command::SUCCESS;
99
    }
100
101 2
    private function mapChannelNames(string $channelsAsString): array
102
    {
103 2
        return array_filter(array_map(
104 2
            static fn (string $c): string => self::MAP_POSSIBLE_CHANNELS[$c] ?? '',
105 2
            explode(',', $channelsAsString),
106
        ));
107
    }
108
109
    /**
110
     * Create the same policy group for all symbols.
111
     *
112
     * @param string[] $symbols
113
     */
114 2
    private function createPolicyForSymbols(array $symbols): NotifierPolicy
115
    {
116 2
        $conditions = array_fill_keys($symbols, new PolicyGroup([
117 2
            'Recent news was found' => new RecentNewsWasFound(),
118
        ]));
119
120 2
        return new NotifierPolicy($conditions);
121
    }
122
123 2
    private function printStartingIteration(
124
        OutputInterface $output,
125
        NotifyCommandInput $commandInput,
126
        int $actualIteration,
127
    ): void {
128 2
        $output->writeln(sprintf('<comment>Looking for news in %s</comment>', implode(', ', $commandInput->getSymbols())));
129 2
        $output->writeln(sprintf('<comment>Completed %d of %d</comment>', $actualIteration, $commandInput->getMaxRepetitions()));
130 2
    }
131
132 2
    private function printNotifyResult(OutputInterface $output, NotifyResult $notifyResult): void
133
    {
134 2
        if ($notifyResult->isEmpty()) {
135 1
            $output->writeln('<question>~~~~ Nothing new here ~~~~</question>');
136
137 1
            return;
138
        }
139
140 1
        $output->writeln('===========================');
141 1
        $output->writeln('====== Notify result ======');
142 1
        $output->writeln('===========================');
143
144 1
        foreach ($notifyResult->conditionNamesGroupBySymbol() as $symbol => $conditionNames) {
145 1
            $quote = $notifyResult->quoteBySymbol($symbol);
146
147 1
            $companyName = ($quote->getCompanyName() !== null)
148 1
                ? $quote->getCompanyName()->getLongName() ?? ''
149 1
                : '';
150
151 1
            $symbol = $quote->getSymbol() ?? '';
152 1
            $output->writeln(sprintf('<options=bold,underscore>%s</> (%s)', $companyName, $symbol));
153
154 1
            foreach ($conditionNames as $conditionName) {
155 1
                $output->writeln("  - <info>{$conditionName}</info>");
156
            }
157 1
            $output->writeln('');
158
        }
159 1
        $output->writeln('');
160 1
    }
161
162 2
    private function sleepWithPrompt(OutputInterface $output, int $sec): void
163
    {
164 2
        $text = "<comment>Sleeping {$sec} seconds</comment>";
165 2
        $len = mb_strlen((string) $sec);
166
167 2
        for ($i = $sec; $i > 0; --$i) {
168
            $output->write(sprintf("{$text}: %0{$len}d\r", $i));
169
            sleep(1);
170
        }
171
172 2
        $output->writeln('');
173 2
        $output->writeln('<info>Awake again!</info>');
174 2
    }
175
}
176