leocavalcante/siler
Siler is a zero-dependency PHP library/micro-framework of high-level functional abstractions for declarative apps and routing. Fast, flat-file friendly, and works well with Swoole. Note: the repository is archived; Nano is recommended as an alternative.
Enables PHP developers to write high-performance, scalable, concurrent TCP, UDP, Unix socket, HTTP, Websocket services in PHP programming language without too much knowledge about non-blocking I/O programming and low-level Linux kernel. Compared with other async programming frameworks or softwares such as Nginx, Tornado, Node.js, Swoole has the built-in async, multiple threads I/O modules. Developers can use sync or async API to write the applications — www.swoole.co.uk
It is open source and free. Released under the license of Apache 2.0.
{% hint style="info" %} Forget about everything you know on how to run PHP on web servers like Apache and Nginx behind modules and CGI layers. {% endhint %}
Swoole is released as a PHP extension (PECL) and runs as a PHP CLI application.
The differences between Swoole with PHP-FPM the traditional PHP model are:
Swoole prerequisites operation system are: Linux, FreeBSD or MacOS, but don't worry Windows-people, we have Docker! And I got us covered with Dwoole:
{% code title="docker-compose.yml" %}
version: '3'
services:
swoole:
container_name: siler_swoole
image: leocavalcante/dwoole:dev
ports:
- '9501:9501'
volumes:
- ./:/app
{% endcode %}
{% hint style="info" %} Beyond being cross-platform, Dwoole helps with others features like Composer and hot-restart that Unix people would also like. {% endhint %}
You already got Siler, right? Flat-files and plain-old PHP functions rockin'on! Just simple. A set of general purpose high-level abstractions aiming an API for declarative programming in PHP. And this wouldn't be different about Swoole.
The Siler\Swoole namespace get you covered.
{% code title="index.php" %}
<?php declare(strict_types=1);
require_once 'vendor/autoload.php';
use Siler\Swoole;
$server = fn() => Swoole\emit('Hello World');
Swoole\http($server)->start();
{% endcode %}
That's it! This attaches a callback handler that always emits "Hello World" on every request and starts a HTTP server on port 9501. Run it using docker-compose up or just php index.php if you're not using Docker.
Go to http://localhost:9051 or http://<docker_machine_ip>:9051 and you should get a "Hello World" response as plain/text.
You know, Siler can do a lot more, it abstracts things like Routing and Twig Templating. Let's add this to our Swoole server:
{% code title="index.php" %}
<?php declare(strict_types=1);
require_once 'vendor/autoload.php';
use Siler\Swoole;
use Siler\Route;
$handler = function ($req) {
Route\get('/', 'pages/home.php');
Swoole\emit('Not found', 404);
};
Swoole\http($handler)->start();
{% endcode %}
Now we are forwarding GET requests from path / to file pages/home.php.
{% code title="pages/home.php" %}
<?php declare(strict_types=1);
use Siler\Swoole;
return fn() => Swoole\emit('Hello World');
{% endcode %}
{% hint style="info" %}
When using Swoole, routes that use files should return a function to ensure a re-computation. Siler will require the file only on the first match, then on the next matches it will only re-execute the returned function. This makes possible the use of require_once while maintaining a way to re-execute something.
{% endhint %}
You may ask: "What about Swoole\emit('Not found', 404) at the end?".
Nice question! Siler\Swoole\emit() function will short-circuit further emit attempts, so it will work exactly like you have imagined, when a route matches a path like / it will emit the proper response, but when no route matches and this means: no route will emit something, then Swoole\emit('Not found', 404) will emit a 404 Not found response.
Go ahead, restart the server and go to http://localhost:9501/, you should still be seeing "Hello World", but going to any other path, like http://localhost:9501/banana, you should be seeing "Not found" and a proper 404 status code.
Twig should work exactly the same as there is no Swoole behind it:
{% tabs %} {% tab title="pages/home.php" %}
<?php declare(strict_types=1);
use Siler\Swoole;
use Siler\Twig;
return fn() => Swoole\emit(Twig\render('home.twig'));
{% endtab %}
{% tab title="index.php" %}
<?php declare(strict_types=1);
require_once 'vendor/autoload.php';
use Siler\Swoole;
use Siler\Route;
use Siler\Twig;
Twig\init('pages');
$handler = function ($req) {
Route\get('/', 'pages/home.php');
Swoole\emit('Not found', 404);
};
Swoole\http($handler)->start();
{% endtab %}
{% tab title="pages/home.twig" %}
{% extends "_layout.twig" %}
{% block page %}
<p>Hello World</p>
{% endblock %}
{% endtab %}
{% tab title="pages/_layout.twig" %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Siler + Swoole</title>
</head>
<body>
{% block page %}{% endblock %}
</body>
</html>
{% endtab %} {% endtabs %}
{% hint style="info" %} Swoole's HTTP server will auto-magically output the Response header Content-type as text/html instead of text/plain now. {% endhint %}
If you're sure that your template doesn't depend on the request, you can render it once:
{% code title="pages/home.php" %}
<?php declare(strict_types=1);
use Siler\Swoole;
use Siler\Twig;
$html = Twig\render('home.twig');
return fn() => Swoole\emit($html);
{% endcode %}
This avoids the template to be re-rendered on each request unnecessarily.
The Siler\Swoole\http function returns a plain Swoole\Http\Server so you can give it to a variable and use regular methods from Swoole's documentation like set:
{% tabs %} {% tab title="index.php" %}
/*
├───api
├───pages
└───public
└───assets
*/
$server = Swoole\http($handler);
$server->set([
'enable_static_handler' => true,
'document_root' => __DIR__ . '/public',
]);
$server->start();
{% endtab %}
{% tab title="pages/_layout.twig" %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Siler + Swoole</title>
<link rel="stylesheet" href="/assets/styles.css">
<script defer src="/assets/scripts.js"></script>
</head>
<body>
{% block page %}{% endblock %}
</body>
</html>
{% endtab %} {% endtabs %}
Since there is no web server module or CGI layer, things like $_GET won't work for query string parameters etc. But fear nothing, Siler provides getters for both Swoole's Request and Response objects: Siler\Swoole\request() and Siler\Swoole\response().
Instead of always printing "Hello World", let's print the name that came from the URL parameter:
{% tabs %} {% tab title="pages/home.php" %}
<?php declare(strict_types=1);
use Siler\Swoole;
use Siler\Twig;
return function () {
$name = Swoole\request()->get['name'] ?? 'World';
Swoole\emit(Twig\render('home.twig', ['name' => $name]));
};
{% endtab %}
{% tab title="pages/home.twig" %}
{% extends "_layout.twig" %}
{% block page %}
<p>Hello {{ name }}</p>
{% endblock %}
{% endtab %} {% endtabs %}
Go to http://localhost:9501/?name=Leo, you should be seeing "Hello Leo" now.
{% hint style="info" %} You can find more about Swoole's Request and Response objects at: swoole.co.uk/docs/modules/swoole-http-server/methods-properties {% endhint %}
This is as simple as Siler gets.
We can add a new route/API endpoint to GET all of our Todos:
{% code title="index.php" %}
<?php declare(strict_types=1);
require_once 'vendor/autoload.php';
use Siler\Route;
use Siler\Swoole;
use Siler\Twig;
Twig\init('pages');
$handler = function ($req, $res) {
Route\get('/', 'pages/home.php');
Route\get('/todos', 'api/todos.php');
// None of the above short-circuited the response with Swoole\emit().
Swoole\emit('Not found', 404);
};
Swoole\http($handler)->start();
{% endcode %}
Then you can return your JSON and within json(), Siler will automatically add the Content-type: application/json response header. Also you can enable CORS.
{% code title="api/todos.php" %}
<?php declare(strict_types=1);
use Siler\Swoole;
$todos = [
['id' => 1, 'text' => 'foo'],
['id' => 2, 'text' => 'bar'],
['id' => 3, 'text' => 'baz'],
];
return function () {
Swoole\cors();
Swoole\json($todos);
};
{% endcode %}
Head to http://localhost:9501/todos. There we go!
A Siler ❤️ Swoole powered API.
{% hint style="info" %} You can still use any other Swoole module like Coroutines and Redis. More abstractions to come. {% endhint %}
How can I help you explore Laravel packages today?