ServeCommand::execute()   A
last analyzed

Complexity

Conditions 3
Paths 12

Size

Total Lines 32
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 21
c 1
b 0
f 0
nc 12
nop 2
dl 0
loc 32
rs 9.584
ccs 0
cts 19
cp 0
crap 12
1
<?php
2
3
namespace BenTools\MercurePHP\Command;
4
5
use BenTools\MercurePHP\Configuration\Configuration;
6
use BenTools\MercurePHP\Hub\HubFactory;
7
use BenTools\MercurePHP\Hub\HubFactoryInterface;
8
use Psr\Log\LoggerInterface;
9
use Psr\Log\LogLevel;
10
use React\EventLoop\Factory;
11
use React\EventLoop\LoopInterface;
12
use Symfony\Component\Console\Command\Command;
13
use Symfony\Component\Console\Input\InputInterface;
14
use Symfony\Component\Console\Input\InputOption;
15
use Symfony\Component\Console\Logger\ConsoleLogger;
16
use Symfony\Component\Console\Output\OutputInterface;
17
use Symfony\Component\Console\Style\SymfonyStyle;
18
19
use function BenTools\MercurePHP\nullify;
20
use function BenTools\MercurePHP\without_nullish_values;
21
22
final class ServeCommand extends Command
23
{
24
    protected static $defaultName = 'mercure:serve';
25
26
    private Configuration $configuration;
27
    private HubFactoryInterface $factory;
28
    private LoopInterface $loop;
29
    private ?LoggerInterface $logger;
30
31
    public function __construct(
32
        Configuration $configuration,
33
        HubFactoryInterface $factory,
34
        LoopInterface $loop,
35
        ?LoggerInterface $logger = null
36
    ) {
37
        parent::__construct();
38
        $this->configuration = $configuration;
39
        $this->factory = $factory;
40
        $this->loop = $loop;
41
        $this->logger = $logger;
42
    }
43
44
    protected function execute(InputInterface $input, OutputInterface $output): int
45
    {
46
        $loop = $this->loop;
47
        $output = new SymfonyStyle($input, $output);
48
        $logger = $this->logger ?? new ConsoleLogger($output, [LogLevel::INFO => OutputInterface::VERBOSITY_NORMAL]);
0 ignored issues
show
Unused Code introduced by
The assignment to $logger is dead and can be removed.
Loading history...
49
        try {
50
            $config = $this->configuration->overrideWith(without_nullish_values($input->getOptions()))->asArray();
51
            $factory = $this->factory->withConfig($config);
52
            $loop->futureTick(
53
                function () use ($config, $output) {
54
                    $this->displayConfiguration($config, $output);
55
                }
56
            );
57
58
            $hub = $factory->create();
59
            $hub->run();
60
61
            if (\SIGINT === $hub->getShutdownSignal()) {
62
                $output->newLine(2);
63
                $output->writeln('SIGINT received. 😢');
64
                $output->writeln('Goodbye! 👋');
65
66
                return 0;
67
            }
68
69
            $output->error('Server process was killed unexpectedly.');
70
71
            return 1;
72
        } catch (\Exception $e) {
73
            $output->error($e->getMessage());
74
75
            return 1;
76
        }
77
    }
78
79
    protected function configure(): void
80
    {
81
        $this->setDescription('Runs the Mercure Hub as a standalone application.');
82
        $this->addOption(
83
            'addr',
84
            null,
85
            InputOption::VALUE_OPTIONAL,
86
            'The address to listen on.',
87
        )
88
            ->addOption(
89
                'transport-url',
90
                null,
91
                InputOption::VALUE_OPTIONAL,
92
                'The DSN to transport messages.',
93
            )
94
            ->addOption(
95
                'storage-url',
96
                null,
97
                InputOption::VALUE_OPTIONAL,
98
                'The DSN to store messages.',
99
            )
100
            ->addOption(
101
                'metrics-url',
102
                null,
103
                InputOption::VALUE_OPTIONAL,
104
                'The DSN to store metrics.',
105
            )
106
            ->addOption(
107
                'cors-allowed-origins',
108
                null,
109
                InputOption::VALUE_OPTIONAL,
110
                'A list of allowed CORS origins, can be * for all.',
111
            )
112
            ->addOption(
113
                'jwt-key',
114
                null,
115
                InputOption::VALUE_OPTIONAL,
116
                'The JWT key to use for both publishers and subscribers',
117
            )
118
            ->addOption(
119
                'jwt-algorithm',
120
                null,
121
                InputOption::VALUE_OPTIONAL,
122
                'The JWT verification algorithm to use for both publishers and subscribers, e.g. HS256 (default) or RS512.',
123
            )
124
            ->addOption(
125
                'publisher-jwt-key',
126
                null,
127
                InputOption::VALUE_OPTIONAL,
128
                'Must contain the secret key to valid publishers\' JWT, can be omitted if jwt_key is set.',
129
            )
130
            ->addOption(
131
                'publisher-jwt-algorithm',
132
                null,
133
                InputOption::VALUE_OPTIONAL,
134
                'The JWT verification algorithm to use for publishers, e.g. HS256 (default) or RS512.',
135
            )
136
            ->addOption(
137
                'subscriber-jwt-key',
138
                null,
139
                InputOption::VALUE_OPTIONAL,
140
                'Must contain the secret key to valid subscribers\' JWT, can be omitted if jwt_key is set.',
141
            )
142
            ->addOption(
143
                'subscriber-jwt-algorithm',
144
                null,
145
                InputOption::VALUE_OPTIONAL,
146
                'The JWT verification algorithm to use for subscribers, e.g. HS256 (default) or RS512.',
147
            )
148
            ->addOption(
149
                'allow-anonymous',
150
                null,
151
                InputOption::VALUE_NONE,
152
                'Allows subscribers with no valid JWT to connect.',
153
            );
154
    }
155
156
    private function displayConfiguration(array $config, SymfonyStyle $output): void
157
    {
158
        if (!$output->isVeryVerbose()) {
159
            return;
160
        }
161
162
        $rows = [];
163
        foreach ($config as $key => $value) {
164
            if (null === $value) {
165
                $value = '<fg=yellow>null</>';
166
            }
167
            if (\is_bool($value)) {
168
                $value = $value ? '<fg=green>true</>' : '<fg=red>false</>';
169
            }
170
            $rows[] = [$key, $value];
171
        }
172
173
        $output->table(['Key', 'Value'], $rows);
174
    }
175
176
    private function getInputOptions(InputInterface $input): array
0 ignored issues
show
Unused Code introduced by
The method getInputOptions() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
177
    {
178
        return \array_filter(
179
            $input->getOptions(),
180
            fn($value) => null !== nullify($value) && false !== $value
181
        );
182
    }
183
}
184