Subiendo proyecto completo sin restricciones de git ignore

This commit is contained in:
Jose Sanchez
2023-08-17 11:44:02 -04:00
parent a0d4f5ba3b
commit 20f1c60600
19921 changed files with 2509159 additions and 45 deletions

View File

@@ -0,0 +1,57 @@
<?php
namespace PayPalHttp;
/**
* Class Curl
* @package PayPalHttp
*
* Curl wrapper used by HttpClient to make curl requests.
* @see HttpClient
*/
class Curl
{
protected $curl;
public function __construct($curl = NULL)
{
if (is_null($curl))
{
$curl = curl_init();
}
$this->curl = $curl;
}
public function setOpt($option, $value)
{
curl_setopt($this->curl, $option, $value);
return $this;
}
public function close()
{
curl_close($this->curl);
return $this;
}
public function exec()
{
return curl_exec($this->curl);
}
public function errNo()
{
return curl_errno($this->curl);
}
public function getInfo($option)
{
return curl_getinfo($this->curl, $option);
}
public function error()
{
return curl_error($this->curl);
}
}

View File

@@ -0,0 +1,115 @@
<?php
namespace PayPalHttp;
use PayPalHttp\Serializer\Form;
use PayPalHttp\Serializer\Json;
use PayPalHttp\Serializer\Multipart;
use PayPalHttp\Serializer\Text;
/**
* Class Encoder
* @package PayPalHttp
*
* Encoding class for serializing and deserializing request/response.
*/
class Encoder
{
private $serializers = [];
function __construct()
{
$this->serializers[] = new Json();
$this->serializers[] = new Text();
$this->serializers[] = new Multipart();
$this->serializers[] = new Form();
}
public function serializeRequest(HttpRequest $request)
{
if (!array_key_exists('content-type', $request->headers)) {
$message = "HttpRequest does not have Content-Type header set";
echo $message;
throw new \Exception($message);
}
$contentType = $request->headers['content-type'];
/** @var Serializer $serializer */
$serializer = $this->serializer($contentType);
if (is_null($serializer)) {
$message = sprintf("Unable to serialize request with Content-Type: %s. Supported encodings are: %s", $contentType, implode(", ", $this->supportedEncodings()));
echo $message;
throw new \Exception($message);
}
if (!(is_string($request->body) || is_array($request->body))) {
$message = "Body must be either string or array";
echo $message;
throw new \Exception($message);
}
$serialized = $serializer->encode($request);
if (array_key_exists("content-encoding", $request->headers) && $request->headers["content-encoding"] === "gzip") {
$serialized = gzencode($serialized);
}
return $serialized;
}
public function deserializeResponse($responseBody, $headers)
{
if (!array_key_exists('content-type', $headers)) {
$message = "HTTP response does not have Content-Type header set";
echo $message;
throw new \Exception($message);
}
$contentType = $headers['content-type'];
$contentType = strtolower($contentType);
/** @var Serializer $serializer */
$serializer = $this->serializer($contentType);
if (is_null($serializer)) {
throw new \Exception(sprintf("Unable to deserialize response with Content-Type: %s. Supported encodings are: %s", $contentType, implode(", ", $this->supportedEncodings())));
}
if (array_key_exists("content-encoding", $headers) && $headers["content-encoding"] === "gzip") {
$responseBody = gzdecode($responseBody);
}
return $serializer->decode($responseBody);
}
private function serializer($contentType)
{
/** @var Serializer $serializer */
foreach ($this->serializers as $serializer) {
try {
if (preg_match($serializer->contentType(), $contentType) == 1) {
return $serializer;
}
} catch (\Exception $ex) {
$message = sprintf("Error while checking content type of %s: %s", get_class($serializer), $ex->getMessage());
echo $message;
throw new \Exception($message, $ex->getCode(), $ex);
}
}
return NULL;
}
private function supportedEncodings()
{
$values = [];
/** @var Serializer $serializer */
foreach ($this->serializers as $serializer) {
$values[] = $serializer->contentType();
}
return $values;
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace PayPalHttp;
/**
* Interface Environment
* @package PayPalHttp
*
* Describes a domain that hosts a REST API, against which an HttpClient will make requests.
* @see HttpClient
*/
interface Environment
{
/**
* @return string
*/
public function baseUrl();
}

View File

@@ -0,0 +1,238 @@
<?php
namespace PayPalHttp;
/**
* Class HttpClient
* @package PayPalHttp
*
* Client used to make HTTP requests.
*/
class HttpClient
{
/**
* @var Environment
*/
public $environment;
/**
* @var Injector[]
*/
public $injectors = [];
/**
* @var Encoder
*/
public $encoder;
/**
* HttpClient constructor. Pass the environment you wish to make calls to.
*
* @param Environment $environment
* @see Environment
*/
function __construct(Environment $environment)
{
$this->environment = $environment;
$this->encoder = new Encoder();
$this->curlCls = Curl::class;
}
/**
* Injectors are blocks that can be used for executing arbitrary pre-flight logic, such as modifying a request or logging data.
* Executed in first-in first-out order.
*
* @param Injector $inj
*/
public function addInjector(Injector $inj)
{
$this->injectors[] = $inj;
}
/**
* The method that takes an HTTP request, serializes the request, makes a call to given environment, and deserialize response
*
* @param HttpRequest $httpRequest
* @return HttpResponse
*
* @throws HttpException
* @throws IOException
*/
public function execute(HttpRequest $httpRequest)
{
$requestCpy = clone $httpRequest;
$curl = new Curl();
foreach ($this->injectors as $inj) {
$inj->inject($requestCpy);
}
$url = $this->environment->baseUrl() . $requestCpy->path;
$formattedHeaders = $this->prepareHeaders($requestCpy->headers);
if (!array_key_exists("user-agent", $formattedHeaders)) {
$requestCpy->headers["user-agent"] = $this->userAgent();
}
$body = "";
if (!is_null($requestCpy->body)) {
$rawHeaders = $requestCpy->headers;
$requestCpy->headers = $formattedHeaders;
$body = $this->encoder->serializeRequest($requestCpy);
$requestCpy->headers = $this->mapHeaders($rawHeaders,$requestCpy->headers);
}
$curl->setOpt(CURLOPT_URL, $url);
$curl->setOpt(CURLOPT_CUSTOMREQUEST, $requestCpy->verb);
$curl->setOpt(CURLOPT_HTTPHEADER, $this->serializeHeaders($requestCpy->headers));
$curl->setOpt(CURLOPT_RETURNTRANSFER, 1);
$curl->setOpt(CURLOPT_HEADER, 0);
if (!is_null($requestCpy->body)) {
$curl->setOpt(CURLOPT_POSTFIELDS, $body);
}
if (strpos($this->environment->baseUrl(), "https://") === 0) {
$curl->setOpt(CURLOPT_SSL_VERIFYPEER, true);
$curl->setOpt(CURLOPT_SSL_VERIFYHOST, 2);
}
if ($caCertPath = $this->getCACertFilePath()) {
$curl->setOpt(CURLOPT_CAINFO, $caCertPath);
}
$response = $this->parseResponse($curl);
$curl->close();
return $response;
}
/**
* Returns an array representing headers with their keys
* to be lower case
* @param $headers
* @return array
*/
public function prepareHeaders($headers){
$preparedHeaders = array_change_key_case($headers);
if (array_key_exists("content-type", $preparedHeaders)) {
$preparedHeaders["content-type"] = strtolower($preparedHeaders["content-type"]);
}
return $preparedHeaders;
}
/**
* Returns an array representing headers with their key in
* original cases and updated values
* @param $rawHeaders
* @param $formattedHeaders
* @return array
*/
public function mapHeaders($rawHeaders, $formattedHeaders){
$rawHeadersKey = array_keys($rawHeaders);
foreach ($rawHeadersKey as $array_key) {
if(array_key_exists(strtolower($array_key), $formattedHeaders)){
$rawHeaders[$array_key] = $formattedHeaders[strtolower($array_key)];
}
}
return $rawHeaders;
}
/**
* Returns default user-agent
*
* @return string
*/
public function userAgent()
{
return "PayPalHttp-PHP HTTP/1.1";
}
/**
* Return the filepath to your custom CA Cert if needed.
* @return string
*/
protected function getCACertFilePath()
{
return null;
}
protected function setCurl(Curl $curl)
{
$this->curl = $curl;
}
protected function setEncoder(Encoder $encoder)
{
$this->encoder = $encoder;
}
private function serializeHeaders($headers)
{
$headerArray = [];
if ($headers) {
foreach ($headers as $key => $val) {
$headerArray[] = $key . ": " . $val;
}
}
return $headerArray;
}
private function parseResponse($curl)
{
$headers = [];
$curl->setOpt(CURLOPT_HEADERFUNCTION,
function($curl, $header) use (&$headers)
{
$len = strlen($header);
$k = "";
$v = "";
$this->deserializeHeader($header, $k, $v);
$headers[$k] = $v;
return $len;
});
$responseData = $curl->exec();
$statusCode = $curl->getInfo(CURLINFO_HTTP_CODE);
$errorCode = $curl->errNo();
$error = $curl->error();
if ($errorCode > 0) {
throw new IOException($error, $errorCode);
}
$body = $responseData;
if ($statusCode >= 200 && $statusCode < 300) {
$responseBody = NULL;
if (!empty($body)) {
$responseBody = $this->encoder->deserializeResponse($body, $this->prepareHeaders($headers));
}
return new HttpResponse(
$errorCode === 0 ? $statusCode : $errorCode,
$responseBody,
$headers
);
} else {
throw new HttpException($body, $statusCode, $headers);
}
}
private function deserializeHeader($header, &$key, &$value)
{
if (strlen($header) > 0) {
if (empty($header) || strpos($header, ':') === false) {
return NULL;
}
list($k, $v) = explode(":", $header);
$key = trim($k);
$value = trim($v);
}
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace PayPalHttp;
class HttpException extends IOException
{
/**
* @var int
*/
public $statusCode;
/**
* @var array
*/
public $headers;
/**
* @param string $message
* @param int $statusCode
* @param array $headers
*/
public function __construct($message, $statusCode, $headers)
{
parent::__construct($message);
$this->statusCode = $statusCode;
$this->headers = $headers;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace PayPalHttp;
/**
* Class HttpRequest
* @package PayPalHttp
*
* Request object that holds all the necessary information required by HTTPClient
*
* @see HttpClient
*/
class HttpRequest
{
/**
* @var string
*/
public $path;
/**
* @var array | string
*/
public $body;
/**
* @var string
*/
public $verb;
/**
* @var array
*/
public $headers;
function __construct($path, $verb)
{
$this->path = $path;
$this->verb = $verb;
$this->body = NULL;
$this->headers = [];
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace PayPalHttp;
/**
* Class HttpResponse
* @package PayPalHttp
*
* Object that holds your response details
*/
class HttpResponse
{
/**
* @var int
*/
public $statusCode;
/**
* @var array | string | object
*/
public $result;
/**
* @var array
*/
public $headers;
public function __construct($statusCode, $body, $headers)
{
$this->statusCode = $statusCode;
$this->headers = $headers;
$this->result = $body;
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace PayPalHttp;
use Throwable;
class IOException extends \Exception
{
public function __construct($message = "", $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace PayPalHttp;
/**
* Interface Injector
* @package PayPalHttp
*
* Interface that can be implemented to apply injectors to Http client.
*
* @see HttpClient
*/
interface Injector
{
/**
* @param HttpRequest $httpRequest
*/
public function inject($httpRequest);
}

View File

@@ -0,0 +1,29 @@
<?php
namespace PayPalHttp;
/**
* Interface Serializer
* @package PayPalHttp
*
* Used to implement different serializers for different content types
*/
interface Serializer
{
/**
* @return string Regex that matches the content type it supports.
*/
public function contentType();
/**
* @param HttpRequest $request
* @return string representation of your data after being serialized.
*/
public function encode(HttpRequest $request);
/**
* @param $body
* @return mixed object/string representing the de-serialized response body.
*/
public function decode($body);
}

View File

@@ -0,0 +1,46 @@
<?php
namespace PayPalHttp\Serializer;
use PayPalHttp\HttpRequest;
use PayPalHttp\Serializer;
class Form implements Serializer
{
/**
* @return string Regex that matches the content type it supports.
*/
public function contentType()
{
return "/^application\/x-www-form-urlencoded$/";
}
/**
* @param HttpRequest $request
* @return string representation of your data after being serialized.
*/
public function encode(HttpRequest $request)
{
if (!is_array($request->body) || !$this->isAssociative($request->body))
{
throw new \Exception("HttpRequest body must be an associative array when Content-Type is: " . $request->headers["Content-Type"]);
}
return http_build_query($request->body);
}
/**
* @param $body
* @return mixed
* @throws \Exception as multipart does not support deserialization.
*/
public function decode($body)
{
throw new \Exception("CurlSupported does not support deserialization");
}
private function isAssociative(array $array)
{
return array_values($array) !== $array;
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace PayPalHttp\Serializer;
class FormPart
{
private $value;
private $headers;
public function __construct($value, $headers)
{
$this->value = $value;
$this->headers = array_merge([], $headers);
}
public function getValue()
{
return $this->value;
}
public function getHeaders()
{
return $this->headers;
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace PayPalHttp\Serializer;
use PayPalHttp\HttpRequest;
use PayPalHttp\Serializer;
/**
* Class Json
* @package PayPalHttp\Serializer
*
* Serializer for JSON content types.
*/
class Json implements Serializer
{
public function contentType()
{
return "/^application\\/json/";
}
public function encode(HttpRequest $request)
{
$body = $request->body;
if (is_string($body)) {
return $body;
}
if (is_array($body)) {
return json_encode($body);
}
throw new \Exception("Cannot serialize data. Unknown type");
}
public function decode($data)
{
return json_decode($data);
}
}

View File

@@ -0,0 +1,134 @@
<?php
namespace PayPalHttp\Serializer;
use finfo;
use PayPalHttp\HttpRequest;
use PayPalHttp\Serializer;
use PayPalHttp\Encoder;
use PayPalHttp\Serializer\FormPart;
/**
* Class Multipart
* @package PayPalHttp\Serializer
*
* Serializer for multipart.
*/
class Multipart implements Serializer
{
const LINEFEED = "\r\n";
public function contentType()
{
return "/^multipart\/.*$/";
}
public function encode(HttpRequest $request)
{
if (!is_array($request->body) || !$this->isAssociative($request->body))
{
throw new \Exception("HttpRequest body must be an associative array when Content-Type is: " . $request->headers["content-type"]);
}
$boundary = "---------------------" . md5(mt_rand() . microtime());
$contentTypeHeader = $request->headers["content-type"];
$request->headers["content-type"] = "{$contentTypeHeader}; boundary={$boundary}";
$value_params = [];
$file_params = [];
$disallow = ["\0", "\"", "\r", "\n"];
$body = [];
foreach ($request->body as $k => $v) {
$k = str_replace($disallow, "_", $k);
if (is_resource($v)) {
$file_params[] = $this->prepareFilePart($k, $v, $boundary);
} else if ($v instanceof FormPart) {
$value_params[] = $this->prepareFormPart($k, $v, $boundary);
} else {
$value_params[] = $this->prepareFormField($k, $v, $boundary);
}
}
$body = array_merge($value_params, $file_params);
// add boundary for each parameters
array_walk($body, function (&$part) use ($boundary) {
$part = "--{$boundary}" . self::LINEFEED . "{$part}";
});
// add final boundary
$body[] = "--{$boundary}--";
$body[] = "";
return implode(self::LINEFEED, $body);
}
public function decode($data)
{
throw new \Exception("Multipart does not support deserialization");
}
private function isAssociative(array $array)
{
return array_values($array) !== $array;
}
private function prepareFormField($partName, $value, $boundary)
{
return implode(self::LINEFEED, [
"Content-Disposition: form-data; name=\"{$partName}\"",
"",
filter_var($value),
]);
}
private function prepareFilePart($partName, $file, $boundary)
{
$fileInfo = new finfo(FILEINFO_MIME_TYPE);
$filePath = stream_get_meta_data($file)['uri'];
$data = file_get_contents($filePath);
$mimeType = $fileInfo->buffer($data);
$splitFilePath = explode(DIRECTORY_SEPARATOR, $filePath);
$filePath = end($splitFilePath);
$disallow = ["\0", "\"", "\r", "\n"];
$filePath = str_replace($disallow, "_", $filePath);
return implode(self::LINEFEED, [
"Content-Disposition: form-data; name=\"{$partName}\"; filename=\"{$filePath}\"",
"Content-Type: {$mimeType}",
"",
$data,
]);
}
private function prepareFormPart($partName, $formPart, $boundary)
{
$contentDisposition = "Content-Disposition: form-data; name=\"{$partName}\"";
$partHeaders = $formPart->getHeaders();
$formattedheaders = array_change_key_case($partHeaders);
if (array_key_exists("content-type", $formattedheaders)) {
if ($formattedheaders["content-type"] === "application/json") {
$contentDisposition .= "; filename=\"{$partName}.json\"";
}
$tempRequest = new HttpRequest('/', 'POST');
$tempRequest->headers = $formattedheaders;
$tempRequest->body = $formPart->getValue();
$encoder = new Encoder();
$partValue = $encoder->serializeRequest($tempRequest);
} else {
$partValue = $formPart->getValue();
}
$finalPartHeaders = [];
foreach ($partHeaders as $k => $v) {
$finalPartHeaders[] = "{$k}: {$v}";
}
$body = array_merge([$contentDisposition], $finalPartHeaders, [""], [$partValue]);
return implode(self::LINEFEED, $body);
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace PayPalHttp\Serializer;
use PayPalHttp\HttpRequest;
use PayPalHttp\Serializer;
/**
* Class Text
* @package PayPalHttp\Serializer
*
* Serializer for Text content types.
*/
class Text implements Serializer
{
public function contentType()
{
return "/^text\\/.*/";
}
public function encode(HttpRequest $request)
{
$body = $request->body;
if (is_string($body)) {
return $body;
}
if (is_array($body)) {
return json_encode($body);
}
return implode(" ", $body);
}
public function decode($data)
{
return $data;
}
}