amphp

Amphp is an open source non-blocking concurrency framework for PHP. It provides an event loop, promises and streams as a base for asynchronous programming. The framework promises in combination with generators are used to build coroutines, which allow writing asynchronous code just like synchronous code, without any callbacks.

Event driven programming requires a different mindset, so Amp provides synchronous feeling APIs to ease the transition. A wide range of packages allows creating entire applications using non-blocking I/O and taking advantage of long running processes. It’s also possible to integrate Amp into existing applications for concurrent data access.

Amphp requires PHP 7.0 or higher, extensions are only needed if your app necessitates a high numbers of concurrent socket connections, usually this limit is configured up to 1024 file descriptors.

Motivation behind Amphp

Traditionally, PHP has a synchronous execution flow, doing one thing at a time. If you query a database, you send the query and wait for the response from the database server in a blocking manner. Once you have the response, you can start doing the next thing.

Instead of sitting there and doing nothing while waiting, we could already send the next database query, or do an HTTP call to an API.

Making use of the time we usually spend on waiting for I/O can speed up the total execution time. The following diagram shows the execution flow with dependencies between the different tasks, once executed sequentially and once concurrently.

Amphp Sequencial vs Concurrent
Amphp Sequencial vs Concurrent

Amp allows such concurrent I/O operations while keeping the cognitive load low by avoiding callbacks. Instead, the results of asynchronous operations can be awaited using yield resulting in code which is structured like traditional blocking I/O code while the actual execution flow is handled by Amp.

Getting started with Amphp

You can install Amphp with composer

composer require amphp/amp

Amphp came with basic components such the byte-stream, socket and dns, in addition to few additional ones for http such as http-server, http-server-session, http-server-router, http-client, websocket. Database components supported so far are MySQL, Redis and Postgres. Some additional components are also available for message queue, caching, process, parallel, uri, windows-registry, parallel function and parser.

Let’s test the http-server component :

composer require amphp/http-server
composer require amphp/cluster

Now get the Hello-World.php sample, notice that we have installed also the cluster component to get the Hello world sample working :

#!/usr/bin/env php
<?php

require \dirname(__DIR__) . "/vendor/autoload.php";

use Amp\ByteStream\ResourceOutputStream;
use Amp\Http\Server\HttpServer;
use Amp\Http\Server\RequestHandler\CallableRequestHandler;
use Amp\Http\Server\Response;
use Amp\Http\Status;
use Amp\Log\ConsoleFormatter;
use Amp\Log\StreamHandler;
use Amp\Socket;
use Monolog\Logger;

// Run this script, then visit http://localhost:1337/ or https://localhost:1338/ in your browser.

Amp\Loop::run(static function () {
    $cert = new Socket\Certificate(__DIR__ . '/../test/server.pem');

    $context = (new Socket\BindContext)
        ->withTlsContext((new Socket\ServerTlsContext)->withDefaultCertificate($cert));

    $servers = [
        Socket\Server::listen("0.0.0.0:1337"),
        Socket\Server::listen("[::]:1337"),
        Socket\Server::listen("0.0.0.0:1338", $context),
        Socket\Server::listen("[::]:1338", $context),
    ];

    $logHandler = new StreamHandler(new ResourceOutputStream(STDOUT));
    $logHandler->setFormatter(new ConsoleFormatter);
    $logger = new Logger('server');
    $logger->pushHandler($logHandler);

    $server = new HttpServer($servers, new CallableRequestHandler(static function () {
        return new Response(Status::OK, [
            "content-type" => "text/plain; charset=utf-8"
        ], "Hello, World!");
    }), $logger);

    yield $server->start();

    // Stop the server when SIGINT is received (this is technically optional, but it is best to call Server::stop()).
    Amp\Loop::onSignal(\SIGINT, static function (string $watcherId) use ($server) {
        Amp\Loop::cancel($watcherId);
        yield $server->stop();
    });
});

And from the console run $php examples/hello-world.php and it will start a web server listing to port localhost:1337 and will print simply “Hello World”. That’s a very complex code to just run a simple Hello world, but let’s try something more useful.

Notice that the Server uses a NullLogger by default. If you pass a Psr\Log\LoggerInterface instance to its constructor, you’ll get helpful log messages. Optionally you can use Options::withRequestLogContext() to enable passing the Request object to the logger in the $context array. This is disabled by default.

The http-server package only contains a relatively minimal functionality besides the Server and the basic interfaces and components. Further components have been moved to separate packages starting in the 0.8.0 release. You can import further functionality that’s ready to be used from the additional packages.

One of these addons is the http-server-static-content which is acceptable for lighter loads, however if you have high traffic website make sure you use a proper CDN.

Event Loops with Amphp

One of the advanced features of Amphp is Event Loops, which is available in multiple different implementations. However it’s fine to use NativeDriver in local development, but it’s highly recommended to have at least one of the extensions available in production. You will not have to choose which implementation to use, Amp will automatically select the best available driver.

A very simple example of Event Loops used as task scheduler, is the example below. The code will wait for entry from user input for 5 seconds, if the user input some text it will be displayed, otherwise the code will continue and will display NULL :

<?php
require \dirname(__DIR__) . "/vendor/autoload.php";

use Amp\Loop;

$myText = null;

function onInput($watcherId, $stream)
{
    global $myText;

    $myText = fgets($stream);
    stream_set_blocking(STDIN, true);

    Loop::cancel($watcherId);
    Loop::stop();
}

Loop::run(function () {
    echo "Please input some text: ";
    stream_set_blocking(STDIN, false);

    // Watch STDIN for input
    Loop::onReadable(STDIN, "onInput");

    // Impose a 5-second timeout if nothing is input
    Loop::delay($msDelay = 5000, "Amp\\Loop::stop");
});

var_dump($myText); // whatever you input on the CLI

// Continue doing regular synchronous things here.

Amphp is an open source software, released under an MIT license. More information at https://amphp.org/

LEAVE A REPLY

Please enter your comment!
Please enter your name here