amphp/amp

repository·3.x·Indexed 26 days ago

https://github.com/amphp/amp

A library providing fundamental primitives for asynchronous programming in PHP, such as Futures and Cancellations. It leverages PHP 8.1 Fibers to enable non-blocking, concurrent code with a synchronous-looking syntax and utilizes revolt/event-loop as its underlying event loop implementation.

Tokens
2.6K
Snippets
5
Records
13
Agent score
89%

What's inside amphp/amp

  1. Overview of amphp/amp

    3.x
    AMPHP is a collection of event-driven libraries for PHP designed for concurrency using Fibers. The amphp/amp package specifically provides Future and Cancellation primitives. It uses Revolt as the underlying event loop implementation rather than shipping its own.
  2. Run tasks concurrently with Amp\async() and Future::await()

    3.x
    Amp uses PHP 8.1 Fibers to allow writing asynchronous code that looks like synchronous, blocking code. To run tasks concurrently, use Amp\async(). To retrieve the result of a concurrent task, use Future::await().
  3. Install amphp/amp and Revolt

    3.x

    Install the core amphp/amp package using Composer. Since amphp/amp relies on an event loop for scheduling, it is highly recommended to explicitly require revolt/event-loop as well.

    composer require amphp/amp
    composer require revolt/event-loop
  4. Use Coroutines with Amp\async()

    3.x

    Coroutines in Amp are interruptible functions implemented using PHP Fibers. You can start a new coroutine (a new fiber with an independent call stack) using Amp\async(). This allows you to run asynchronous tasks without the boilerplate required by older generator-based implementations.

    Callbacks registered on the Revolt event-loop are automatically run as coroutines, making it safe to suspend within them.

    <?php
    
    require __DIR__ . '/vendor/autoload.php';
    
    Amp\async(function () {
        print '++ Executing callback passed to async()' . PHP_EOL;
    
        Amp\[delay(3);
    
        print '++ Finished callback passed to async()' . PHP_EOL;
    });
    
    print '++ Suspending to event loop...' . PHP_EOL;
    Amp\delay(5);
    
    print '++ Script end' . PHP_EOL;
  5. Use Amp utility functions

    3.x

    Amp\delay(float $timeout, bool $reference = true, ?Cancellation $cancellation = null): void

    Suspends the current coroutine until the timeout elapses or the cancellation is triggered.

    Amp\trapSignal(int|array $signals, bool $reference = true, ?Cancellation $cancellation = null): int

    Suspends the current coroutine until one of the specified signals is received. Returns the signal number.

    Amp\now(): float

    Returns a high-resolution time relative to an arbitrary point, useful for calculating time differences independent of wall-time.

    Amp\weakClosure(Closure $closure): Closure

    Wraps a closure in a weak-reference to any $this object it holds. This prevents circular references that delay garbage collection.

  6. Run repeating tasks with Amp\Interval

    3.x

    The Amp\Interval class registers a callback in the event-loop that is invoked within a new coroutine every specified number of seconds. The interval runs until Interval::disable() is called or the object is destroyed.

    Tip: When storing an Interval inside another class, use Amp\weakClosure() for the callback to avoid circular references that prevent garbage collection.

    // Creates a callback which is invoked every 0.5s
    // unless disabled or the object is destroyed.
    $interval = new Interval(0.5, function (): void {
        // ...
    });
    
    // Disable the repeating timer, stopping future
    // invocations until enabled again.
    $interval->disable();
    
    // Enable the repeating timer. The callback will
    // not be invoked until the given timeout has elapsed.
    $interval->enable();
  7. Work with Amp\Future

    3.x

    A Future represents the eventual result of an asynchronous operation. It can be in one of three states: Completed (success), Errored (exception thrown), or Pending.

    Key Methods

    • await(): Suspends the current coroutine until the future completes or errors. Returns the result or throws the exception.
    • map(Closure $map): Future: Attaches a callback for successful completion. Returns a new future with the callback's result.
    • catch(Closure $catch): Future: Attaches a callback for errors. Returns a new future with the caught exception handled.
    • finally(Closure $finally): Future: Attaches a callback that always runs. Returns a new future with the original result or error.
  8. Create a Future using Amp\DeferredFuture

    3.x

    An Amp\DeferredFuture is used to manually complete a pending Future. This is an advanced API typically used for internal state within an operation.

    1. Create a DeferredFuture instance.
    2. Return its future via $deferred->getFuture() to the caller.
    3. Use $deferred->complete($value) or $deferred->error($throwable) to resolve the future.

    Warning: Do not pass DeferredFuture objects around; only pass the Future they produce.

    <?php // Example async producer using DeferredFuture
    
    use Amp\Future;
    use Revolt\EventLoop;
    
    require __DIR__ . '/vendor/autoload.php';
    
    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)
  9. Combine multiple Futures with Amp\Future combinators

    3.x

    Use combinators to manage multiple concurrent asynchronous operations:

    • Amp\Future\await($iterable, $cancellation): Awaits all futures in an iterable. If any fail, the operation aborts with that exception. Returns an array of results.
    • Amp\Future\awaitAnyN($count, $iterable, $cancellation): Returns once exactly $count instances complete successfully. Tolerates individual errors.
    • Amp\Future\awaitAll($iterable, $cancellation): Awaits all futures and returns an array in the format [$errors, $values].
    • Amp\Future\awaitFirst($iterable, $cancellation): Unwraps the first completed Future (success or error).
    • Amp\Future\awaitAny($iterable, $cancellation): Unwraps the first successfully completed Future.
    <?php
    
    use Amp\Future;
    use Amp\Http\Client\HttpClientBuilder;
    use Amp\Http\Client\Request;
    
    require __DIR__ . '/vendor/autoload.php';
    
    $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) {
        echo $e->getMessage(), "\n";
    }
  10. Implement Cancellation in operations

    3.x

    Operations can support cancellation by accepting a Cancellation object. Use $cancellation->throwIfRequested() to fail the operation with a CancelledException when requested, or use $cancellation->subscribe() to react to cancellation events.

    Cancellation Implementations

    • Amp\TimeoutCancellation: Automatically cancels after a specified number of seconds.
    • Amp\SignalCancellation: Automatically cancels when a specific process signal (e.g., SIGINT) is received.
    • Amp\DeferredCancellation: Allows manual cancellation via $deferredCancellation->cancel(). Best for custom logic.
    • Amp\NullCancellation: A non-cancelling object used to avoid null checks.
    • Amp\CompositeCancellation: Combines multiple cancellations; if any one is cancelled, the composite is cancelled.
  11. Handle operation timeouts with TimeoutException

    3.x
    The Amp\TimeoutException is thrown when an operation exceeds its allotted time, specifically when a TimeoutCancellation expires. You can catch this exception to handle scenarios where an asynchronous operation failed to complete within the expected duration.
  12. Handle SignalException when using SignalCancellation

    3.x
    The Amp\SignalException is thrown when an asynchronous operation is interrupted by a system signal (e.g., SIGINT) via a SignalCancellation. It is typically used as the underlying cause for a CancelledException when a SignalCancellation is triggered. You can catch this exception to handle graceful shutdowns or interruptions caused by external signals.