Skip to content
Open
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
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.5.0"
".": "0.6.0"
}
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Changelog

## 0.6.0 (2025-12-10)

Full Changelog: [v0.5.0...v0.6.0](https://github.com/e-invoice-be/e-invoice-php/compare/v0.5.0...v0.6.0)

### ⚠ BREAKING CHANGES

* use camel casing for all class properties

### Features

* add `BaseResponse` class for accessing raw responses ([ebca89e](https://github.com/e-invoice-be/e-invoice-php/commit/ebca89eaa03eff90dd4ee4435910fb2c0658703e))
* allow both model class instances and arrays in setters ([f22de1a](https://github.com/e-invoice-be/e-invoice-php/commit/f22de1aa99924f6721be1ab4a593f76e935661b9))
* split out services into normal & raw types ([e290c64](https://github.com/e-invoice-be/e-invoice-php/commit/e290c6404f5c8db7ebea93902d3e69e54d9d9bf4))
* use camel casing for all class properties ([9a81036](https://github.com/e-invoice-be/e-invoice-php/commit/9a81036ee96d23c0ab1cc22f68fde1555cb51f56))


### Chores

* ensure constant values are marked as optional in array types ([9cf1177](https://github.com/e-invoice-be/e-invoice-php/commit/9cf11779ff1e150b328100531e3e52d3f5f4d2ba))
* **internal:** improve pagination tests ([e2a20ee](https://github.com/e-invoice-be/e-invoice-php/commit/e2a20ee5fa4bc6830ecfb9484082042ddfc61694))
* switch from `#[Api(optional: true|false)]` to `#[Required]|#[Optional]` for annotations ([e2085d4](https://github.com/e-invoice-be/e-invoice-php/commit/e2085d469c3dac597e44ffc38f3cabef6401e8d3))
* use `$self = clone $this;` instead of `$obj = clone $this;` ([3d841fa](https://github.com/e-invoice-be/e-invoice-php/commit/3d841fa738d64708d78b6b321b66bf13651c774f))

## 0.5.0 (2025-12-05)

Full Changelog: [v0.4.1...v0.5.0](https://github.com/e-invoice-be/e-invoice-php/compare/v0.4.1...v0.5.0)
Expand Down
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ use EInvoiceAPI\Client;

$client = new Client(apiKey: getenv('E_INVOICE_API_KEY') ?: 'My API Key');

$documentResponse = $client->documents->create([]);
$documentResponse = $client->documents->create();

var_dump($documentResponse->id);
```
Expand All @@ -67,7 +67,7 @@ use EInvoiceAPI\Client;

$client = new Client(apiKey: getenv('E_INVOICE_API_KEY') ?: 'My API Key');

$page = $client->inbox->list([]);
$page = $client->inbox->list();

var_dump($page);

Expand All @@ -91,11 +91,11 @@ When the library is unable to connect to the API, or if the API returns a non-su
use EInvoiceAPI\Core\Exceptions\APIConnectionException;

try {
$documentResponse = $client->documents->create([]);
$documentResponse = $client->documents->create();
} catch (APIConnectionException $e) {
echo "The server could not be reached", PHP_EOL;
var_dump($e->getPrevious());
} catch (RateLimitError $_) {
} catch (RateLimitError $e) {
echo "A 429 status code was received; we should back off a bit.", PHP_EOL;
} catch (APIStatusError $e) {
echo "Another non-200-range status code was received", PHP_EOL;
Expand Down Expand Up @@ -137,7 +137,9 @@ use EInvoiceAPI\RequestOptions;
$client = new Client(maxRetries: 0);

// Or, configure per-request:
$result = $client->documents->create([], RequestOptions::with(maxRetries: 5));
$result = $client->documents->create(
requestOptions: RequestOptions::with(maxRetries: 5)
);
```

## Advanced concepts
Expand All @@ -156,8 +158,7 @@ Note: the `extra*` parameters of the same name overrides the documented paramete
use EInvoiceAPI\RequestOptions;

$documentResponse = $client->documents->create(
[],
RequestOptions::with(
requestOptions: RequestOptions::with(
extraQueryParams: ['my_query_parameter' => 'value'],
extraBodyParams: ['my_body_parameter' => 'value'],
extraHeaders: ['my-header' => 'value'],
Expand Down
2 changes: 1 addition & 1 deletion scripts/lint
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ set -e
cd -- "$(dirname -- "$0")/.."

echo "==> Running PHPStan"
exec -- ./vendor/bin/phpstan analyse --memory-limit=2G
exec -- ./vendor/bin/phpstan analyse --memory-limit=12G
4 changes: 2 additions & 2 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ public function __construct(?string $apiKey = null, ?string $baseUrl = null)
headers: [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'User-Agent' => sprintf('e-invoice/PHP %s', '0.5.0'),
'User-Agent' => sprintf('e-invoice/PHP %s', '0.6.0'),
'X-Stainless-Lang' => 'php',
'X-Stainless-Package-Version' => '0.5.0',
'X-Stainless-Package-Version' => '0.6.0',
'X-Stainless-OS' => $this->getNormalizedOS(),
'X-Stainless-Arch' => $this->getNormalizedArchitecture(),
'X-Stainless-Runtime' => 'php',
Expand Down
35 changes: 35 additions & 0 deletions src/Core/Attributes/Optional.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace EInvoiceAPI\Core\Attributes;

use EInvoiceAPI\Core\Conversion\Contracts\Converter;
use EInvoiceAPI\Core\Conversion\Contracts\ConverterSource;

/**
* @internal
*/
#[\Attribute(\Attribute::TARGET_PROPERTY)]
final class Optional extends Required
{
/**
* @param class-string<ConverterSource>|Converter|string|null $type
* @param class-string<\BackedEnum>|Converter|null $enum
* @param class-string<ConverterSource>|Converter|null $union
* @param class-string<ConverterSource>|Converter|string|null $list
* @param class-string<ConverterSource>|Converter|string|null $map
*/
public function __construct(
?string $apiName = null,
Converter|string|null $type = null,
Converter|string|null $enum = null,
Converter|string|null $union = null,
Converter|string|null $list = null,
Converter|string|null $map = null,
bool $nullable = false,
) {
parent::__construct(apiName: $apiName, type: $type, enum: $enum, union: $union, list: $list, map: $map, nullable: $nullable);
$this->optional = true;
}
}
23 changes: 16 additions & 7 deletions src/Core/Attributes/Api.php → src/Core/Attributes/Required.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,17 @@
* @internal
*/
#[\Attribute(\Attribute::TARGET_PROPERTY)]
final class Api
class Required
{
/** @var class-string<ConverterSource>|Converter|string|null */
public readonly Converter|string|null $type;

public readonly ?string $apiName;

public bool $optional;

public readonly bool $nullable;

/** @var array<string,Converter> */
private static array $enumConverters = [];

Expand All @@ -30,14 +36,13 @@ final class Api
* @param class-string<ConverterSource>|Converter|string|null $map
*/
public function __construct(
public readonly ?string $apiName = null,
?string $apiName = null,
Converter|string|null $type = null,
Converter|string|null $enum = null,
Converter|string|null $union = null,
Converter|string|null $list = null,
Converter|string|null $map = null,
public readonly bool $nullable = false,
public readonly bool $optional = false,
bool $nullable = false,
) {
$type ??= $union;
if (null !== $list) {
Expand All @@ -47,17 +52,21 @@ public function __construct(
$type ??= new MapOf($map);
}
if (null !== $enum) {
$type ??= $enum instanceof Converter ? $enum : $this->getEnumConverter($enum);
$type ??= $enum instanceof Converter ? $enum : self::enumConverter($enum);
}

$this->apiName = $apiName;
$this->type = $type;
$this->optional = false;
$this->nullable = $nullable;
}

/** @property class-string<\BackedEnum> $enum */
private function getEnumConverter(string $enum): Converter
private static function enumConverter(string $enum): Converter
{
if (!isset(self::$enumConverters[$enum])) {
$converter = new EnumOf(array_column($enum::cases(), 'value')); // @phpstan-ignore-line
// @phpstan-ignore-next-line argument.type
$converter = new EnumOf(array_column($enum::cases(), column_key: 'value'));
self::$enumConverters[$enum] = $converter;
}

Expand Down
37 changes: 10 additions & 27 deletions src/Core/BaseClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
namespace EInvoiceAPI\Core;

use EInvoiceAPI\Core\Contracts\BasePage;
use EInvoiceAPI\Core\Contracts\BaseResponse;
use EInvoiceAPI\Core\Contracts\BaseStream;
use EInvoiceAPI\Core\Conversion\Contracts\Converter;
use EInvoiceAPI\Core\Conversion\Contracts\ConverterSource;
use EInvoiceAPI\Core\Exceptions\APIConnectionException;
use EInvoiceAPI\Core\Exceptions\APIStatusException;
use EInvoiceAPI\Core\Implementation\RawResponse;
use EInvoiceAPI\RequestOptions;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
Expand Down Expand Up @@ -51,9 +53,11 @@ public function __construct(
* @param string|list<mixed> $path
* @param array<string,mixed> $query
* @param array<string,mixed> $headers
* @param class-string<BasePage<mixed>> $page
* @param class-string<BaseStream<mixed>> $stream
* @param class-string<BasePage<mixed>>|null $page
* @param class-string<BaseStream<mixed>>|null $stream
* @param RequestOptions|array<string,mixed>|null $options
*
* @return BaseResponse<mixed>
*/
public function request(
string $method,
Expand All @@ -65,7 +69,7 @@ public function request(
?string $page = null,
?string $stream = null,
RequestOptions|array|null $options = [],
): mixed {
): BaseResponse {
// @phpstan-ignore-next-line
[$req, $opts] = $this->buildRequest(method: $method, path: $path, query: $query, headers: $headers, body: $body, opts: $options);
['method' => $method, 'path' => $uri, 'headers' => $headers] = $req;
Expand All @@ -74,32 +78,11 @@ public function request(
$request = $opts->requestFactory->createRequest($method, uri: $uri);
$request = Util::withSetHeaders($request, headers: $headers);

// @phpstan-ignore-next-line
// @phpstan-ignore-next-line argument.type
$rsp = $this->sendRequest($opts, req: $request, data: $body, redirectCount: 0, retryCount: 0);

if (!is_null($stream)) {
return new $stream(
convert: $convert,
request: $request,
response: $rsp
);
}

if (!is_null($page)) {
return new $page(
convert: $convert,
client: $this,
request: $req,
response: $rsp,
options: $opts,
);
}

if (!is_null($convert)) {
return Conversion::coerceResponse($convert, response: $rsp);
}

return Util::decodeContent($rsp);
// @phpstan-ignore-next-line argument.type
return new RawResponse(client: $this, request: $request, response: $rsp, options: $opts, requestInfo: $req, stream: $stream, page: $page, convert: $convert ?? 'null');
}

/** @return array<string,string> */
Expand Down
101 changes: 101 additions & 0 deletions src/Core/Concerns/ResponseProxy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

declare(strict_types=1);

namespace EInvoiceAPI\Core\Concerns;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;

trait ResponseProxy
{
private ResponseInterface $response;

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

public function withProtocolVersion(string $version): static
{
$self = clone $this;
$self->response = $this->response->withProtocolVersion($version);

return $self;
}

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

public function hasHeader(string $name): bool
{
return $this->response->hasHeader($name);
}

public function getHeader(string $name): array
{
return $this->response->getHeader($name);
}

public function getHeaderLine(string $name): string
{
return $this->response->getHeaderLine($name);
}

public function withHeader(string $name, $value): static
{
$self = clone $this;
$self->response = $this->response->withHeader($name, value: $value);

return $self;
}

public function withAddedHeader(string $name, $value): static
{
$self = clone $this;
$self->response = $this->response->withAddedHeader($name, value: $value);

return $self;
}

public function withoutHeader(string $name): static
{
$self = clone $this;
$self->response = $this->response->withoutHeader($name);

return $self;
}

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

public function withBody(StreamInterface $body): static
{
$self = clone $this;
$self->response = $this->response->withBody($body);

return $self;
}

public function getStatusCode(): int
{
return $this->response->getStatusCode();
}

public function withStatus(int $code, string $reasonPhrase = ''): static
{
$self = clone $this;
$self->response = $this->response->withstatus($code, reasonPhrase: $reasonPhrase);

return $self;
}

public function getReasonPhrase(): string
{
return $this->response->getReasonPhrase();
}
}
Loading