GuzzleHTTP/Promises для PHP: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

GuzzleHTTP/Promises для PHP: полное руководство с примерами

Добро пожаловать в подробное руководство по библиотеке GuzzleHTTP/Promises для языка PHP. Эта библиотека является одной из ключевых в экосистеме Guzzle и предоставляет мощную реализацию паттерна "Promise" (Обещание) для асинхронного программирования. Если вы работаете с HTTP-запросами, параллельными задачами или просто хотите сделать ваш PHP-код более эффективным и читаемым — эта статья для вас.

1. Что это и зачем нужна

GuzzleHTTP/Promises — это PHP-библиотека, реализующая концепцию Promises (промисов) и асинхронного выполнения задач. Она является частью экосистемы Guzzle, но может использоваться полностью независимо от HTTP-клиента. Основная идея промисов заключается в том, что вы создаете объект, который представляет собой результат асинхронной операции, который может быть получен сейчас, позже или никогда. Это позволяет писать код, который не блокирует выполнение программы в ожидании завершения длительных операций.

Зачем это нужно в PHP? Традиционно PHP является синхронным языком, где каждая операция выполняется последовательно. Однако с ростом популярности микросервисов, API-интеграций и работы с внешними сервисами, возникла необходимость в эффективном управлении параллельными задачами. GuzzleHTTP/Promises решает эту проблему, позволяя запускать несколько операций одновременно (например, несколько HTTP-запросов к разным API) и обрабатывать их результаты по мере завершения, не дожидаясь выполнения всех по очереди.

Библиотека предоставляет полный набор инструментов для работы с асинхронным кодом: создание промисов, их объединение, цепочки вызовов, обработку ошибок и управление временем ожидания. Она является стандартом де-факто для асинхронной работы в PHP-сообществе и используется в таких популярных проектах, как Laravel, Symfony, WordPress (через плагины) и, конечно, сам Guzzle HTTP-клиент.

2. Установка

Установка библиотеки производится через Composer — стандартный менеджер пакетов для PHP. Выполните следующую команду в терминале в корне вашего проекта:

composer require guzzlehttp/promises

Если вы используете Guzzle HTTP-клиент, библиотека промисов уже включена в его зависимости, и отдельная установка не требуется. После установки убедитесь, что файл vendor/autoload.php подключен в вашем скрипте:

require 'vendor/autoload.php';

3. Быстрый старт — минимальный рабочий пример

Давайте создадим простейший пример работы с промисами. Мы создадим промис, который "выполняется" через 1 секунду и возвращает строку "Hello, World!":

use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\Utils;

// Создаем новый промис
$promise = new Promise(function () use (&$promise) {
    // Симуляция асинхронной операции
    usleep(1000000); // 1 секунда
    $promise->resolve('Hello, World!');
});

// Добавляем обработчик для успешного выполнения
$promise->then(function ($value) {
    echo "Получено: " . $value . PHP_EOL;
});

// Запускаем выполнение промиса
Utils::queue()->add(function () use ($promise) {
    $promise->wait();
});

// Запускаем очередь
Utils::queue()->run();

Этот пример демонстрирует базовую механику: создание промиса, его разрешение (resolve) и обработку результата через метод then.

4. Основные методы/функции/классы

Рассмотрим 8+ ключевых компонентов библиотеки с подробным описанием и примерами.

4.1. Класс GuzzleHttp\Promise\Promise

Сигнатура: new Promise(callable $waitFn = null, callable $canceller = null)

Что делает: Создает новый промис. Опционально принимает функцию ожидания ($waitFn), которая вызывается при вызове метода wait(), и функцию отмены ($canceller), вызываемую при отмене промиса.

use GuzzleHttp\Promise\Promise;

$promise = new Promise(function () use (&$promise) {
    // Асинхронная работа
    $result = file_get_contents('https://api.example.com/data');
    $promise->resolve($result);
});

