PHP Logoamphp/amp

amphp/amp is a foundational PHP library that provides low-level concurrency primitives, primarily focusing on 'Promises' and 'Deferrals', to enable asynchronous, non-blocking programming. It allows developers to write efficient I/O-bound applications without relying on multi-threading.

At its core, `amphp/amp` introduces:

1. Promise: A `Promise` represents the eventual result of an asynchronous operation. It can be in one of three states: 'pending' (operation in progress), 'resolved' (operation completed successfully with a value), or 'failed' (operation failed with an exception). Promises allow you to register callbacks that will be executed once the operation completes, regardless of success or failure.
2. Deferred: A `Deferred` is used to create and control a `Promise`. It provides methods (`resolve()` and `fail()`) to transition the associated promise from the 'pending' state to either 'resolved' or 'failed'. This is how the outcome of an asynchronous operation is communicated back to its promise.

`amphp/amp` works in conjunction with an event loop (typically `amphp/loop`). When an asynchronous task is initiated, it immediately returns a `Promise`, allowing the program's execution to continue. The actual work is then scheduled on the event loop. Once the asynchronous operation completes, the event loop triggers the promise to resolve or fail, executing any registered `onResolve` (or `then`, `catch`) callbacks.

This approach significantly enhances application responsiveness and scalability for I/O-bound tasks (like database queries, API calls, file system operations) by allowing a single PHP process to manage many concurrent operations efficiently, leading to better resource utilization and higher throughput.

Example Code

```php
<?php

// To run this example, ensure you have Composer installed and run the following commands:
// composer require amphp/amp amphp/loop
// php your_script_name.php

require __DIR__ . '/vendor/autoload.php';

use Amp\Deferred;
use Amp\Delayed;
use Amp\Loop;
use Amp\Promise;

/
 * Simulates an asynchronous operation using Amp\call().
 * Amp\call() wraps a generator function, allowing you to 'yield' promises
 * and have their results 'sent' back into the generator function upon completion.
 */
function simulateAsyncOperation(string $taskName, int $delayMs): Promise
{
    return Amp\call(function () use ($taskName, $delayMs) {
        echo "[" . date('H:i:s') . "] Task '{$taskName}' started, waiting for {$delayMs}ms...\n";
        // Yielding a Delayed promise pauses *this specific generator* until the delay expires,
        // but the event loop continues to run other tasks concurrently.
        yield new Delayed($delayMs);
        echo "[" . date('H:i:s') . "] Task '{$taskName}' completed after {$delayMs}ms.\n";
        return "Result for {$taskName}";
    });
}

/
 * Simulates an asynchronous operation using a Deferred object directly.
 * This is useful when integrating with external asynchronous APIs or low-level event listeners.
 */
function simulateAsyncOperationWithDeferred(string $taskName, int $delayMs): Promise
{
    $deferred = new Deferred();

    // Schedule a callback on the event loop to resolve/fail the deferred after a delay.
    Loop::delay($delayMs, function () use ($taskName, $delayMs, $deferred) {
        // Simulate a potential failure (e.g., a network error)
        if (rand(0, 10) < 3) { // 30% chance of failure
            echo "[" . date('H:i:s') . "] Task '{$taskName}' FAILED after {$delayMs}ms!\n";
            $deferred->fail(new Exception("Random failure for {$taskName}!"));
        } else {
            echo "[" . date('H:i:s') . "] Task '{$taskName}' completed via Deferred after {$delayMs}ms.\n";
            $deferred->resolve("Deferred Result for {$taskName}");
        }
    });

    return $deferred->promise();
}

// The entry point for the Amphp event loop.
// All asynchronous operations must be initiated within this callback.
Loop::run(function () {
    echo "[" . date('H:i:s') . "] Starting Amphp/Amp example...\n";

    // Initiate multiple asynchronous operations concurrently.
    // Each call returns a Promise immediately.
    $promiseA = simulateAsyncOperation('Task A', 1000); // 1 second delay
    $promiseB = simulateAsyncOperation('Task B', 500);  // 0.5 second delay
    $promiseC = simulateAsyncOperationWithDeferred('Task C', 1500); // 1.5 second delay, using Deferred
    $promiseD = simulateAsyncOperation('Task D', 700); // 0.7 second delay

    // Attach callbacks to handle the resolution or failure of each promise.
    // The onResolve() method takes a callback that receives either an exception (if failed) or a value (if resolved).
    $promiseA->onResolve(function (?Throwable $error, mixed $value) {
        if ($error) {
            echo "[" . date('H:i:s') . "] Task A Handler: Failed - {$error->getMessage()}\n";
        } else {
            echo "[" . date('H:i:s') . "] Task A Handler: Resolved - {$value}\n";
        }
    });

    $promiseB->onResolve(function (?Throwable $error, mixed $value) {
        if ($error) {
            echo "[" . date('H:i:s') . "] Task B Handler: Failed - {$error->getMessage()}\n";
        } else {
            echo "[" . date('H:i:s') . "] Task B Handler: Resolved - {$value}\n";
        }
    });

    $promiseC->onResolve(function (?Throwable $error, mixed $value) {
        if ($error) {
            echo "[" . date('H:i:s') . "] Task C Handler: Failed - {$error->getMessage()}\n";
        } else {
            echo "[" . date('H:i:s') . "] Task C Handler: Resolved - {$value}\n";
        }
    });

    $promiseD->onResolve(function (?Throwable $error, mixed $value) {
        if ($error) {
            echo "[" . date('H:i:s') . "] Task D Handler: Failed - {$error->getMessage()}\n";
        } else {
            echo "[" . date('H:i:s') . "] Task D Handler: Resolved - {$value}\n";
        }
    });

    echo "[" . date('H:i:s') . "] All asynchronous tasks initiated. Script continues execution while tasks run concurrently in the background.\n";

    // To wait for all promises to complete before the event loop exits, you could use Amp\Promise\all()
    // or Amp\Promise\any(). For this example, individual onResolve handlers are sufficient to demonstrate.
    // If you uncomment the line below, the script would wait for ALL promises to complete before printing "All tasks have completed."
    // yield Amp\Promise\all([$promiseA, $promiseB, $promiseC, $promiseD]);
    // echo "[" . date('H:i:s') . "] All tasks have completed.\n";
});

echo "[" . date('H:i:s') . "] Event loop finished. This message appears after Loop::run() completes.\n";
```