Skip to content

Commit 0b5dd49

Browse files
committed
Refactor into a single command
1 parent 3e41842 commit 0b5dd49

File tree

7 files changed

+146
-45
lines changed

7 files changed

+146
-45
lines changed

README.md

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
Development web server for serverless-native PHP web apps.
22

3-
**This project is currently experimental and the documentation incomplete.**
4-
53
## Why?
64

75
This web server is meant for HTTP applications implemented without framework, using API Gateway as the router and PSR-15 controllers.
86

7+
It is also meant to be used with the [Bref Micro](https://github.com/brefphp/micro) framework.
8+
99
## Installation
1010

1111
```bash
@@ -17,5 +17,19 @@ composer require --dev bref/dev-server
1717
Run the webserver with:
1818

1919
```bash
20-
php -S 127.0.0.1:8000 vendor/bin/bref-dev-server
20+
vendor/bin/bref-dev-server
21+
```
22+
23+
The application will be available at [http://localhost:8000/](http://localhost:8000/).
24+
25+
Routes will be parsed from `serverless.yml` in the current directory.
26+
27+
### Assets
28+
29+
By default, static assets are served from the current directory.
30+
31+
To customize that, use the `--assets` option. For example to serve static files from the `public/` directory:
32+
33+
```bash
34+
vendor/bin/bref-dev-server --assets public
2135
```

bin/bref-dev-server

100644100755
Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,41 @@
1+
#!/usr/bin/env php
12
<?php declare(strict_types=1);
23

3-
use Bref\DevServer\DevServer;
4-
5-
if (file_exists(__DIR__ . '/vendor/autoload.php')) {
6-
require_once __DIR__ . '/vendor/autoload.php';
4+
if (file_exists(__DIR__ . '/../vendor/autoload.php')) {
5+
require_once __DIR__ . '/../vendor/autoload.php';
76
} elseif (file_exists(__DIR__ . '/../../autoload.php')) {
87
require_once __DIR__ . '/../../autoload.php';
98
} else {
109
require_once __DIR__ . '/../../../autoload.php';
1110
}
1211

13-
return (new DevServer)->run();
12+
use Bref\DevServer\Handler;
13+
use Silly\Application;
14+
use Symfony\Component\Console\Output\OutputInterface;
15+
use Symfony\Component\Process\Process;
16+
17+
$app = new Application('Bref dev server');
18+
19+
$app->command('run [-a|--assets=]', function (OutputInterface $output, ?string $assets = null) {
20+
$handler = __DIR__ . '/../src/server-handler.php';
21+
$assetsDirectory = $assets ?: getcwd();
22+
$output->writeln("<info>Serving PHP from serverless.yml routes</info>");
23+
$output->writeln("<info>Serving assets from $assetsDirectory/</info>");
24+
25+
$server = new Process(['php', '-S', '127.0.0.1:8000', $handler, '-t', $assetsDirectory]);
26+
$server->setTimeout(null);
27+
$server->setTty(true);
28+
$server->setEnv([
29+
'PHP_CLI_SERVER_WORKERS' => 2,
30+
Handler::ASSETS_DIRECTORY_VARIABLE => $assetsDirectory,
31+
]);
32+
33+
$server->run();
34+
35+
exit($server->getExitCode());
36+
})->descriptions('Run the development server', [
37+
'--assets' => 'The directory where static assets can be found. By default it is the current directory.',
38+
]);
39+
40+
$app->setDefaultCommand('run');
41+
$app->run();

composer.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@
2020
"require": {
2121
"php": ">=8.0",
2222
"bref/bref": "^1.0",
23-
"filp/whoops": "^2.9",
23+
"filp/whoops": "^2.5",
24+
"mnapoli/silly": "^1.5",
2425
"nyholm/psr7": "^1.0",
2526
"nyholm/psr7-server": "^1.0",
26-
"psr/http-server-handler": "^1.0",
2727
"psr/http-message": "^1.0",
28+
"psr/http-server-handler": "^1.0",
2829
"psr/http-server-middleware": "^1.0",
30+
"symfony/console": "^4.0|^5.0",
31+
"symfony/process": "^4.0|^5.0",
2932
"symfony/yaml": "^4.0|^5.0"
3033
},
3134
"require-dev": {

src/DevServer.php

Lines changed: 13 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,46 +2,25 @@
22

33
namespace Bref\DevServer;
44

5-
use Bref\Bref;
6-
use Nyholm\Psr7\Factory\Psr17Factory;
7-
use Nyholm\Psr7Server\ServerRequestCreator;
8-
use Symfony\Component\Yaml\Yaml;
9-
use Whoops\Handler\PrettyPageHandler;
10-
use Whoops\Run;
5+
use Symfony\Component\Process\Process;
116

127
class DevServer
138
{
14-
public function run(): bool|null
9+
public function run(): void
1510
{
16-
// Serve assets
17-
if (PHP_SAPI === 'cli-server') {
18-
$url = parse_url($_SERVER['REQUEST_URI']);
19-
if (is_file(getcwd() . '/web' . ($url['path'] ?? ''))) return false;
20-
}
11+
$handler = __DIR__ . '/server-handler.php';
12+
$assetsDirectory = getcwd();
2113

22-
$whoops = new Run;
23-
$whoops->pushHandler(new PrettyPageHandler);
24-
$whoops->register();
14+
$server = new Process(['php', '-S', '127.0.0.1:8000', $handler, '-t', $assetsDirectory]);
15+
$server->setTimeout(null);
16+
$server->setTty(true);
17+
$server->setEnv([
18+
'PHP_CLI_SERVER_WORKERS' => 2,
19+
Handler::ASSETS_DIRECTORY_VARIABLE => $assetsDirectory,
20+
]);
2521

26-
$container = Bref::getContainer();
22+
$server->run();
2723

28-
$psr17Factory = new Psr17Factory;
29-
$requestFactory = new ServerRequestCreator(
30-
$psr17Factory,
31-
$psr17Factory,
32-
$psr17Factory,
33-
$psr17Factory
34-
);
35-
36-
$serverlessConfig = Yaml::parseFile(getcwd() . '/serverless.yml', Yaml::PARSE_CUSTOM_TAGS);
37-
$router = Router::fromServerlessConfig($serverlessConfig);
38-
39-
$request = $requestFactory->fromGlobals();
40-
[$handler, $request] = $router->match($request);
41-
$controller = $handler ? $container->get($handler) : new NotFound;
42-
$response = $controller->handle($request);
43-
(new ResponseEmitter)->emit($response);
44-
45-
return null;
24+
exit($server->getExitCode());
4625
}
4726
}

src/Handler.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Bref\DevServer;
4+
5+
use Bref\Bref;
6+
use Nyholm\Psr7\Factory\Psr17Factory;
7+
use Nyholm\Psr7Server\ServerRequestCreator;
8+
use Psr\Http\Message\ServerRequestInterface;
9+
use RuntimeException;
10+
use Symfony\Component\Yaml\Yaml;
11+
use Whoops\Handler\PrettyPageHandler;
12+
use Whoops\Run;
13+
14+
/**
15+
* @internal
16+
*/
17+
class Handler
18+
{
19+
public const ASSETS_DIRECTORY_VARIABLE = '_BREF_LOCAL_ASSETS_DIRECTORY';
20+
21+
public function handleRequest(): bool|null
22+
{
23+
$assetsDirectory = getenv(self::ASSETS_DIRECTORY_VARIABLE);
24+
25+
// Serve assets
26+
if (PHP_SAPI === 'cli-server') {
27+
$url = parse_url($_SERVER['REQUEST_URI']);
28+
if (is_file($assetsDirectory . ($url['path'] ?? ''))) return false;
29+
}
30+
31+
$whoops = new Run;
32+
$whoops->pushHandler(new PrettyPageHandler);
33+
$whoops->register();
34+
35+
$container = Bref::getContainer();
36+
37+
$serverlessFile = getcwd() . '/serverless.yml';
38+
if (! file_exists($serverlessFile)) {
39+
throw new RuntimeException('No serverless.yml file was found in the current directory. This dev server needs a serverless.yml to discover the API Gateway routes.');
40+
}
41+
$serverlessConfig = Yaml::parseFile($serverlessFile, Yaml::PARSE_CUSTOM_TAGS);
42+
$router = Router::fromServerlessConfig($serverlessConfig);
43+
44+
$request = $this->requestFromGlobals();
45+
[$handler, $request] = $router->match($request);
46+
$controller = $handler ? $container->get($handler) : new NotFound;
47+
$response = $controller->handle($request);
48+
(new ResponseEmitter)->emit($response);
49+
50+
return null;
51+
}
52+
53+
private function requestFromGlobals(): ServerRequestInterface
54+
{
55+
$psr17Factory = new Psr17Factory;
56+
$requestFactory = new ServerRequestCreator(
57+
$psr17Factory,
58+
$psr17Factory,
59+
$psr17Factory,
60+
$psr17Factory
61+
);
62+
return $requestFactory->fromGlobals();
63+
}
64+
}

src/NotFound.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ class NotFound implements RequestHandlerInterface
1414
{
1515
public function handle(ServerRequestInterface $request): ResponseInterface
1616
{
17-
return new Response(404, [], 'Route not found in serverless.yml');
17+
$url = $request->getUri()->getPath();
18+
19+
return new Response(404, [], "Route '$url' not found in serverless.yml");
1820
}
1921
}

src/server-handler.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php declare(strict_types=1);
2+
3+
use Bref\DevServer\Handler;
4+
5+
if (file_exists(__DIR__ . '/../vendor/autoload.php')) {
6+
require_once __DIR__ . '/../vendor/autoload.php';
7+
} else {
8+
require_once __DIR__ . '/../../../autoload.php';
9+
}
10+
11+
return (new Handler)->handleRequest();

0 commit comments

Comments
 (0)