Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
56 changes: 56 additions & 0 deletions src/Adapter/CurlAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,94 @@

namespace LapostaApi\Adapter;

/**
* Adapter class for PHP's cURL functions.
*
* This class provides a simple wrapper around PHP's native cURL functions
* to allow for easier testing through mocking of HTTP requests in the application.
*/
class CurlAdapter
{
/**
* Initialize a new cURL session.
*
* @return \CurlHandle|false A cURL handle on success, false on failure
*/
public function init(): \CurlHandle|false
{
return curl_init();
}

/**
* Set an option for a cURL transfer.
*
* @param \CurlHandle $handle The cURL handle
* @param int $option The CURLOPT option to set
* @param mixed $value The value to set the option to
*
* @return bool Returns true on success, false on failure
*/
public function setopt(\CurlHandle $handle, int $option, mixed $value): bool
{
return curl_setopt($handle, $option, $value);
}

/**
* Execute the cURL request.
*
* @param \CurlHandle $handle The cURL handle
*
* @return string|false Returns the result of the request as string on success, false on failure
*/
public function exec(\CurlHandle $handle): string|false
{
return curl_exec($handle);
}

/**
* Get information about the last transfer.
*
* @param \CurlHandle $handle The cURL handle
* @param int $option The CURLINFO option to get, or 0 to get all info
*
* @return mixed The requested information or an array with all information if no option specified
*/
public function getInfo(\CurlHandle $handle, int $option = 0): mixed
{
return curl_getinfo($handle, $option);
}

/**
* Get the error code for the last cURL operation.
*
* @param \CurlHandle $handle The cURL handle
*
* @return int The error number or 0 if no error occurred
*/
public function getErrno(\CurlHandle $handle): int
{
return curl_errno($handle);
}

/**
* Get the error message for the last cURL operation.
*
* @param \CurlHandle $handle The cURL handle
*
* @return string The error message or an empty string if no error occurred
*/
public function getError(\CurlHandle $handle): string
{
return curl_error($handle);
}

/**
* Close a cURL session and free all resources.
*
* @param \CurlHandle $handle The cURL handle
*
* @return void
*/
public function close(\CurlHandle $handle): void
{
curl_close($handle);
Expand Down
95 changes: 95 additions & 0 deletions src/Adapter/StreamAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,63 +4,158 @@

namespace LapostaApi\Adapter;

/**
* Adapter class for PHP's file stream functions.
*
* This class provides a simple wrapper around PHP's native stream functions
* to allow for easier testing through mocking of file operations in the application.
*/
class StreamAdapter
{
/**
* Opens a file or URL.
*
* @param string $filename The filename or URL to open
* @param string $mode The mode for opening the file
*
* @return resource|false A file pointer resource on success, or false on failure
*/
public function fopen(string $filename, string $mode)
{
return fopen($filename, $mode);
}

/**
* Checks if a variable is a resource.
*
* @param mixed $value The value to check
*
* @return bool True if the value is a resource, false otherwise
*/
public function isResource(mixed $value): bool
{
return is_resource($value);
}

/**
* Reads up to a specified number of bytes from a stream.
*
* @param resource $stream The file pointer resource
* @param int $length The number of bytes to read
*
* @return string|false The read string or false on failure
*/
public function fread($stream, int $length): string|false
{
return fread($stream, $length);
}

/**
* Writes to a stream.
*
* @param resource $stream The file pointer resource
* @param string $string The string to write
*
* @return int|false The number of bytes written or false on failure
*/
public function fwrite($stream, string $string): int|false
{
return fwrite($stream, $string);
}

/**
* Gets information about a file using an open file pointer.
*
* @param resource $stream The file pointer resource
*
* @return array|false An array with file statistics or false on failure
*/
public function fstat($stream): array|false
{
return fstat($stream);
}

/**
* Gets the current position of the file pointer.
*
* @param resource $stream The file pointer resource
*
* @return int|false The position of the file pointer or false on error
*/
public function ftell($stream): int|false
{
return ftell($stream);
}

/**
* Tests for end-of-file on a file pointer.
*
* @param resource $stream The file pointer resource
*
* @return bool True if the file pointer is at EOF or an error occurs, false otherwise
*/
public function feof($stream): bool
{
return feof($stream);
}

/**
* Seeks on a file pointer.
*
* @param resource $stream The file pointer resource
* @param int $offset The offset in bytes
* @param int $whence The position from where to start seeking
*
* @return int Returns 0 on success, -1 on failure
*/
public function fseek($stream, int $offset, int $whence = SEEK_SET): int
{
return fseek($stream, $offset, $whence);
}

/**
* Rewinds a file pointer to the beginning.
*
* @param resource $stream The file pointer resource
*
* @return bool True on success, false on failure
*/
public function rewind($stream): bool
{
return rewind($stream);
}

/**
* Closes an open file pointer.
*
* @param resource $stream The file pointer resource
*
* @return bool True on success, false on failure
*/
public function fclose($stream): bool
{
return fclose($stream);
}

/**
* Reads the remaining contents from a stream into a string.
*
* @param resource $stream The file pointer resource
*
* @return string|false The read data or false on failure
*/
public function streamGetContents($stream): string|false
{
return stream_get_contents($stream);
}

/**
* Retrieves header/meta data from streams/file pointers.
*
* @param resource $stream The file pointer resource
*
* @return array Array containing metadata
*/
public function streamGetMetaData($stream): array
{
return stream_get_meta_data($stream);
Expand Down
39 changes: 35 additions & 4 deletions src/Api/BaseApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
abstract class BaseApi
{
/**
* Constructor method to initialize the Laposta instance and request factory.
* Constructor method to initialize the Laposta instance.
*
* @param Laposta $laposta An instance of the Laposta class.
*/
Expand Down Expand Up @@ -141,7 +141,7 @@ protected function createRequest(
$formattedBody = $contentType->formatBody($body);

// Create a stream and write the body to the stream
$stream = (new StreamFactory())->createStream();
$stream = $this->laposta->getStreamFactory()->createStream();
$stream->write($formattedBody);
$request = $request->withBody($stream);

Expand Down Expand Up @@ -180,8 +180,39 @@ protected function handleResponse(RequestInterface $request, ResponseInterface $
// Check HTTP status code
$statusCode = $response->getStatusCode();
if ($statusCode < 200 || $statusCode >= 300) {
$body = (string)$response->getBody();
$errorMessage = sprintf('API request failed with status code %d', $statusCode);

// Try to extract error details from the response body
try {
$responseData = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
if (isset($responseData['error']) && is_array($responseData['error'])) {
// Add code, type, parameter and message if available
if (isset($responseData['error']['code'])) {
$errorMessage .= sprintf(', error.code: %s', $responseData['error']['code']);
}

if (isset($responseData['error']['type'])) {
$errorMessage .= sprintf(', error.type: %s', $responseData['error']['type']);
}

if (isset($responseData['error']['parameter'])) {
$errorMessage .= sprintf(', error.parameter: %s', $responseData['error']['parameter']);
}

if (isset($responseData['error']['message'])) {
$errorMessage .= sprintf(', error.message: %s', $responseData['error']['message']);
}
}
} catch (\JsonException $e) {
// If the response body is not valid JSON, add the raw body
if (!empty($body) && strlen($body) < 1000) {
$errorMessage .= sprintf(', response body: %s', $body);
}
}

throw new ApiException(
sprintf('API request failed with status code %d', $statusCode),
$errorMessage,
$request,
$response,
);
Expand Down Expand Up @@ -209,7 +240,7 @@ protected function handleResponse(RequestInterface $request, ResponseInterface $
*/
protected function getResource(): string
{
// Extract resource name by removing namespace and suffix
// Extract the resource name by removing namespace and suffix
return strtolower(str_replace(['LapostaApi\\Api\\', 'Api'], '', static::class));
}
}
57 changes: 56 additions & 1 deletion src/Exception/ApiException.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public function getResponseBody(): string
/**
* Returns the parsed JSON response (lazy loaded)
*/
public function getResponseJson(): array
public function getResponseData(): array
{
if ($this->responseJson === null) {
$body = $this->getResponseBody();
Expand All @@ -99,13 +99,68 @@ public function getResponseJson(): array
return $this->responseJson;
}

/**
* Returns the error type if available
*/
public function getErrorType(): ?string
{
return $this->getResponseData()['error']['type'] ?? null;
}

/**
* Returns the error code if available
*/
public function getErrorCode(): ?int
{
$code = $this->getResponseData()['error']['code'] ?? null;
return $code !== null ? (int)$code : null;
}

/**
* Returns the error parameter if available
*/
public function getErrorParameter(): ?string
{
return $this->getResponseData()['error']['parameter'] ?? null;
}

/**
* Returns the error message if available
*/
public function getErrorMessage(): ?string
{
return $this->getResponseData()['error']['message'] ?? null;
}


/**
* Returns a readable representation of the exception
*/
public function __toString(): string
{
$str = parent::__toString();
$str .= "\nHTTP Status: " . $this->getHttpStatus();

$errorCode = $this->getErrorCode();
if ($errorCode !== null) {
$str .= "\nError Code: " . $errorCode;
}

$errorType = $this->getErrorType();
if ($errorType !== null) {
$str .= "\nError Type: " . $errorType;
}

$errorParameter = $this->getErrorParameter();
if ($errorParameter !== null) {
$str .= "\nError Parameter: " . $errorParameter;
}

$errorMessage = $this->getErrorMessage();
if ($errorMessage !== null) {
$str .= "\nError Message: " . $errorMessage;
}

$str .= "\nResponse Body: " . $this->getResponseBody();

return $str;
Expand Down
Loading