Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use http client with curl #299

Merged
merged 47 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
a3f88c2
feat: add Uri class
Matiasnickolas Sep 17, 2024
b2be0cf
feat: add stream class
Matiasnickolas Sep 17, 2024
1fb6022
feat: add stream exception
Matiasnickolas Sep 17, 2024
68af764
feat: add response class
Matiasnickolas Sep 17, 2024
a3b111a
feat: add request class
Matiasnickolas Sep 17, 2024
1b1d656
feat: add client class
Matiasnickolas Sep 17, 2024
fd83751
feat: add http curl client
Matiasnickolas Sep 17, 2024
b063bbb
feat: use curl client on request service
Matiasnickolas Sep 17, 2024
6294ec2
feat: remove old http client
Matiasnickolas Sep 17, 2024
b008f2d
chore: update dependencies
Matiasnickolas Sep 17, 2024
ed1c3a7
test: update request service test
Matiasnickolas Sep 17, 2024
4a0267f
test: update webpay plus without mock test
Matiasnickolas Sep 17, 2024
773b38c
chore: ignore coverage folder
Matiasnickolas Sep 17, 2024
853740c
refactor: remove commented code
Matiasnickolas Sep 17, 2024
220a21c
feat: add curl request exception
Matiasnickolas Sep 24, 2024
f565cf2
feat: update exception on http client interface
Matiasnickolas Sep 24, 2024
3d77d65
feat: update curl request exception
Matiasnickolas Sep 24, 2024
4a0cdb9
feat: handle sendRequest method exception
Matiasnickolas Sep 24, 2024
1e19542
feat: replace guzzle exception by curl request exception
Matiasnickolas Sep 24, 2024
7d41613
feat: improve headers methods on request class
Matiasnickolas Sep 30, 2024
c085ac0
feat: improve get header method on response class
Matiasnickolas Sep 30, 2024
5dd9a2a
feat: set resource to null on close method
Matiasnickolas Sep 30, 2024
f94d2a0
test: add uri tests
Matiasnickolas Sep 30, 2024
870aac2
test: add stream test class
Matiasnickolas Sep 30, 2024
98d0009
test: get empty string
Matiasnickolas Sep 30, 2024
c03e29b
test: close resource test
Matiasnickolas Sep 30, 2024
5023eaf
test: detach resource
Matiasnickolas Sep 30, 2024
6a5b832
test: get size
Matiasnickolas Sep 30, 2024
c796e3b
test: tell position
Matiasnickolas Sep 30, 2024
f6add81
test: get eof
Matiasnickolas Sep 30, 2024
5b9291b
test: seek test
Matiasnickolas Sep 30, 2024
6edc1d0
test: write stream
Matiasnickolas Sep 30, 2024
b5860aa
test: read data test
Matiasnickolas Sep 30, 2024
767aa1b
test: add response tests
Matiasnickolas Sep 30, 2024
49c8349
test: set class properties on response
Matiasnickolas Sep 30, 2024
78afe34
test: add curl request exception test
Matiasnickolas Sep 30, 2024
11a6476
test: add request test
Matiasnickolas Sep 30, 2024
32fcf41
test: set properties en request test
Matiasnickolas Sep 30, 2024
9774b91
feat: add trait for message interface
Matiasnickolas Sep 30, 2024
cc3656b
refactor: use message trait on request
Matiasnickolas Sep 30, 2024
a8297d3
feat: use message trait on response
Matiasnickolas Sep 30, 2024
f5649dd
feat: use return directly
Matiasnickolas Sep 30, 2024
b88ba8a
feat: let curl decide http version to use
Matiasnickolas Sep 30, 2024
fb00a33
feat: use empty protocol version on trait
Matiasnickolas Sep 30, 2024
044c99a
feat: use empty protocol version on request construct
Matiasnickolas Sep 30, 2024
e7a85fb
feat: add method to get curl http version
Matiasnickolas Sep 30, 2024
e441f37
test: update response test for default protocol value
Matiasnickolas Sep 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ composer.lock
.phpunit.result.cache
/build
.vscode

