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