$promise->then(function ($data) {
    echo "Данные получены: " . substr($data, 0, 100) . "...\n";
});

4.2. Метод then()

Сигнатура: $promise->then(callable $onFulfilled = null, callable $onRejected = null): PromiseInterface

Что делает: Добавляет обработчики для успешного выполнения ($onFulfilled) и для ошибки ($onRejected). Возвращает новый промис, позволяя строить цепочки.

$promise = new Promise(function () use (&$promise) {
    $promise->resolve(42);
});

$promise
    ->then(function ($value) {
        return $value * 2;
    })
    ->then(function ($value) {
        echo "Результат: " . $value . PHP_EOL; // Выведет: Результат: 84
    });

4.3. Метод otherwise()

Сигнатура: $promise->otherwise(callable $onRejected): PromiseInterface

Что делает: Специализированный метод для обработки только ошибок. Эквивалентен вызову then(null, $onRejected).

$promise = new Promise(function () use (&$promise) {
    $promise->reject(new \RuntimeException('Что-то пошло не так'));
});

$promise->otherwise(function (\Throwable $e) {
    echo "Ошибка: " . $e->getMessage() . PHP_EOL;
});

4.4. Функция GuzzleHttp\Promise\Utils::queue()

Сигнатура: Utils::queue(): TaskQueueInterface

Что делает: Возвращает глобальную очередь задач, которая управляет выполнением промисов. Позволяет добавлять задачи и запускать их выполнение.

use GuzzleHttp\Promise\Utils;

$queue = Utils::queue();
$queue->add(function () {
    echo "Задача 1 выполнена\n";
});
$queue->add(function () {
    echo "Задача 2 выполнена\n";
});

$queue->run(); // Выполнит обе задачи

4.5. Функция GuzzleHttp\Promise\Utils::all()

Сигнатура: Utils::all(array $promises): PromiseInterface

Что делает: Принимает массив промисов и возвращает новый промис, который разрешается, когда все переданные промисы будут выполнены. Если хотя бы один промис завершится ошибкой, результирующий промис также будет отклонен.

use GuzzleHttp\Promise\Utils;
use GuzzleHttp\Promise\Promise;

$promises = [
    'first' => new Promise(function (&$resolve) { $resolve('Результат 1'); }),
    'second' => new Promise(function (&$resolve) { $resolve('Результат 2'); }),
];

$allPromise = Utils::all($promises);
$allPromise->then(function (array $results) {
    print_r($results);
});

4.6. Функция GuzzleHttp\Promise\Utils::settle()

Сигнатура: Utils::settle(array $promises): PromiseInterface

Что делает: Аналогична all(), но ожидает завершения всех промисов независимо от того, были они выполнены или отклонены. Возвращает массив с состоянием каждого промиса.

$promises = [
    new Promise(function (&$resolve) { $resolve('OK'); }),
    new Promise(function (&$reject) { $reject(new \Exception('Ошибка')); }),
];

Utils::settle($promises)->then(function (array $results) {
    foreach ($results as $result) {
        echo "Состояние: " . $result['state'] . PHP_EOL;
        if ($result['state'] === 'fulfilled') {
            echo "Значение: " . $result['value'] . PHP_EOL;
        } else {
            echo "Причина: " . $result['reason']->getMessage() . PHP_EOL;
        }
    }
});

4.7. Функция GuzzleHttp\Promise\Utils::unwrap()

Сигнатура: Utils::unwrap(array $promises): array

Что делает: Синхронно ожидает выполнения всех промисов и возвращает массив результатов. Если какой-либо промис отклонен, выбрасывается исключение.

$promises = [
    new Promise(function (&$resolve) { $resolve(1); }),
    new Promise(function (&$resolve) { $resolve(2); }),
];

try {
    $results = Utils::unwrap($promises);
    print_r($results); // [1, 2]
} catch (\Throwable $e) {
    echo "Ошибка: " . $e->getMessage();
}

4.8. Функция GuzzleHttp\Promise\Utils::inspect()