/coverage
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"guzzlehttp/guzzle":"^7"
"psr/http-client": "^1.0",
"psr/http-message": "^2.0"
},
"require-dev": {
"phpunit/phpunit": "^9",
Expand Down
4 changes: 2 additions & 2 deletions src/Contracts/HttpClientInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace Transbank\Contracts;

use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Exception\GuzzleException;
use Transbank\Utils\Curl\Exceptions\CurlRequestException;

interface HttpClientInterface
{
Expand All @@ -13,7 +13,7 @@ interface HttpClientInterface
* @param array|null $payload
* @param array|null $options
*
* @throws GuzzleException
* @throws CurlRequestException
*
* @return ResponseInterface
*/
Expand Down
6 changes: 3 additions & 3 deletions src/PatpassComercio/Inscription.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
use Transbank\Utils\RequestServiceTrait;
use Transbank\Contracts\RequestService;
use Transbank\PatpassComercio\Options;
use GuzzleHttp\Exception\GuzzleException;
use Transbank\Utils\Curl\Exceptions\CurlRequestException;

class Inscription
{
Expand Down Expand Up @@ -63,7 +63,7 @@ public function __construct(
* @param string $city
*
* @throws InscriptionStartException
* @throws GuzzleException
* @throws CurlRequestException
*
* @return InscriptionStartResponse
*/
Expand Down Expand Up @@ -123,7 +123,7 @@ public function start(
* @param string $token
*
* @throws InscriptionStatusException
* @throws GuzzleException
* @throws CurlRequestException
*
* @return InscriptionStatusResponse
*/
Expand Down
82 changes: 82 additions & 0 deletions src/Utils/Curl/Client.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

namespace Transbank\Utils\Curl;

use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\RequestInterface;
use Transbank\Utils\Curl\Exceptions\CurlRequestException;


class Client implements ClientInterface
{
private int $timeout;
public function __construct(int $timeout)
{
$this->timeout = $timeout;
}
public function sendRequest(RequestInterface $request): ResponseInterface
{
$curl = curl_init();

if (!$curl) {
throw new CurlRequestException('Unable to initialize cURL session.');
}

curl_setopt_array($curl, [
CURLOPT_URL => (string) $request->getUri(),
CURLOPT_CUSTOMREQUEST => $request->getMethod(),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
Matiasnickolas marked this conversation as resolved.
Show resolved Hide resolved
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_CONNECTTIMEOUT => $this->timeout,
]);

$headers = [];
foreach ($request->getHeaders() as $name => $value) {
$headers[] = "$name: $value";
}
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);

$body = (string) $request->getBody();
if (!empty($body)) {
curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
}

$response = curl_exec($curl);
if ($response === false) {
if (is_resource($curl)) {
curl_close($curl);
}
throw new CurlRequestException(curl_error($curl), curl_errno($curl));
}

$headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
$statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$rawHeaders = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);

curl_close($curl);

$headers = $this->parseHeaders($rawHeaders);

return new Response($statusCode, $headers, $body);
}

private function parseHeaders(string $rawHeaders): array
{
$headers = [];
$lines = explode("\r\n", $rawHeaders);

foreach ($lines as $line) {
if (strpos($line, ':') !== false) {
list($name, $value) = explode(': ', $line, 2);
$headers[$name][] = $value;
}
}

return $headers;
}
}
24 changes: 24 additions & 0 deletions src/Utils/Curl/Exceptions/CurlRequestException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Transbank\Utils\Curl\Exceptions;

class CurlRequestException extends \Exception
{
const DEFAULT_MESSAGE = 'An error happened on the request';

/**
* RequestException constructor.
*
* @param string $message
* @param \Throwable|null $previous
*/
public function __construct(string $message = self::DEFAULT_MESSAGE, int $errorCode = 0, \Throwable|null $previous = null)
{
parent::__construct($message, $errorCode, $previous);
}

public function __toString(): string
{
return __CLASS__ . ": [error code {$this->code}]: {$this->message} in {$this->file} on line {$this->line}\n";
}
}
19 changes: 19 additions & 0 deletions src/Utils/Curl/Exceptions/StreamException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Transbank\Utils\Curl\Exceptions;

class StreamException extends \Exception
{
const DEFAULT_MESSAGE = 'An error happened on the stream';

/**
* StreamException constructor.
*
* @param string $message
* @param \Throwable|null $previous
*/
public function __construct(string $message = self::DEFAULT_MESSAGE, \Throwable|null $previous = null)
{
parent::__construct($message, 0, $previous);
}
}
44 changes: 44 additions & 0 deletions src/Utils/Curl/HttpCurlClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace Transbank\Utils\Curl;

use Composer\InstalledVersions;
use Transbank\Contracts\HttpClientInterface;
use Psr\Http\Message\ResponseInterface;


class HttpCurlClient implements HttpClientInterface
{
public function request(
string $method,
string $url,
array|null $payload = [],
array|null $options = null
): ResponseInterface {
$installedVersion = 'unknown';

if (class_exists('\Composer\InstalledVersions') && InstalledVersions::isInstalled('transbank/transbank-sdk')) {
$installedVersion = InstalledVersions::getVersion('transbank/transbank-sdk') ?? 'unknown';
}

$baseHeaders = [
'Content-Type' => 'application/json',
'User-Agent' => 'SDK-PHP/' . $installedVersion,
];

$givenHeaders = $options['headers'] ?? [];
$headers = array_merge($baseHeaders, $givenHeaders);
if (!$payload) {
$payload = null;
}
if (is_array($payload)) {
$payload = json_encode($payload);
}

$requestTimeout = $options['timeout'] ?? 0;

$request = new Request($method, $url, $headers, $payload);
$client = new Client($requestTimeout);
return $client->sendRequest($request);
}
}
63 changes: 63 additions & 0 deletions src/Utils/Curl/MessageTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace Transbank\Utils\Curl;

use Psr\Http\Message\StreamInterface;

trait MessageTrait
{
private string $protocolVersion = '1.1';
private array $headers = [];
private StreamInterface $body;

public function getProtocolVersion(): string
{
return $this->protocolVersion;
}

public function withProtocolVersion($version): static
{
$new = clone $this;
$new->protocolVersion = $version;
return $new;
}

public function getHeaders(): array
{
return $this->headers;
}

public function hasHeader($name): bool
{
return isset($this->headers[$name]);
}

public function getHeader($name): array
{
return isset($this->headers[$name]) ? [$this->headers[$name]] : [];
}

public function getHeaderLine($name): string
{
return implode(',', $this->getHeader($name));
}

public function withoutHeader($name): static
{
$new = clone $this;
unset($new->headers[$name]);
return $new;
}

public function getBody(): StreamInterface
{
return $this->body;
}

public function withBody(StreamInterface $body): static
{
$new = clone $this;
$new->body = $body;
return $new;
}
}
92 changes: 92 additions & 0 deletions src/Utils/Curl/Request.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

namespace Transbank\Utils\Curl;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriInterface;
use Psr\Http\Message\StreamInterface;

class Request implements RequestInterface
{
use MessageTrait;
private string $method;
private UriInterface|string $uri;
private string $requestTarget = '';

public function __construct(string $method, UriInterface|string $uri, array $headers = [], StreamInterface|string|null $body = null, string $protocolVersion = '1.1')
{
$this->method = $method;
$this->uri = is_string($uri) ? new Uri($uri) : $uri;
$this->body = $this->createBody($body);
$this->protocolVersion = $protocolVersion;
Matiasnickolas marked this conversation as resolved.
Show resolved Hide resolved
$this->headers = $headers;
}

public function getRequestTarget(): string
{
if ($this->requestTarget === '') {
$this->requestTarget = $this->uri->getPath() ?: '/';
if ($this->uri->getQuery()) {
$this->requestTarget .= '?' . $this->uri->getQuery();
}
}

return $this->requestTarget;
}

public function withRequestTarget($requestTarget): RequestInterface
{
$new = clone $this;
$new->requestTarget = $requestTarget;
return $new;
}

public function getMethod(): string
{
return $this->method;
}

public function withMethod($method): RequestInterface
{
$new = clone $this;
$new->method = $method;
return $new;
}

public function getUri(): UriInterface
{
return $this->uri;
}

public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface
{
$new = clone $this;
$new->uri = $uri;

if (!$preserveHost || !$this->hasHeader('Host')) {
$new->headers['Host'] = [$uri->getHost()];
}

return $new;
}

public function withHeader($name, $value): RequestInterface
{
return clone $this;
}

public function withAddedHeader($name, $value): RequestInterface
{
return clone $this;
}

private function createBody($body = ''): StreamInterface
{
$resource = fopen('php://temp', 'rw+');
if (!empty($body)) {
fwrite($resource, $body);
}

return new Stream($resource);
}
}
Loading