Skip to content

Commit

Permalink
Refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
micc83 committed Aug 22, 2020
1 parent f3c5498 commit c5b71b3
Show file tree
Hide file tree
Showing 13 changed files with 293 additions and 73 deletions.
2 changes: 1 addition & 1 deletion .phpunit.result.cache
Original file line number Diff line number Diff line change
@@ -1 +1 @@
C:37:"PHPUnit\Runner\DefaultTestResultCache":109:{a:2:{s:7:"defects";a:0:{}s:5:"times";a:1:{s:49:"Tests\WebSocketComponentTest::one_and_one_are_two";d:0.001;}}}
C:37:"PHPUnit\Runner\DefaultTestResultCache":701:{a:2:{s:7:"defects";a:3:{s:65:"Tests\ConfigTest::it_allows_to_retrieve_a_value_with_dot_notation";i:4;s:77:"Tests\ConfigTest::it_allows_to_retrieve_a_value_from_custom_or_default_config";i:4;s:49:"Tests\WebSocketComponentTest::one_and_one_are_two";i:3;}s:5:"times";a:5:{s:49:"Tests\WebSocketComponentTest::one_and_one_are_two";d:0.003;s:65:"Tests\ConfigTest::it_allows_to_retrieve_a_value_with_dot_notation";d:0.001;s:77:"Tests\ConfigTest::it_allows_to_retrieve_a_value_from_custom_or_default_config";d:0.001;s:74:"Tests\ConfigTest::it_allows_to_retrieve_a_value_from_default_or_alt_values";d:0;s:89:"Tests\WebSocketComponentTest::a_websocket_message_is_sent_to_connect_clients_on_new_email";d:0.003;}}}
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
"description": "Test SMTP mail server",
"type": "project",
"require": {
"php": "^7.4",
"ext-fileinfo": "*",
"ext-mbstring": "*",
"ext-json": "*",
"react/react": "^1.1",
"phpmailer/phpmailer": "^6.1",
"zbateson/mail-mime-parser": "^1.2",
"symfony/console": "^5.1",
"symfony/event-dispatcher": "^5.1",
"ext-mbstring": "*",
"ext-json": "*",
"cboden/ratchet": "^0.4.3"
},
"autoload-dev": {
Expand Down
17 changes: 17 additions & 0 deletions config.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

return [
'version' => '1.0.0',

'smtp' => [
'host' => '127.0.0.1:8025',
],

'web' => [
'host' => '127.0.0.1:8080',
],

'websocket' => [
'host' => '127.0.0.1:1338',
],
];
9 changes: 7 additions & 2 deletions index.php
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
#!/usr/bin/env php
<?php

require __DIR__.'/vendor/autoload.php';
require __DIR__ . '/vendor/autoload.php';

use Mailamie\Config;
use Mailamie\StartServer;
use Symfony\Component\Console\Application;

$application = new Application();

$command = new StartServer();
$localConfig = @include getenv("HOME") . '/.mailamie.config.php';

$config = new Config(require 'config.php', $localConfig ?: null);

$command = new StartServer($config);

$application->add($command);
$application->setDefaultCommand($command->getName(), true);
Expand Down
File renamed without changes.
55 changes: 55 additions & 0 deletions src/Config.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

namespace Mailamie;

use Exception;

class Config
{
private array $params;
private ?array $altParams;

public function __construct(array $params, array $altParams = null)
{
$this->params = $params;
$this->altParams = $altParams;
}

/**
* @param string $key
* @return array|mixed|null
* @throws Exception
*/
public function get(string $key)
{
if ($this->altParams) {
try {
return static::dotGet($key, $this->altParams);
} catch (Exception $e) {
}
}

return static::dotGet($key, $this->params);
}

/**
* @param string $key
* @param array $data
* @return array|mixed|null
* @throws Exception
*/
private static function dotGet(string $key, array $data)
{
$keys = explode('.', $key);

foreach ($keys as $innerKey) {
if (!array_key_exists($innerKey, $data)) {
throw new Exception('Cannot find the given key in config.');
}

$data = $data[$innerKey];
}

return $data;
}
}
3 changes: 1 addition & 2 deletions src/SmtpServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,14 @@ class SmtpServer
private string $host;
private EventDispatcherInterface $events;

const DEFAULT_HOST = '127.0.0.1:8025';
/**
* @var StreamSelectLoop
*/
private StreamSelectLoop $loop;

public function __construct(string $host, StreamSelectLoop $loop, EventDispatcherInterface $events)
{
$this->host = $host ?: static::DEFAULT_HOST;
$this->host = $host;
$this->events = $events;
$this->loop = $loop;
}
Expand Down
29 changes: 20 additions & 9 deletions src/StartServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@
use Mailamie\Events\Request;
use Mailamie\Events\Response;
use Mailamie\Events\ServerStarted;
use Ratchet\ConnectionInterface;
use Ratchet\Http\HttpServer;
use Ratchet\MessageComponentInterface;
use Ratchet\Server\IoServer;
use Ratchet\WebSocket\WsServer;
use React\EventLoop\Factory;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputDefinition;
Expand All @@ -30,6 +25,13 @@ class StartServer extends Command
use Helpers;

protected static $defaultName = 'start-server';
private Config $config;

public function __construct(Config $config)
{
parent::__construct();
$this->config = $config;
}

protected function configure()
{
Expand Down Expand Up @@ -59,9 +61,17 @@ protected function execute(InputInterface $input, OutputInterface $output)

$messageStore = new MessageStore();

$webServer = new WebServer($loop, $messageStore);
$webServer = new WebServer(
$this->config->get('web.host'),
$loop,
$messageStore
);

$websocketServer = new WebSocketServer($loop, $messageStore);
$websocketServer = new WebSocketServer(
$this->config->get('websocket.host'),
$loop,
$messageStore
);

$this->registerEventListenersOn($dispatcher, $messageStore);

Expand All @@ -82,7 +92,7 @@ private function registerEventListenersOn(EventDispatcher $dispatcher, MessageSt
$this->writeInfoBlockOn(
$startingSection,
'✓ SERVER UP AND RUNNING',
"Listening on {$event->host}"
"SMTP listening on {$event->host}\n Web interface at http://{$this->config->get('web.host')}"
);
});

Expand Down Expand Up @@ -126,7 +136,8 @@ private function handleMessage(Message $message, MessageStore $messageStore): vo

private function getHost(): string
{
return (string)$this->getInput()->getOption('host');
return (string)$this->getInput()->getOption('host')
?: $this->config->get('smtp.host');
}

private function startingBanner(): ConsoleSectionOutput
Expand Down
132 changes: 132 additions & 0 deletions src/WebController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php

namespace Mailamie;

use Mailamie\Emails\Store;
use Psr\Http\Message\ServerRequestInterface;
use React\Http\Message\Response;
use Throwable;

class WebController
{
private Store $store;

public function __construct(Store $store)
{
$this->store = $store;
}

public function route(ServerRequestInterface $request): Response
{
try {
$path = $request->getUri()->getPath();

if (preg_match('/^\/api\//i', $path)) {
return $this->handleApiCall($request);
}

$path = $this->convertToPublicPath($path);

if (file_exists($path)) {
if (static::endsWith($path, '.php')) {
return $this->handlePhpFile($path);
}

return $this->handleStaticFile($path);
}
} catch (Throwable $e) {
$this->serverError($e);
}

return $this->fileNotFoundError();
}

private function convertToPublicPath(string $originalPath): string
{
$path = $originalPath === '/' ? '/index.php' : $originalPath;
return "public" . DIRECTORY_SEPARATOR . $path;
}

private function handleApiCall(ServerRequestInterface $request): Response
{
$path = $request->getUri()->getPath();

try {
if (preg_match('/^\/api\/messages\/?$/i', $path)) {
return $this->json($this->store->all());
}

if (preg_match('/^\/api\/messages\/(.*)$/i', $path, $matches)) {
$id = (string)$matches[1];
$message = $this->store->get($id);
return $this->json($message->toArray());
}
} catch (Throwable $e) {
return $this->serverError($e);
}
}

private function handleStaticFile(string $path)
{
return new Response(
200,
['Content-Type' => mime_content_type($path)],
file_get_contents($path)
);
}

private function handlePhpFile(string $path)
{
ob_start();
require($path);
$page = ob_get_contents();
ob_end_clean();

return new Response(
200,
['Content-Type' => 'text/html'],
$page
);
}

private function fileNotFoundError(): Response
{
return new Response(
404,
['Content-Type' => 'text/html'],
"<h2>404 - Page or content not found</h2>"
);
}

private function serverError(Throwable $e): Response
{
return new Response(
500,
['Content-Type' => 'text/html'],
"<h2>{$e->getMessage()}</h2> <pre>{$e->getTraceAsString()}</pre>"
);
}

/**
* @param array|object $data
* @return Response
*/
private function json($data): Response
{
return new Response(
200,
['Content-Type' => 'application/json'],
json_encode($data)
);
}

private static function endsWith(string $haystack, string $needle): bool
{
$length = strlen($needle);
if (!$length) {
return true;
}
return substr($haystack, -$length) === $needle;

}
}
Loading

0 comments on commit c5b71b3

Please sign in to comment.