Skip to content
Permalink
main
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
description
Flat files and plain-old PHP functions rocking on a production-grade, high-performance, scalable, concurrent and non-blocking HTTP server.

Siler ❤️ Swoole

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

{% 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 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:

{% 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 %}

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

{% 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.

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:

{% 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 Templating

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.

Serving static assets

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 %}

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:

{% 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 %}

Building an API

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 %}