Top Related Projects
Event-driven, non-blocking I/O with PHP.
Cross-platform asynchronous I/O
Node.js JavaScript runtime ✨🐢🚀✨
🚀 Coroutine-based concurrency library for PHP
PPM is a process manager, supercharger and load balancer for modern PHP applications.
Asynchronous & Fault-tolerant PHP Framework for Distributed Applications.
Quick Overview
Amp is a non-blocking concurrency framework for PHP. It provides a powerful set of tools for writing asynchronous code, including coroutines, promises, and event loops. Amp aims to make concurrent programming in PHP more accessible and efficient.
Pros
- Enables writing non-blocking, concurrent PHP code
- Provides a rich set of tools and abstractions for asynchronous programming
- Improves performance for I/O-bound operations
- Well-documented with comprehensive examples
Cons
- Steep learning curve for developers new to asynchronous programming
- Requires a shift in thinking from traditional synchronous PHP code
- Limited compatibility with existing synchronous PHP libraries
- May introduce complexity in simpler applications where concurrency is not necessary
Code Examples
- Creating and resolving a promise:
use Amp\Promise;
use function Amp\async;
use function Amp\Future\await;
$promise = async(function () {
return "Hello, World!";
});
$result = await($promise);
echo $result; // Outputs: Hello, World!
- Using coroutines for asynchronous operations:
use Amp\Http\Client\HttpClientBuilder;
use function Amp\async;
use function Amp\Future\await;
$client = HttpClientBuilder::buildDefault();
$coroutine = async(function () use ($client) {
$response = yield $client->request('GET', 'https://example.com');
$body = yield $response->getBody()->buffer();
return $body;
});
$result = await($coroutine);
echo $result; // Outputs the content of example.com
- Concurrent execution of multiple tasks:
use function Amp\async;
use function Amp\Future\await;
$tasks = [
async(fn() => sleep(2) && "Task 1"),
async(fn() => sleep(1) && "Task 2"),
async(fn() => sleep(3) && "Task 3"),
];
$results = await($tasks);
print_r($results); // Outputs results in the order of completion
Getting Started
To start using Amp, first install it via Composer:
composer require amphp/amp
Then, you can begin writing asynchronous code using Amp's primitives:
use function Amp\async;
use function Amp\Future\await;
$result = await(async(function () {
// Your asynchronous code here
return "Async operation complete";
}));
echo $result;
For more detailed information and advanced usage, refer to the official Amp documentation at https://amphp.org/amp/.
Competitor Comparisons
Event-driven, non-blocking I/O with PHP.
Pros of ReactPHP
- More mature and established project with a larger community
- Wider ecosystem of packages and extensions
- Better documentation and more comprehensive examples
Cons of ReactPHP
- Can be more complex to use for beginners
- Slightly higher memory usage in some scenarios
- Less focus on performance optimizations compared to Amp
Code Comparison
ReactPHP example:
$loop = React\EventLoop\Factory::create();
$socket = new React\Socket\Server('127.0.0.1:8080', $loop);
$socket->on('connection', function (React\Socket\ConnectionInterface $conn) {
$conn->write("Hello!\n");
});
$loop->run();
Amp example:
Loop::run(function () {
$server = Socket\listen("127.0.0.1:8080");
while ($socket = yield $server->accept()) {
$socket->write("Hello!\n");
}
});
Both ReactPHP and Amp are powerful libraries for asynchronous PHP programming. ReactPHP offers a more extensive ecosystem and better documentation, making it easier for developers to find solutions and integrations. However, Amp focuses more on performance optimizations and has a slightly simpler API in some cases. The choice between the two often depends on specific project requirements and developer preferences.
Cross-platform asynchronous I/O
Pros of libuv
- Written in C, offering lower-level performance and efficiency
- Cross-platform support for asynchronous I/O operations
- Widely used and battle-tested in popular projects like Node.js
Cons of libuv
- Requires more complex setup and integration for PHP developers
- Steeper learning curve due to its lower-level nature
- Less idiomatic for PHP developers compared to amp
Code Comparison
libuv (C):
uv_loop_t *loop = malloc(sizeof(uv_loop_t));
uv_loop_init(loop);
uv_run(loop, UV_RUN_DEFAULT);
uv_loop_close(loop);
free(loop);
amp (PHP):
Loop::run(function () {
yield someFunctionReturningPromise();
});
amp is a PHP library for writing asynchronous code using coroutines, while libuv is a C library for asynchronous I/O. amp provides a more PHP-friendly API, making it easier for PHP developers to write asynchronous code. libuv, on the other hand, offers lower-level control and is used as a foundation for many other libraries and frameworks across multiple languages.
Node.js JavaScript runtime ✨🐢🚀✨
Pros of Node.js
- Larger ecosystem with more packages and community support
- Better performance for CPU-intensive tasks
- Wider adoption in industry, leading to more job opportunities
Cons of Node.js
- Blocking I/O operations can impact overall performance
- Callback-based programming can lead to "callback hell"
- Less suitable for long-running, high-concurrency applications
Code Comparison
Node.js (asynchronous file read):
const fs = require('fs');
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
});
Amp (asynchronous file read):
use Amp\File;
$contents = yield File\read('file.txt');
echo $contents;
Key Differences
- Amp focuses on asynchronous programming in PHP, while Node.js is a JavaScript runtime
- Amp uses coroutines and generators for async operations, Node.js primarily uses callbacks and Promises
- Amp is designed for high-concurrency applications, while Node.js excels in rapid prototyping and microservices
Use Cases
- Node.js: Web applications, APIs, real-time services, command-line tools
- Amp: High-concurrency PHP applications, long-running processes, event-driven systems
Community and Ecosystem
- Node.js has a larger, more diverse ecosystem with npm
- Amp has a smaller but growing community focused on asynchronous PHP development
🚀 Coroutine-based concurrency library for PHP
Pros of swoole-src
- Higher performance due to its C-based implementation
- Built-in support for WebSockets, HTTP server, and TCP/UDP sockets
- Lower memory footprint for handling concurrent connections
Cons of swoole-src
- Requires PHP extension installation and server configuration
- Limited compatibility with existing PHP frameworks and libraries
- Steeper learning curve for developers unfamiliar with asynchronous programming
Code Comparison
Swoole-src example:
$server = new Swoole\HTTP\Server('127.0.0.1', 9501);
$server->on('request', function ($request, $response) {
$response->end('<h1>Hello World</h1>');
});
$server->start();
Amp example:
Loop::run(function () {
$server = Socket\listen('127.0.0.1:1337');
while ($socket = yield $server->accept()) {
$client = new Http\Server\Request($socket);
$client->respond(new Http\Server\Response(Status::OK, ['content-type' => 'text/plain'], 'Hello, World!'));
}
});
Both libraries aim to provide asynchronous programming capabilities for PHP, but they differ in their implementation and approach. Swoole-src offers higher performance and built-in features at the cost of compatibility and ease of use, while Amp focuses on pure PHP implementation and better integration with existing ecosystems.
PPM is a process manager, supercharger and load balancer for modern PHP applications.
Pros of php-pm
- Designed specifically for PHP, offering optimized performance for PHP applications
- Supports multiple web server adapters (e.g., Nginx, Apache)
- Provides built-in process management and worker scaling
Cons of php-pm
- Limited to PHP applications, lacking language-agnostic flexibility
- Less active development and community support compared to amp
- May require more configuration and setup for complex applications
Code Comparison
amp example:
Loop::run(function () {
$server = Socket\listen("0.0.0.0:1337");
while ($socket = yield $server->accept()) {
$client = new Client($socket);
$client->start();
}
});
php-pm example:
$loop = \React\EventLoop\Factory::create();
$socket = new \React\Socket\Server('0.0.0.0:8080', $loop);
$http = new \React\Http\Server($socket);
$http->on('request', function ($request, $response) {
$response->writeHead(200, ['Content-Type' => 'text/plain']);
$response->end("Hello World!\n");
});
$loop->run();
Both examples demonstrate creating a simple HTTP server, but amp uses its own event loop and coroutine-based approach, while php-pm relies on ReactPHP components for similar functionality.
Asynchronous & Fault-tolerant PHP Framework for Distributed Applications.
Pros of Kraken
- Comprehensive framework for building distributed applications
- Built-in support for microservices architecture
- Includes tools for service discovery and load balancing
Cons of Kraken
- Steeper learning curve due to its complexity
- Less active development and community support
- More opinionated, potentially limiting flexibility
Code Comparison
Kraken example:
$server = new \Kraken\Network\Http\HttpServer(8080);
$server->addListener(new \Kraken\Network\Http\HttpServerListener());
$server->start();
Amp example:
Loop::run(function () {
$server = Socket\listen("0.0.0.0:8080");
while ($socket = yield $server->accept()) {
// Handle connection
}
});
Summary
Kraken is a more comprehensive framework for building distributed systems, offering built-in support for microservices and related tools. However, it has a steeper learning curve and less active community support compared to Amp. Amp, on the other hand, is a more lightweight and flexible asynchronous programming library, focusing on providing core async primitives rather than a full-fledged framework. The choice between the two depends on the specific needs of your project and your preference for a more structured framework versus a flexible library.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
amphp/amp
AMPHP is a collection of event-driven libraries for PHP designed with fibers and concurrency in mind.
amphp/amp
specifically provides futures and cancellations as fundamental primitives for asynchronous programming.
We're now using Revolt instead of shipping an event loop implementation with amphp/amp
.
Amp makes heavy use of fibers shipped with PHP 8.1 to write asynchronous code just like synchronous, blocking code. In
contrast to earlier versions, there's no need for generator based coroutines or callbacks. Similar to threads, each
fiber has its own call stack, but fibers are scheduled cooperatively by the event loop. Use Amp\async()
to run things
concurrently.
Motivation
Traditionally, PHP follows a sequential execution model. The PHP engine executes one line after the other in sequential order. Often, however, programs consist of multiple independent sub-programs which can be executed concurrently.
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. Let's make use of the time we usually spend on waiting for I/O!
Revolt allows such concurrent I/O operations. We keep the cognitive load low by avoiding callbacks.
Our APIs can be used like any other library, except that things also work concurrently, because we use non-blocking I/O under the hood.
Run things concurrently using Amp\async()
and await the result using Future::await()
where and when you need it!
There have been various techniques for implementing concurrency in PHP over the years, e.g. callbacks and generators shipped in PHP 5. These approaches suffered from the "What color is your function" problem, which we solved by shipping Fibers with PHP 8.1. They allow for concurrency with multiple independent call stacks.
Fibers are cooperatively scheduled by the event-loop, which is why they're also called coroutines. It's important to understand that only one coroutine is running at any given time, all other coroutines are suspended in the meantime.
You can compare coroutines to a computer running multiple programs using a single CPU core. Each program gets a timeslot to execute. Coroutines, however, are not preemptive. They don't get their fixed timeslot. They have to voluntarily give up control to the event loop.
Any blocking I/O function blocks the entire process while waiting for I/O. You'll want to avoid them. If you haven't read the installation guide, have a look at the Hello World example that demonstrates the effect of blocking functions. The libraries provided by AMPHP avoid blocking for I/O.
Installation
This package can be installed as a Composer dependency.
composer require amphp/amp
If you use this library, it's very likely you want to schedule events using Revolt, which you should require separately, even if it's automatically installed as a dependency.
composer require revolt/event-loop
These packages provide the basic building blocks for asynchronous / concurrent applications in PHP. We offer a lot of packages building on top of these, e.g.
amphp/byte-stream
providing a stream abstractionamphp/socket
providing a socket layer for UDP and TCP including TLSamphp/parallel
providing parallel processing to utilize multiple CPU cores and offload blocking operationsamphp/http-client
providing an HTTP/1.1 and HTTP/2 clientamphp/http-server
providing an HTTP/1.1 and HTTP/2 application serveramphp/mysql
andamphp/postgres
for non-blocking database access- and many more packages
Requirements
This package requires PHP 8.1 or later. No extensions required!
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.
Usage
Coroutines
Coroutines are interruptible functions. In PHP, they can be implemented using fibers.
Note Previous versions of Amp used generators for a similar purpose, but fibers can be interrupted anywhere in the call stack making previous boilerplate like
Amp\call()
unnecessary.
At any given time, only one fiber is running. When a coroutine suspends, execution of the coroutine is temporarily
interrupted, allowing other tasks to be run. Execution is resumed once a timer expires, stream operations are possible,
or any awaited Future
completes.
Low-level suspension and resumption of coroutines is handled by Revolt's Suspension
API.
<?php
require __DIR__ . '/vendor/autoload.php';
use Revolt\EventLoop;
$suspension = EventLoop::getSuspension();
EventLoop::delay(5, function () use ($suspension): void {
print '++ Executing callback created by EventLoop::delay()' . PHP_EOL;
$suspension->resume(null);
});
print '++ Suspending to event loop...' . PHP_EOL;
$suspension->suspend();
print '++ Script end' . PHP_EOL;
Callbacks registered on the Revolt event-loop are automatically run as coroutines and it's safe to suspend them. Apart from the event-loop API, Amp\async()
can be used to start an independent call stack.
<?php
use function Amp\delay;
require __DIR__ . '/vendor/autoload.php';
Amp\async(function () {
print '++ Executing callback passed to async()' . PHP_EOL;
delay(3);
print '++ Finished callback passed to async()' . PHP_EOL;
});
print '++ Suspending to event loop...' . PHP_EOL;
delay(5);
print '++ Script end' . PHP_EOL;
Future
A Future
is an object representing the eventual result of an asynchronous operation. There are three states:
- Completed successfully: The future has been completed successfully.
- Errored: The future failed with an exception.
- Pending: The future is still pending.
A successfully completed future is analog to a return value, while an errored future is analog to throwing an exception.
One way to approach asynchronous APIs is using callbacks that are passed when the operation is started and called once it completes:
doSomething(function ($error, $value) {
if ($error) {
/* ... */
} else {
/* ... */
}
});
The callback approach has several drawbacks.
- Passing callbacks and doing further actions in them that depend on the result of the first action gets messy really quickly.
- An explicit callback is required as input parameter to the function, and the return value is simply unused. There's no way to use this API without involving a callback.
That's where futures come into play.
They're placeholders for the result that are returned like any other return value.
The caller has the choice of awaiting the result using Future::await()
or registering one or several callbacks.
try {
$value = doSomething()->await();
} catch (...) {
/* ... */
}
Combinators
In concurrent applications, there will be multiple futures, where you might want to await them all or just the first one.
await
Amp\Future\await($iterable, $cancellation)
awaits all Future
objects of an iterable
. If one of the Future
instances errors, the operation
will be aborted with that exception. Otherwise, the result is an array matching keys from the input iterable
to their
completion values.
The await()
combinator is extremely powerful because it allows you to concurrently execute many asynchronous operations
at the same time. Let's look at an example using amphp/http-client
to
retrieve multiple HTTP resources concurrently:
<?php
use Amp\Future;
use Amp\Http\Client\HttpClientBuilder;
use Amp\Http\Client\Request;
$httpClient = HttpClientBuilder::buildDefault();
$uris = [
"google" => "https://www.google.com",
"news" => "https://news.google.com",
"bing" => "https://www.bing.com",
"yahoo" => "https://www.yahoo.com",
];
try {
$responses = Future\await(array_map(function ($uri) use ($httpClient) {
return Amp\async(fn () => $httpClient->request(new Request($uri, 'HEAD')));
}, $uris));
foreach ($responses as $key => $response) {
printf(
"%s | HTTP/%s %d %s\n",
$key,
$response->getProtocolVersion(),
$response->getStatus(),
$response->getReason()
);
}
} catch (Exception $e) {
// If any one of the requests fails the combo will fail
echo $e->getMessage(), "\n";
}
awaitAnyN
Amp\Future\awaitAnyN($count, $iterable, $cancellation)
is the same as await()
except that it tolerates individual errors. A result is returned once
exactly $count
instances in the iterable
complete successfully. The return value is an array of values. The
individual keys in the component array are preserved from the iterable
passed to the function for evaluation.
awaitAll
Amp\Promise\awaitAll($iterable, $cancellation)
awaits all futures and returns their results as [$errors, $values]
array.
awaitFirst
Amp\Promise\awaitFirst($iterable, $cancellation)
unwraps the first completed Future
, whether successfully completed or errored.
awaitAny
Amp\Promise\awaitAny($iterable, $cancellation)
unwraps the first successfully completed Future
.
Future Creation
Futures can be created in several ways. Most code will use Amp\async()
which takes a function and runs it as coroutine in another Fiber.
Sometimes an interface mandates a Future
to be returned, but results are immediately available, e.g. because they're cached.
In these cases Future::complete(mixed)
and Future::error(Throwable)
can be used to construct an immediately completed Future
.
DeferredFuture
Note The
DeferredFuture
API described below is an advanced API that many applications probably don't need. UseAmp\async()
or combinators instead where possible.
Amp\DeferredFuture
is responsible for completing a pending Future
.
You create a Amp\DeferredFuture
and uses its getFuture
method to return an Amp\Future
to the caller.
Once result is ready, you complete the Future
held by the caller using complete
or error
on the linked DeferredFuture
.
final class DeferredFuture
{
public function getFuture(): Future;
public function complete(mixed $value = null);
public function error(Throwable $throwable);
}
Warning If you're passing
DeferredFuture
objects around, you're probably doing something wrong. They're supposed to be internal state of your operation.
Warning You can't complete a future with another future; Use
Future::await()
before callingDeferredFuture::complete()
in such cases.
Here's a simple example of an asynchronous value producer asyncMultiply()
creating a DeferredFuture
and returning the
associated Future
to its caller.
<?php // Example async producer using DeferredFuture
use Revolt\EventLoop;
function asyncMultiply(int $x, int $y): Future
{
$deferred = new Amp\DeferredFuture;
// Complete the async result one second from now
EventLoop::delay(1, function () use ($deferred, $x, $y) {
$deferred->complete($x * $y);
});
return $deferred->getFuture();
}
$future = asyncMultiply(6, 7);
$result = $future->await();
var_dump($result); // int(42)
Cancellation
Every operation that supports cancellation accepts an instance of Cancellation
as argument.
Cancellations are objects that allow registering handlers to subscribe to cancellation requests.
These objects are passed down to sub-operations or have to be handled by the operation itself.
$cancellation->throwIfRequested()
can be used to fail the current operation with a CancelledException
once cancellation has been requested.
While throwIfRequested()
works well, some operations might want to subscribe with a callback instead. They can do so
using Cancellation::subscribe()
to subscribe any cancellation requests that might happen.
The caller creates a Cancellation
by using one of the implementations below.
Note Cancellations are advisory only. A DNS resolver might ignore cancellation requests after the query has been sent as the response has to be processed anyway and can still be cached. An HTTP client might continue a nearly finished HTTP request to reuse the connection, but might abort a chunked encoding response as it cannot know whether continuing is actually cheaper than aborting.
TimeoutCancellation
A TimeoutCancellations
automatically cancels itself after the specified number of seconds.
request("...", new Amp\TimeoutCancellation(30));
SignalCancellation
A SignalCancellation
automatically cancels itself after a specified signal has been received by the current process.
request("...", new Amp\SignalCancellation(SIGINT));
DeferredCancellation
A DeferredCancellation
allows manual cancellation with the call of a method.
This is the preferred way if you need to register some custom callback somewhere instead of shipping your own implementation.
Only the caller has access to the DeferredCancellation
and can cancel the operation using DeferredCancellation::cancel()
.
$deferredCancellation = new Amp\DeferredCancellation();
// Register some custom callback somewhere
onSomeEvent(fn () => $deferredCancellation->cancel());
request("...", $deferredCancellation->getCancellation());
NullCancellation
A NullCancellation
will never be cancelled.
Cancellation is often optional, which is usually implemented by making the parameter nullable.
To avoid guards like if ($cancellation)
, a NullCancellation
can be used instead.
$cancellation ??= new NullCancellationToken();
CompositeCancellation
A CompositeCancellation
combines multiple independent cancellation objects. If any of these cancellations is cancelled, the CompositeCancellation
itself will be cancelled.
Versioning
amphp/amp
follows the semver semantic versioning specification like all other amphp
packages.
Compatible Packages
Compatible packages should use the amphp
topic on GitHub.
Security
If you discover any security related issues, please email me@kelunik.com
instead of using the
issue tracker.
License
The MIT License (MIT). Please see LICENSE
for more information.
Top Related Projects
Event-driven, non-blocking I/O with PHP.
Cross-platform asynchronous I/O
Node.js JavaScript runtime ✨🐢🚀✨
🚀 Coroutine-based concurrency library for PHP
PPM is a process manager, supercharger and load balancer for modern PHP applications.
Asynchronous & Fault-tolerant PHP Framework for Distributed Applications.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot