Passed
Pull Request — master (#2)
by Mariano
11:07
created

PhiremockServerCommand::initializeLogger()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 8
rs 10
1
<?php
2
/**
3
 * This file is part of Phiremock.
4
 *
5
 * Phiremock is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU Lesser General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * Phiremock is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with Phiremock.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18
19
namespace Mcustiel\Phiremock\Server\Cli\Commands;
20
21
use Mcustiel\Phiremock\Server\Factory\Factory;
22
use Mcustiel\Phiremock\Server\Http\ServerInterface;
23
use Mcustiel\Phiremock\Server\Utils\Config\Config;
24
use Mcustiel\Phiremock\Server\Utils\Config\ConfigBuilder;
25
use Mcustiel\Phiremock\Server\Utils\Config\Directory;
26
use Psr\Log\LoggerInterface;
27
use Symfony\Component\Console\Command\Command;
28
use Symfony\Component\Console\Input\InputInterface;
29
use Symfony\Component\Console\Input\InputOption;
30
use Symfony\Component\Console\Output\OutputInterface;
31
32
class PhiremockServerCommand extends Command
33
{
34
    const IP_HELP_MESSAGE = 'IP address of the interface where Phiremock must list for connections.';
35
    const DEFAULT_IP = '0.0.0.0';
36
    const PORT_HELP_MESSAGE = 'Port where Phiremock must list for connections.';
37
    const DEFAULT_PORT = 8086;
38
    const EXPECTATIONS_DIR_HELP_MESSAGE = 'Directory in which to search for expectation definition files.';
39
    const DEFAULT_EXPECTATIONS_DIR = '[USER_HOME_PATH]/.phiremock/expectations';
40
    const DEBUG_HELP_MESSAGE = 'Sets debug mode.';
41
    const CONFIG_PATH_HELP_MESSAGE = 'Directory in which to search for configuration files. Default: current directory.';
42
    const FACTORY_CLASS_HELP_MESSAGE = 'Factory class to use. It must inherit from: ' . Factory::class;
43
    const CERTIFICATE_HELP_MESSAGE = 'Path to the local certificate for secure connection';
44
    const CERTIFICATE_KEY_HELP_MESSAGE = 'Path to the local certificate key for secure connection';
45
    const PASSPHRASE_HELP_MESSAGE = 'Passphrase if the local certificate is encrypted';
46
47
    /** @var Factory */
48
    private $factory;
49
    /** @var LoggerInterface */
50
    private $logger;
51
    /** @var ServerInterface */
52
    private $httpServer;
53
54
    public function __construct()
55
    {
56
        parent::__construct('run');
57
    }
58
59
    protected function configure(): void
60
    {
61
        $this->setDescription('Runs Phiremock server')
62
            ->setHelp('This is the main command to run Phiremock as a HTTP server.')
63
            ->addOption(
64
                'ip',
65
                'i',
66
                InputOption::VALUE_REQUIRED,
67
                self::IP_HELP_MESSAGE
68
            )
69
            ->addOption(
70
                'port',
71
                'p',
72
                InputOption::VALUE_REQUIRED,
73
                self::PORT_HELP_MESSAGE
74
            )
75
            ->addOption(
76
                'expectations-dir',
77
                'e',
78
                InputOption::VALUE_REQUIRED,
79
                self::EXPECTATIONS_DIR_HELP_MESSAGE
80
            )
81
            ->addOption(
82
                'debug',
83
                'd',
84
                InputOption::VALUE_NONE,
85
                sprintf(self::DEBUG_HELP_MESSAGE)
86
            )
87
            ->addOption(
88
                'config-path',
89
                'c',
90
                InputOption::VALUE_REQUIRED,
91
                sprintf(self::CONFIG_PATH_HELP_MESSAGE)
92
            )
93
            ->addOption(
94
                'factory-class',
95
                'f',
96
                InputOption::VALUE_REQUIRED,
97
                sprintf(self::FACTORY_CLASS_HELP_MESSAGE)
98
            )
99
            ->addOption(
100
                'certificate',
101
                't',
102
                InputOption::VALUE_REQUIRED,
103
                sprintf(self::CERTIFICATE_HELP_MESSAGE)
104
            )
105
            ->addOption(
106
                'certificate-key',
107
                'k',
108
                InputOption::VALUE_REQUIRED,
109
                sprintf(self::CERTIFICATE_KEY_HELP_MESSAGE)
110
            )
111
            ->addOption(
112
                'cert-passphrase',
113
                's',
114
                InputOption::VALUE_REQUIRED,
115
                sprintf(self::PASSPHRASE_HELP_MESSAGE)
116
            );
117
    }
118
119
    protected function execute(InputInterface $input, OutputInterface $output): int
120
    {
121
        $this->createPhiremockPathIfNotExists();
122
123
        $configPath = new Directory($input->getOption('config-path') ?? getcwd());
124
        $cliConfig = [];
125
        if ($input->getOption('ip')) {
126
            $cliConfig['ip'] = (string) $input->getOption('ip');
127
        }
128
        if ($input->getOption('port')) {
129
            $cliConfig['port'] = (int) $input->getOption('port');
130
        }
131
        if ($input->getOption('debug')) {
132
            $cliConfig['debug'] = true;
133
        }
134
        if ($input->getOption('expectations-dir')) {
135
            $cliConfig['expectations-dir'] = $input->getOption('expectations-dir');
136
        }
137
        if ($input->getOption('factory-class')) {
138
            $cliConfig['factory-class'] = $input->getOption('factory-class');
139
        }
140
        if ($input->getOption('certificate')) {
141
            $cliConfig['certificate'] = $input->getOption('certificate');
142
        }
143
        if ($input->getOption('certificate-key')) {
144
            $cliConfig['certificate-key'] = $input->getOption('certificate-key');
145
        }
146
        if ($input->getOption('cert-passphrase')) {
147
            $cliConfig['cert-passphrase'] = $input->getOption('cert-passphrase');
148
        }
149
150
        $config = (new ConfigBuilder($configPath))->build($cliConfig);
151
152
        $this->factory = $config->getFactoryClassName()->asInstance($config);
153
        $this->initializeLogger($config);
154
        $this->processFileExpectations($config);
155
        $this->startHttpServer($config);
156
157
        return 0;
158
    }
159
160
    private function createPhiremockPathIfNotExists()
161
    {
162
        $defaultExpectationsPath = ConfigBuilder::getDefaultExpectationsDir();
163
        if (!$defaultExpectationsPath->exists()) {
164
            $defaultExpectationsPath->create();
165
        } elseif (!$defaultExpectationsPath->isDirectory()) {
166
            throw new \Exception('Expectations path must be a directory');
167
        }
168
    }
169
170
    private function startHttpServer(Config $config): void
171
    {
172
        $this->httpServer = $this->factory->createHttpServer();
173
        $this->setUpHandlers();
174
        $this->httpServer->listen($config->getInterfaceIp(), $config->getPort(), $config->getSecureOptions());
175
    }
176
177
    private function initializeLogger(Config $config): void
178
    {
179
        $this->logger = $this->factory->createLogger();
180
        $this->logger->info(
181
            sprintf(
182
                '[%s] Starting Phiremock%s...',
183
                date('Y-m-d H:i:s'),
184
                ($config->isDebugMode() ? ' in debug mode' : '')
185
            )
186
        );
187
    }
188
189
    private function processFileExpectations(Config $config): void
190
    {
191
        $expectationsDir = $config->getExpectationsPath()->asString();
192
        $this->logger->debug(
193
            sprintf(
194
                'Phiremock\'s expectation dir is set to: %s',
195
                $this->factory->createFileSystemService()->getRealPath($expectationsDir))
196
        );
197
        $this->factory
198
            ->createFileExpectationsLoader()
199
            ->loadExpectationsFromDirectory($expectationsDir);
200
    }
201
202
    private function setUpHandlers(): void
203
    {
204
        $handleTermination = function () {
205
            $this->logger->info('Stopping Phiremock...');
206
            $this->httpServer->shutdown();
207
            $this->logger->info('Bye bye');
208
        };
209
210
        $this->logger->debug('Registering shutdown function');
211
        register_shutdown_function($handleTermination);
212
213
        if (\function_exists('pcntl_signal')) {
214
            $this->logger->debug('PCNTL present: Installing signal handlers');
215
            pcntl_signal(SIGTERM, function () { exit(0); });
216
        }
217
218
        $errorHandler = function ($severity, $message, $file, $line) {
219
            $errorInformation = sprintf('%s:%s (%s)', $file, $line, $message);
220
            if ($this->isError($severity)) {
221
                $this->logger->error($errorInformation);
222
                throw new \ErrorException($message, 0, $severity, $file, $line);
223
            }
224
            $this->logger->warning($errorInformation);
225
226
            return false;
227
        };
228
        set_error_handler($errorHandler);
229
    }
230
231
    private function isError(int $severity): bool
232
    {
233
        return \in_array(
234
            $severity,
235
            [
236
                E_COMPILE_ERROR,
237
                E_CORE_ERROR,
238
                E_USER_ERROR,
239
                E_PARSE,
240
                E_ERROR,
241
            ],
242
            true
243
        );
244
    }
245
}
246