Сигнатура: Utils::inspect(PromiseInterface $promise): array

Что делает: Позволяет "заглянуть" в состояние промиса без ожидания его выполнения. Возвращает массив с ключами state, value (если выполнен) или reason (если отклонен).

$promise = new Promise(function (&$resolve) { $resolve('Готово'); });
$state = Utils::inspect($promise);
echo "Состояние: " . $state['state'] . PHP_EOL; // pending (ожидание)

// После выполнения
$promise->wait();
$state = Utils::inspect($promise);
echo "Состояние: " . $state['state'] . PHP_EOL; // fulfilled
echo "Значение: " . $state['value'] . PHP_EOL; // Готово

4.9. Функция GuzzleHttp\Promise\Utils::task()

Сигнатура: Utils::task(callable $handler): PromiseInterface

Что делает: Создает промис, который выполняется асинхронно через очередь задач. Удобно для оборачивания синхронных функций в асинхронный контекст.

$promise = Utils::task(function () {
    // Тяжелая работа
    sleep(2);
    return 'Данные обработаны';
});

$promise->then(function ($result) {
    echo $result . PHP_EOL;
});

Utils::queue()->run();

4.10. Функция GuzzleHttp\Promise\Utils::isFulfilled()

Сигнатура: Utils::isFulfilled(PromiseInterface $promise): bool

Что делает: Проверяет, выполнен ли промис успешно.

$promise = new Promise(function (&$resolve) { $resolve('OK'); });
echo "Выполнен? " . (Utils::isFulfilled($promise) ? 'Да' : 'Нет') . PHP_EOL; // Нет

$promise->wait();
echo "Выполнен? " . (Utils::isFulfilled($promise) ? 'Да' : 'Нет') . PHP_EOL; // Да

5. Реальный практический пример

Рассмотрим реальный сценарий: нам нужно получить данные о погоде для трех городов из внешнего API, обработать их и сохранить в файл. Используем Guzzle HTTP-клиент вместе с промисами для параллельного выполнения запросов.

require 'vendor/autoload.php';

use GuzzleHttp\Client;
use GuzzleHttp\Promise\Utils;
use GuzzleHttp\Promise\Promise;

class WeatherAggregator
{
    private Client $httpClient;
    private array $cities;
    private string $apiKey;

    public function __construct(string $apiKey, array $cities)
    {
        $this->apiKey = $apiKey;
        $this->cities = $cities;
        $this->httpClient = new Client([
            'base_uri' => 'https://api.openweathermap.org/data/2.5/',
            'timeout' => 10.0,
        ]);
    }

    /**
     * Создает промис для получения погоды в одном городе
     */
    private function fetchWeatherForCity(string $city): Promise
    {
        $promise = new Promise(function () use ($city, &$promise) {
            try {
                $response = $this->httpClient->get('weather', [
                    'query' => [
                        'q' => $city,
                        'appid' => $this->apiKey,
                        'units' => 'metric',
                    ]
                ]);

                $data = json_decode($response->getBody(), true);
                $weather = [
                    'city' => $city,
                    'temperature' => $data['main']['temp'],
                    'humidity' => $data['main']['humidity'],
                    'description' => $data['weather'][0]['description'],
                    'timestamp' => date('Y-m-d H:i:s'),
                ];

                $promise->resolve($weather);
            } catch (\Throwable $e) {
                $promise->reject(new \RuntimeException(
                    "Ошибка получения погоды для {$city}: " . $e->getMessage()
                ));
            }
        });

        return $promise;
    }

    /**
     * Запускает параллельное получение погоды для всех городов
     */
    public function fetchAllWeather(): Promise
    {
        $promises = [];
        foreach ($this->cities as $city) {
            $promises[$city] = $this->fetchWeatherForCity($city);
        }

        return Utils::all($promises);
    }

    /**
     * Сохраняет результаты в файл
     */
    private                    

Рекомендаций