Passed
Push — master ( e879f4...f6cdcb )
by Chema
03:02
created

NotifyCommand::configure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 37
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 1

Importance

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