Passed
Pull Request — master (#2)
by Mariano
04:51
created

PhiremockServerCommand::execute()   C

Complexity

Conditions 9
Paths 256

Size

Total Lines 39
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 9
eloc 25
c 2
b 0
f 0
nc 256
nop 2
dl 0
loc 39
rs 6.5222
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 ErrorException;
22
use Exception;
23
use Mcustiel\Phiremock\Server\Factory\Factory;
24
use Mcustiel\Phiremock\Server\Http\ServerInterface;
25
use Mcustiel\Phiremock\Server\Utils\Config\Config;
26
use Mcustiel\Phiremock\Server\Utils\Config\ConfigBuilder;
27
use Mcustiel\Phiremock\Server\Utils\Config\Directory;
28
use Psr\Log\LoggerInterface;
29
use Symfony\Component\Console\Command\Command;
30
use Symfony\Component\Console\Input\InputInterface;
31
use Symfony\Component\Console\Input\InputOption;
32
use Symfony\Component\Console\Output\OutputInterface;
33
34
class PhiremockServerCommand extends Command
35
{
36
    const IP_HELP_MESSAGE = 'IP address of the interface where Phiremock must list for connections.';
37
    const PORT_HELP_MESSAGE = 'Port where Phiremock must list for connections.';
38
    const EXPECTATIONS_DIR_HELP_MESSAGE = 'Directory in which to search for expectation definition files.';
39
    const DEBUG_HELP_MESSAGE = 'Sets debug mode.';
40
    const CONFIG_PATH_HELP_MESSAGE = 'Directory in which to search for configuration files. Default: current directory.';
41
    const FACTORY_CLASS_HELP_MESSAGE = 'Factory class to use. It must inherit from: ' . Factory::class;
42
    const CERTIFICATE_HELP_MESSAGE = 'Path to the local certificate for secure connection';
43
    const CERTIFICATE_KEY_HELP_MESSAGE = 'Path to the local certificate key for secure connection';
44
    const PASSPHRASE_HELP_MESSAGE = 'Passphrase if the local certificate is encrypted';
45
46
    /** @var Factory */
47
    private $factory;
48
    /** @var LoggerInterface */
49
    private $logger;
50
    /** @var ServerInterface */
51
    private $httpServer;
52
53
    public function __construct()
54
    {
55
        parent::__construct('run');
56
    }
57
58
    protected function configure(): void
59
    {
60
        $this->setDescription('Runs Phiremock server')
61
            ->setHelp('This is the main command to run Phiremock as a HTTP server.')
62
            ->addOption(
63
                'ip',
64
                'i',
65
                InputOption::VALUE_REQUIRED,
66
                self::IP_HELP_MESSAGE
67
            )
68
            ->addOption(
69
                'port',
70
                'p',
71
                InputOption::VALUE_REQUIRED,
72
                self::PORT_HELP_MESSAGE
73
            )
74
            ->addOption(
75
                'expectations-dir',
76
                'e',
77
                InputOption::VALUE_REQUIRED,
78
                self::EXPECTATIONS_DIR_HELP_MESSAGE
79
            )
80
            ->addOption(
81
                'debug',
82
                'd',
83
                InputOption::VALUE_NONE,
84
                sprintf(self::DEBUG_HELP_MESSAGE)
85
            )
86
            ->addOption(
87
                'config-path',
88
                'c',
89
                InputOption::VALUE_REQUIRED,
90
                sprintf(self::CONFIG_PATH_HELP_MESSAGE)
91
            )
92
            ->addOption(
93
                'factory-class',
94
                'f',
95
                InputOption::VALUE_REQUIRED,
96
                sprintf(self::FACTORY_CLASS_HELP_MESSAGE)
97
            )
98
            ->addOption(
99
                'certificate',
100
                't',
101
                InputOption::VALUE_REQUIRED,
102
                sprintf(self::CERTIFICATE_HELP_MESSAGE)
103
            )
104
            ->addOption(
105
                'certificate-key',
106
                'k',
107
                InputOption::VALUE_REQUIRED,
108
                sprintf(self::CERTIFICATE_KEY_HELP_MESSAGE)
109
            )
110
            ->addOption(
111
                'cert-passphrase',
112
                's',
113
                InputOption::VALUE_REQUIRED,
114
                sprintf(self::PASSPHRASE_HELP_MESSAGE)
115
            );
116
    }
117
118
    /** @throws Exception */
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
    /** @throws Exception */
161
    private function createPhiremockPathIfNotExists(): void
162
    {
163
        $defaultExpectationsPath = ConfigBuilder::getDefaultExpectationsDir();
164
        if (!$defaultExpectationsPath->exists()) {
165
            $defaultExpectationsPath->create();
166
        } elseif (!$defaultExpectationsPath->isDirectory()) {
167
            throw new Exception('Expectations path must be a directory');
168
        }
169
    }
170
171
    /** @throws Exception */
172
    private function startHttpServer(Config $config): void
173
    {
174
        $this->httpServer = $this->factory->createHttpServer();
175
        $this->setUpHandlers();
176
        $this->httpServer->listen($config->getInterfaceIp(), $config->getPort(), $config->getSecureOptions());
177
    }
178
179
    private function initializeLogger(Config $config): void
180
    {
181
        $this->logger = $this->factory->createLogger();
182
        $this->logger->info(
183
            sprintf(
184
                '[%s] Starting Phiremock%s...',
185
                date('Y-m-d H:i:s'),
186
                ($config->isDebugMode() ? ' in debug mode' : '')
187
            )
188
        );
189
    }
190
191
    private function processFileExpectations(Config $config): void
192
    {
193
        $expectationsDir = $config->getExpectationsPath()->asString();
194
        $this->logger->debug(
195
            sprintf(
196
                'Phiremock\'s expectation dir is set to: %s',
197
                $this->factory->createFileSystemService()->getRealPath($expectationsDir))
198
        );
199
        $this->factory
200
            ->createFileExpectationsLoader()
201
            ->loadExpectationsFromDirectory($expectationsDir);
202
    }
203
204
    private function setUpHandlers(): void
205
    {
206
        $handleTermination = function () {
207
            $this->logger->info('Stopping Phiremock...');
208
            $this->httpServer->shutdown();
209
            $this->logger->info('Bye bye');
210
        };
211
212
        $this->logger->debug('Registering shutdown function');
213
        register_shutdown_function($handleTermination);
214
215
        if (function_exists('pcntl_signal')) {
216
            $this->logger->debug('PCNTL present: Installing signal handlers');
217
            pcntl_signal(SIGTERM, function () { exit(0); });
218
        }
219
220
        $errorHandler = function ($severity, $message, $file, $line) {
221
            $errorInformation = sprintf('%s:%s (%s)', $file, $line, $message);
222
            if ($this->isError($severity)) {
223
                $this->logger->error($errorInformation);
224
                throw new ErrorException($message, 0, $severity, $file, $line);
225
            }
226
            $this->logger->warning($errorInformation);
227
228
            return false;
229
        };
230
        set_error_handler($errorHandler);
231
    }
232
233
    private function isError(int $severity): bool
234
    {
235
        return in_array(
236
            $severity,
237
            [
238
                E_COMPILE_ERROR,
239
                E_CORE_ERROR,
240
                E_USER_ERROR,
241
                E_PARSE,
242
                E_ERROR,
243
            ],
244
            true
245
        );
246
    }
247
}
248