diff --git a/_translations/cookbook/po/ru/README.md.po b/_translations/cookbook/po/ru/README.md.po index 98a3b739..e1d8be6f 100644 --- a/_translations/cookbook/po/ru/README.md.po +++ b/_translations/cookbook/po/ru/README.md.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" -"POT-Creation-Date: 2025-10-08 11:47+0000\n" +"POT-Creation-Date: 2025-10-19 11:34+0000\n" "PO-Revision-Date: 2025-09-05 15:05+0500\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -59,6 +59,11 @@ msgstr "[Вступление](preface.md)" msgid "[Structuring code by use-case with vertical slices](organizing-code/structuring-by-use-case-with-vertical-slices.md)" msgstr "[Организация кода по сценариям использования вертикальными слайсами](organizing-code/structuring-by-use-case-with-vertical-slices.md)" +#. type: Bullet: '- ' +#: ../../cookbook/en/README.md +msgid "[Making HTTP requests](making-http-requests.md)" +msgstr "" + #. type: Bullet: '- ' #: ../../cookbook/en/README.md msgid "[Disabling CSRF protection](disabling-csrf-protection.md)" diff --git a/_translations/cookbook/pot/README.md.pot b/_translations/cookbook/pot/README.md.pot index 3ddeede0..dc79302d 100644 --- a/_translations/cookbook/pot/README.md.pot +++ b/_translations/cookbook/pot/README.md.pot @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2025-10-08 11:47+0000\n" +"POT-Creation-Date: 2025-10-19 11:34+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -65,6 +65,11 @@ msgid "" "structuring-by-use-case-with-vertical-slices.md)" msgstr "" +#. type: Bullet: '- ' +#: ../../cookbook/en/README.md +msgid "[Making HTTP requests](making-http-requests.md)" +msgstr "" + #. type: Bullet: '- ' #: ../../cookbook/en/README.md msgid "[Disabling CSRF protection](disabling-csrf-protection.md)" diff --git a/cookbook/en/README.md b/cookbook/en/README.md index 2854f41f..a16f5e03 100644 --- a/cookbook/en/README.md +++ b/cookbook/en/README.md @@ -14,6 +14,7 @@ This book conforms to the [Terms of Yii Documentation](https://www.yiiframework. - [Preface](preface.md) - [Structuring code by use-case with vertical slices](organizing-code/structuring-by-use-case-with-vertical-slices.md) +- [Making HTTP requests](making-http-requests.md) - [Disabling CSRF protection](disabling-csrf-protection.md) - [Sentry integration](sentry-integration.md) - [Configuring webservers](configuring-webservers/general.md) diff --git a/cookbook/en/making-http-requests.md b/cookbook/en/making-http-requests.md new file mode 100644 index 00000000..c16bdc51 --- /dev/null +++ b/cookbook/en/making-http-requests.md @@ -0,0 +1,367 @@ +# Making HTTP requests + +When building modern applications, you often need to make HTTP requests to external APIs. This article demonstrates how to make HTTP requests in Yii3 applications using Guzzle with and [PSR interfaces](https://www.php-fig.org/psr/). + +## What are PSR interfaces for HTTP + +The PHP-FIG (PHP Framework Interoperability Group) has defined several PSR standards for HTTP handling: + +- **PSR-7**: HTTP message interfaces for requests and responses +- **PSR-17**: HTTP factory interfaces for creating PSR-7 message objects +- **PSR-18**: HTTP client interface for sending PSR-7 requests and returning PSR-7 responses + +Using these interfaces ensures your code is framework-agnostic and follows established PHP standards. + +## Installation + +Install the Guzzle HTTP client with PSR-18 support and PSR-17 factories: + +```shell +composer require guzzlehttp/guzzle +composer require guzzlehttp/psr7 +``` + +## Basic usage + +### Simple GET request + +Here's how to make a basic GET request using PSR-18 interfaces: + +```php +requestFactory->createRequest( + 'GET', + "https://example.com/users/{$userId}" + ); + + return $this->httpClient->sendRequest($request); + } +} +``` + +### POST request with JSON data + +Here's an example of making a POST request with JSON payload: + +```php +streamFactory->createStream($jsonData); + + $request = $this->requestFactory->createRequest('POST', 'https://example.com/users') + ->withHeader('Content-Type', 'application/json') + ->withHeader('Accept', 'application/json') + ->withBody($stream); + + return $this->httpClient->sendRequest($request); + } +} +``` + +## Configuration in Yii3 + +### Container configuration + +Configure the HTTP client and PSR factories in your DI container: + +```php + [ + 'class' => Client::class, + '__construct()' => [ + 'config' => [ + 'timeout' => 30, + 'connect_timeout' => 10, + ], + ], + ], + + // Configure PSR-17 factories - these will depend on your chosen PSR-7 implementation + RequestFactoryInterface::class => static function (): RequestFactoryInterface { + return new \GuzzleHttp\Psr7\HttpFactory(); + }, + ResponseFactoryInterface::class => static function (): ResponseFactoryInterface { + return new \GuzzleHttp\Psr7\HttpFactory(); + }, + StreamFactoryInterface::class => static function (): StreamFactoryInterface { + return new \GuzzleHttp\Psr7\HttpFactory(); + }, + UriFactoryInterface::class => static function (): UriFactoryInterface { + return new \GuzzleHttp\Psr7\HttpFactory(); + }, +]; +``` + +### Service with error handling + +Here's a more robust service example with proper error handling: + +```php +requestFactory->createRequest( + 'GET', + "https://api.openweathermap.org/data/2.5/weather?q={$city}&appid={$this->apiKey}&units=metric" + ); + + $response = $this->httpClient->sendRequest($request); + + if ($response->getStatusCode() !== 200) { + $this->logger->warning('Weather API returned non-200 status', [ + 'status_code' => $response->getStatusCode(), + 'city' => $city, + ]); + return null; + } + + $data = json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); + return $data; + } catch (ClientExceptionInterface $e) { + $this->logger->error('HTTP client error when fetching weather data', [ + 'city' => $city, + 'error' => $e->getMessage(), + ]); + return null; + } catch (\JsonException $e) { + $this->logger->error('Failed to decode weather API response', [ + 'city' => $city, + 'error' => $e->getMessage(), + ]); + return null; + } + } +} +``` + +## Advanced usage + +### Using middlewares + +Guzzle supports middleware for cross-cutting concerns like authentication, logging, or retrying: + +```php +push(Middleware::log( + $this->logger, + new \GuzzleHttp\MessageFormatter('HTTP {method} {uri} - {code} {phrase}') + )); + + // Add retry middleware + $stack->push(Middleware::retry( + function (int $retries, RequestInterface $request) { + return $retries < 3; + } + )); + + return new Client([ + 'handler' => $stack, + 'timeout' => 30, + ]); + } +} +``` + +### Async requests + +For better performance when making multiple requests, you can use asynchronous requests: + +> **Note**: Async functionality is not part of PSR interfaces, so this code depends on Guzzle explicitly. + +```php + $userIds + * @return array + */ + public function fetchMultipleUsers(array $userIds): array + { + $promises = []; + + foreach ($userIds as $userId) { + $promises[$userId] = $this->httpClient->getAsync( + "https://example.com/users/{$userId}" + ); + } + + // Wait for all requests to complete + $responses = \GuzzleHttp\Promise\settle($promises)->wait(); + + $results = []; + foreach ($responses as $userId => $response) { + if ($response['state'] === PromiseInterface::FULFILLED) { + $results[$userId] = $response['value']; + } + } + + return $results; + } +} +``` + +## Testing HTTP clients + +When testing services that make HTTP requests, you can use Guzzle's MockHandler: + +```php + 'London', + 'main' => ['temp' => 20.5], + ])), + ]); + + $handlerStack = HandlerStack::create($mockHandler); + $client = new Client(['handler' => $handlerStack]); + + $service = new WeatherService( + $client, + new \GuzzleHttp\Psr7\HttpFactory(), + new \GuzzleHttp\Psr7\HttpFactory(), + $this->createMock(\Psr\Log\LoggerInterface::class), + 'test-api-key' + ); + + $result = $service->getCurrentWeather('London'); + + $this->assertNotNull($result); + $this->assertSame('London', $result['name']); + $this->assertSame(20.5, $result['main']['temp']); + } +} +``` + +## Best practices + +1. **Use PSR interfaces**: Always type-hint against PSR interfaces rather than concrete implementations for better testability and flexibility. + +2. **Handle errors gracefully**: Always wrap HTTP requests in try-catch blocks and handle network failures appropriately. + +3. **Configure timeouts**: Set reasonable connection and request timeouts to prevent hanging requests. + +4. **Log requests**: Use middleware or manual logging to track API calls for debugging and monitoring. + +5. **Use dependency injection**: Inject HTTP clients and factories through your DI container rather than creating them directly. + +6. **Mock in tests**: Use Guzzle's MockHandler or similar tools to test your HTTP client code without making real network requests. + +By following these patterns and using PSR interfaces, you'll create maintainable, testable, and interoperable HTTP client code in your Yii3 applications. \ No newline at end of file diff --git a/cookbook/ru/README.md b/cookbook/ru/README.md index a5f15e40..22bf58b9 100644 --- a/cookbook/ru/README.md +++ b/cookbook/ru/README.md @@ -17,6 +17,7 @@ Yii](https://www.yiiframework.com/license#docs). - [Вступление](preface.md) - [Structuring code by use-case with vertical slices](organizing-code/structuring-by-use-case-with-vertical-slices.md) +- [Making HTTP requests](making-http-requests.md) - [Disabling CSRF protection](disabling-csrf-protection.md) - [Интеграция с Sentry](sentry-integration.md) - [Конфигурирование веб-серверов](configuring-webservers/general.md)