Šimon Podlipský
composer require guzzlehttp/guzzleuse \GuzzleHttp\Client;
$client = new Client();
$response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle');
echo $response->getStatusCode(); // 200
echo $response->getHeaderLine('content-type'); // application/json; charset=utf8
echo $response->getBody(); // {"id": 1420053, "name": "guzzle", ...}
composer require symfony/http-clientuse Symfony\Component\HttpClient\HttpClient;
$client = HttpClient::create();
$response = $client->request('GET', 'https://api.github.com/repos/symfony/symfony-docs');
$statusCode = $response->getStatusCode(); // 200
$contentType = $response->getHeaders()['content-type'][0]; // application/json
$content = $response->getContent(); // {"id":521583, "name":"symfony-docs", ...}
$content = $response->toArray(); // ['id' => 521583, 'name' => 'symfony-docs', ...]final class Something
{
public function do()
{
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'http://soukupnahrad.cz');
return $this->parseResponse($response);
}
...
}
Cannot unit test this
function testProcessResponse()
{
$something = new Something();
self::assertX(
...,
$something->do()
);
}use GuzzleHttp\Client;
final class Something
{
private Client $client;
public function __construct(Client $client)
{
$this->client = $client;
}
public function do()
{
$response = $this->client->request('POST', 'http://soukupnahrad.cz');
return $this->parseResponse($response);
}
}
function testProcessResponse()
{
$something = new Something(new \GuzzleHttp\Client());
self::assertX(
...,
$something->do()
);
}use GuzzleHttp\Client;
final class Something
{
private ClientInterface $client;
public function __construct(ClientInterface $client)
{
$this->client = $client;
}
public function do()
{
$response = $this->client->request('POST', 'http://soukupnahrad.cz');
return $this->parseResponse($response);
}
}
function testProcessResponse()
{
$something = new Something(new FakeClient());
self::assertX(
...,
$something->do()
);
}class Client implements ClientInterface$client->request(
'POST',
'http://soukupnahrad.cz',
['json' => json_encode([true])]
);$client->request(
'POST',
'http://soukupnahrad.cz',
['json' => [true]]
);$response = $client->request(
'POST',
'http://soukupnahrad.cz',
[
'json' => '',
'headers' => [
'Content-Type' => 'application/hal+json',
],
]
);PHP-FIG
(framework interoperability group)
composer require psr/http-message # PSR-7
composer require psr/http-factory # PSR-17
composer require psr/http-client # PSR-18use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
final class Something
{
private ClientInterface $client;
private RequestFactoryInterface $requestFactory;
public function __construct(
ClientInterface $client,
RequestFactoryInterface $requestFactory
) {
$this->client = $client;
$this->requestFactory = $requestFactory;
}
...
}$request = $this->requestFactory->createRequest('POST', 'http://soukupnahrad.cz');$request = $this->requestFactory->createRequest('POST', 'http://soukupnahrad.cz');
$request = $request->withHeader('Authorization', 'Bearer ' . $token); // Mind immutability$request = $this->requestFactory->createRequest('POST', 'http://soukupnahrad.cz')
->withHeader('Authorization', 'Bearer ' . $token)
->withBody(
$this->streamFactory->create('body contents')
);
$response = $this->client->send($request);$request = $this->requestFactory->createRequest('POST', 'http://soukupnahrad.cz')
->withHeader('Authorization', 'Bearer ' . $token)
->withBody(
$this->streamFactory->create('body contents')
);use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
final class Something
{
private ClientInterface $client;
private RequestFactoryInterface $requestFactory;
private StreamFactoryInterface $streamFactory;
public function __construct(
ClientInterface $client,
RequestFactoryInterface $requestFactory,
StreamFactoryInterface $streamFactory
) {
$this->client = $client;
$this->requestFactory = $requestFactory;
$this->streamFactory = $streamFactory;
}
...
}use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
final class Something
{
private ClientInterface $client;
private RequestFactoryInterface $requestFactory;
private StreamFactoryInterface $streamFactory;
public function __construct(
ClientInterface $client,
RequestFactoryInterface $requestFactory,
StreamFactoryInterface $streamFactory
) {
$this->client = $client;
$this->requestFactory = $requestFactory;
$this->streamFactory = $streamFactory;
}
public function do()
{
$request = $this->requestFactory->createRequest('POST', 'http://soukupnahrad.cz')
->withHeader('Authorization', 'Bearer ' . $token)
->withBody(
$this->streamFactory->create('body contents')
);
$response = $this->client->send($request);
...
}
}composer require nyholm/psr7final class Psr17Factory implements
RequestFactoryInterface,
ResponseFactoryInterface,
ServerRequestFactoryInterface,
StreamFactoryInterface,
UploadedFileFactoryInterface,
UriFactoryInterfaceRuns: 30,000
Runs per second: 11214
Average time per run: 0.0892 ms
Total time: 2.6751 s
1K LOC
Runs: 30,000
Runs per second: 8614
Average time per run: 0.1161 ms
Total time: 3.4824 s
3K LOC
Runs: 30,000
Runs per second: 7424
Average time per run: 0.1347 ms
Total time: 4.0409 s
1.7K LOC
Runs: 30,000
Runs per second: 6422
Average time per run: 0.1557 ms
Total time: 4.6709 s
3K LOC
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<defaults autowire="true" autoconfigure="true" public="false" />
<service id="Nyholm\Psr7\Factory\Psr17Factory" />
<service id="Http\Client\Curl\Client" />
<service id="Psr\Http\Client\ClientInterface" alias="Http\Client\Curl\Client" />
<service id="Psr\Http\Message\ResponseFactoryInterface" alias="Nyholm\Psr7\Factory\Psr17Factory" />
<service id="Psr\Http\Message\RequestFactoryInterface" alias="Nyholm\Psr7\Factory\Psr17Factory" />
<service id="Psr\Http\Message\StreamFactoryInterface" alias="Nyholm\Psr7\Factory\Psr17Factory" />
<service id="Psr\Http\Message\UriFactoryInterface" alias="Nyholm\Psr7\Factory\Psr17Factory" />
</services>
</container>
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<defaults autowire="true" autoconfigure="true" public="false" />
<service id="Nyholm\Psr7\Factory\Psr17Factory" />
<service id="Http\Client\Curl\Client" />
<service id="Psr\Http\Client\ClientInterface" alias="Http\Client\Curl\Client" />
<service id="Psr\Http\Message\ResponseFactoryInterface" alias="Nyholm\Psr7\Factory\Psr17Factory" />
<service id="Psr\Http\Message\RequestFactoryInterface" alias="Nyholm\Psr7\Factory\Psr17Factory" />
<service id="Psr\Http\Message\StreamFactoryInterface" alias="Nyholm\Psr7\Factory\Psr17Factory" />
<service id="Psr\Http\Message\UriFactoryInterface" alias="Nyholm\Psr7\Factory\Psr17Factory" />
</services>
</container>
services:
- Nyholm\Psr7\Factory\Psr17Factory
- Http\Client\Curl\Client$response = $this->client->sendRequest($request);
// ?????$response = $this->client->sendRequest($request);
if ($response->getStatusCode() === 200) {
return ...;
}
if ($response->getStatusCode() === 400) {
throw ...;
}
if ($response->getStatusCode() >= 500) {
throw ...;
}
Response is always returned
...except...
on network errors etc.
try {
$response = $this->client->request(...);
} catch (ClientException $exception) {
// Handle e4xx
} catch (ServerException $exception) {
// Handle e5xx
}
// Handle success<?php
namespace Psr\Http\Client;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
interface ClientInterface
{
/**
* Sends a PSR-7 request and returns a PSR-7 response.
*
* @param RequestInterface $request
*
* @return ResponseInterface
*
* @throws \Psr\Http\Client\ClientExceptionInterface If an error happens while processing the request.
*/
public function sendRequest(RequestInterface $request): ResponseInterface;
}
<?php
namespace Psr\Http\Client;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
interface ClientInterface
{
/**
* Sends a PSR-7 request and returns a PSR-7 response.
*
* @param RequestInterface $request
*
* @return ResponseInterface
*
* @throws \Psr\Http\Client\ClientExceptionInterface If an error happens while processing the request.
*/
public function sendRequest(RequestInterface $request): ResponseInterface;
}
interface ResponseInterface {
...
/**
* Gets the body of the message.
*
* @return StreamInterface Returns the body as a stream.
*/
public function getBody();
}$contents = $response->getBody()->getContents(); // "body content"<?php
namespace Psr\Http\Message;
/**
* Describes a data stream.
*
* Typically, an instance will wrap a PHP stream; this interface provides
* a wrapper around the most common operations, including serialization of
* the entire stream to a string.
*/
interface StreamInterface
{
...
/**
* Returns the remaining contents in a string
*
* @return string
* @throws \RuntimeException if unable to read or an error occurs while
* reading.
*/
public function getContents();
...
}
$contents = $response->getBody()->getContents(); // "body content"
$contents2 = $response->getBody()->getContents(); // ""$contents = (string) $response->getBody();<?php
namespace Psr\Http\Message;
/**
* Describes a data stream.
*
* Typically, an instance will wrap a PHP stream; this interface provides
* a wrapper around the most common operations, including serialization of
* the entire stream to a string.
*/
interface StreamInterface
{
/**
* Reads all data from the stream into a string, from the beginning to end.
*
* This method MUST attempt to seek to the beginning of the stream before
* reading data and read the stream until the end is reached.
*
* Warning: This could attempt to load a large amount of data into memory.
*
* This method MUST NOT raise an exception in order to conform with PHP's
* string casting operations.
*
* @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
* @return string
*/
public function __toString();
...
}
<?php
namespace Psr\Http\Message;
/**
* Describes a data stream.
*
* Typically, an instance will wrap a PHP stream; this interface provides
* a wrapper around the most common operations, including serialization of
* the entire stream to a string.
*/
interface StreamInterface
{
/**
* Reads all data from the stream into a string, from the beginning to end.
*
* This method MUST attempt to seek to the beginning of the stream before
* reading data and read the stream until the end is reached.
*
* Warning: This could attempt to load a large amount of data into memory.
*
* This method MUST NOT raise an exception in order to conform with PHP's
* string casting operations.
*
* @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
* @return string
*/
public function __toString();
...
}