Siler ❤️ Swoole

Flat files and plain-old PHP functions rocking on a production-grade, high-performance, scalable, concurrent and non-blocking HTTP server.

Swoole

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

Features

  • Rapid development of high performance protocol servers & clients with PHP language

  • Event-driven, asynchronous programming for PHP

  • Event loop API

  • Processes management API

  • Memory management API

  • Golang style channels for inter-processes communication

Use cases

  • Web applications and systems

  • Mobile communication systems

  • Online game systems

  • Internet of things

  • Car networking

  • Smart home systems

It is open source and free. Released under the license of Apache 2.0.

Get started

Forget about everything you know on how to run PHP on web servers like Apache and Nginx behind modules and CGI layers.

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 forks a number of worker processes based on CPU core number to utilize all CPU cores.

  • Swoole supports Long-live connections for websocket server or TCP/UDP server.

  • Swoole supports more server-side protocols.

  • Swoole can manage and reuse the status in memory.

Docker

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:

docker-compose.yml
version: '3'
services:
swoole:
container_name: siler_swoole
image: leocavalcante/dwoole:dev
ports:
- '9501:9501'
volumes:
- ./:/app

Beyond being cross-platform, Dwoole helps with others features like Composer and hot-restart that Unix people would also like.

Siler

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.

Hello World

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();

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.

More Siler!

You know, Siler can do a lot more, it abstracts things like Routing and Twig Templating. Let's add this to our Swoole server:

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();

Now we are forwarding GET requests from path / to file pages/home.php.

pages/home.php
<?php declare(strict_types=1);
use Siler\Swoole;
return fn() => Swoole\emit('Hello World');

When using Swoole, routes that uses 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.

You may ask: "What about Swoole\emit('Not found', 404) on 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:

pages/home.php
index.php
pages/home.twig
pages/_layout.twig
<?php declare(strict_types=1);
use Siler\Swoole;
use Siler\Twig;
return fn() => Swoole\emit(Twig\render('home.twig'));
<?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();
{% extends "_layout.twig" %}
{% block page %}
<p>Hello World</p>
{% endblock %}
<!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>

Swoole's HTTP server will auto-magically outputs the Response header Content-type as text/html instead of text/plain now.

If you're sure that your template doesn't depends on the request, you can render it once:

pages/home.php
<?php declare(strict_types=1);
use Siler\Swoole;
use Siler\Twig;
$html = Twig\render('home.twig');
return fn() => Swoole\emit($html);

This avoids the template to be re-rendered on each request unnecessarily.

Request's Query string, Body & Headers

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:

pages/home.php
pages/home.twig
<?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]));
};
{% extends "_layout.twig" %}
{% block page %}
<p>Hello {{ name }}</p>
{% endblock %}

Go to http://localhost:9501/?name=Leo, you should be seeing "Hello Leo" now.

You can find more about Swoole's Request and Response objects at: swoole.co.uk/docs/modules/swoole-http-server/methods-properties

Building an API

This is as simple as Siler gets. We can add a new route/API endpoint to GET all of our Todos:

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();

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.

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);
};

Head to http://localhost:9501/todos. There we go! A Siler ❤️ Swoole powered API.

You can still use any other Swoole module like Coroutines and Redis. More abstractions to come.