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
2 changes: 1 addition & 1 deletion .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
strategy:
matrix:
operating-system: [ubuntu-latest]
php-versions: [ '8.1', '8.2', '8.3', '8.4']
php-versions: [ '8.2', '8.3', '8.4', '8.5']
steps:
- uses: actions/checkout@v2
- name: Setup PHP
Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# Changelog

## 3.0.0-alpha.1 (18.03.2026)

### Breaking Changes
* Minimum PHP version is now 8.2.
* Removed dependency `illuminate/collections` - Replaced all `collect()` usages with native PHP array functions.
* Method `delete()` now returns `?APIResponse` instead of `bool` across all models.
* Changed various method signatures to use native PHP types and improved type hinting.

### Features
* Improved support for Managed Certificates.
* Improved support for Firewall label selectors.
* Improved support for Primary IPs.
* Improved support for Placement Groups.
* Improved support for Load Balancers.

### Internal
* General code cleanup and modernization.

## 2.5.0 (12.10.2021)

* Upgrade to GitHub-native Dependabot by @dependabot-preview in https://github.com/LKDevelopment/hetzner-cloud-php-sdk/pull/73
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ You can just run `phpunit`. The whole library is based on unit tests and sample

### Changelog

Please see [CHANGELOG](https://github.com/LKDevelopment/hetzner-cloud-php-sdk/releases) for more information what has changed recently.
Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.

### Upgrade Guide

Please see [Upgrade to v3.0](UPGRADE-3.0.md) for more information how to upgrade from v2.x.


### Security
Expand Down
76 changes: 76 additions & 0 deletions UPGRADE-3.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Upgrade Guide: v2.x to v3.0

This guide provides instructions for upgrading your application from `hetzner-cloud-php-sdk` version 2.x to 3.0.

## Breaking Changes

### PHP Version Requirement
- **Requirement:** PHP 8.2 or higher is now required.
- **Action:** Ensure your server or environment is running PHP 8.2+.

### Removal of `illuminate/collections`
The dependency on `illuminate/collections` has been removed to reduce the library's footprint and avoid dependency conflicts. All internal usages of `collect()` have been replaced with native PHP array functions.

- **Impact:** If your application relied on the SDK returning Laravel Collections, this will no longer happen.
- **Action:** Use native PHP array functions like `array_map()`, `array_filter()`, or wrap the returned arrays in a collection yourself if you still need them.

**Before (v2.x):**
```php
// Some internal methods or if you were using collect() on SDK results
$names = collect($hetznerClient->servers()->all())->map(fn($s) => $s->name);
```

**After (v3.0):**
```php
// Results are now always native arrays
$servers = $hetznerClient->servers()->all();
$names = array_map(fn($s) => $s->name, $servers);

// If you want to keep using Collections in your project:
// composer require illuminate/collections
$names = collect($hetznerClient->servers()->all())->map(fn($s) => $s->name);
```

### `delete()` Method Return Type Change
The `delete()` method on all resource models now returns an `APIResponse` object (or `null`) instead of a `boolean`. This provides more information about the API response, such as the `Action` created by the deletion.

- **Impact:** Any code checking for `if ($model->delete())` will still work as `APIResponse` is truthy, but it is better to update your logic if you were relying on the boolean result.

**Before (v2.x):**
```php
$success = $server->delete(); // returned bool
if ($success) {
// ...
}
```

**After (v3.0):**
```php
$response = $server->delete(); // returns ?APIResponse
if ($response !== null) {
$action = $response->action; // You can now access the action
// ...
}
```

### Native Type Hints
Many method signatures have been updated to include native PHP 8.1 type hints. This improves IDE support and static analysis.

- **Impact:** If you have extended SDK classes and overridden methods, you may need to update your method signatures to match the new type hints to avoid PHP fatal errors.

---

## New Features
Version 3.0 also introduces several new features and improvements:
- Support for **Primary IPs**.
- Support for **Placement Groups**.
- Support for **Firewalls** (including label selectors).
- Improved support for **Load Balancers**.
- Improved support for **Managed Certificates**.

## Summary for LLMs
- Minimum PHP: `8.2`
- Dependency removed: `illuminate/collections`
- `delete()` returns: `?LKDev\HetznerCloud\APIResponse` (was `bool`)
- All list methods return: `array`
- Models use native type hints for parameters and return types.
7 changes: 3 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@
"hetzner cloud",
"cloud php library"
],
"require": {
"php": "^8.1",
"require": {
"php": "^8.2",
"ext-json": "*",
"guzzlehttp/guzzle": "^6.3|^7.0",
"illuminate/collections": "^5.5|^v6.18|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0"
"guzzlehttp/guzzle": "^6.3|^7.0"
},
"require-dev": {
"phpunit/phpunit": "^7.0|^8.5.5|^9.0",
Expand Down
2 changes: 1 addition & 1 deletion src/HetznerAPIClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class HetznerAPIClient
/**
* Version of the API Client.
*/
const VERSION = '2.9.1';
const VERSION = '3.0.0';

const MAX_ENTITIES_PER_PAGE = 50;

Expand Down
8 changes: 4 additions & 4 deletions src/Models/Actions/Action.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ class Action extends Model implements Resource
public $progress;

/**
* @var string
* @var string|null
*/
public $started;

/**
* @var string
* @var string|null
*/
public $finished;

Expand Down Expand Up @@ -66,7 +66,7 @@ public function __construct(
string $command,
int $progress,
string $status,
string $started,
?string $started = null,
?string $finished = null,
$resources = null,
$error = null
Expand Down Expand Up @@ -148,6 +148,6 @@ public static function parse($input)
return;
}

return new self($input->id, $input->command, $input->progress, $input->status, $input->started, $input->finished, $input->resources, $input->error ?? null);
return new self($input->id, $input->command, $input->progress, $input->status, $input->started ?? null, $input->finished ?? null, $input->resources, $input->error ?? null);
}
}
4 changes: 2 additions & 2 deletions src/Models/Actions/Actions.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ public function getByName(string $name)
*/
public function setAdditionalData($input)
{
$this->actions = collect($input)->map(function ($action, $key) {
$this->actions = array_map(function ($action) {
return Action::parse($action);
})->toArray();
}, $input);

return $this;
}
Expand Down
10 changes: 8 additions & 2 deletions src/Models/Certificates/Certificate.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ class Certificate extends Model implements Resource
* @var array
*/
public $labels;
/**
* @var string
*/
public $type;

/**
* Certificate constructor.
Expand All @@ -71,8 +75,9 @@ class Certificate extends Model implements Resource
* @param string|null $fingerprint
* @param array|null $used_by
* @param array|null $labels
* @param string|null $type
*/
public function __construct(int $id, ?string $name = null, ?string $certificate = null, ?string $created = null, ?string $not_valid_before = null, ?string $not_valid_after = null, ?array $domain_names = null, ?string $fingerprint = null, $used_by = null, $labels = [])
public function __construct(int $id, ?string $name = null, ?string $certificate = null, ?string $created = null, ?string $not_valid_before = null, ?string $not_valid_after = null, ?array $domain_names = null, ?string $fingerprint = null, $used_by = null, $labels = [], ?string $type = null)
{
$this->id = $id;
$this->name = $name;
Expand All @@ -84,6 +89,7 @@ public function __construct(int $id, ?string $name = null, ?string $certificate
$this->fingerprint = $fingerprint;
$this->used_by = $used_by;
$this->labels = $labels;
$this->type = $type;

parent::__construct();
}
Expand Down Expand Up @@ -136,7 +142,7 @@ public function delete(): bool
*/
public static function parse($input)
{
return new self($input->id, $input->name, $input->certificate, $input->created, $input->not_valid_before, $input->not_valid_after, $input->domain_names, $input->fingerprint, $input->used_by, $input->labels);
return new self($input->id, $input->name, $input->certificate, $input->created, $input->not_valid_before, $input->not_valid_after, $input->domain_names, $input->fingerprint, $input->used_by, $input->labels, $input->type ?? null);
}

/**
Expand Down
20 changes: 13 additions & 7 deletions src/Models/Certificates/Certificates.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,21 @@ class Certificates extends Model implements Resources
*/
public function create(
string $name,
string $certificate,
string $privateKey,
array $labels = []
?string $certificate = null,
?string $privateKey = null,
array $labels = [],
string $type = 'uploaded'
): ?Certificate {
$parameters = [
'name' => $name,
'certificate' => $certificate,
'private_key' => $privateKey,
'type' => $type,
];
if ($certificate !== null) {
$parameters['certificate'] = $certificate;
}
if ($privateKey !== null) {
$parameters['private_key'] = $privateKey;
}
if (! empty($labels)) {
$parameters['labels'] = $labels;
}
Expand Down Expand Up @@ -116,9 +122,9 @@ public function list(?RequestOpts $requestOpts = null): ?APIResponse
*/
public function setAdditionalData($input)
{
$this->certificates = collect($input)->map(function ($certificate, $key) {
$this->certificates = array_map(function ($certificate) {
return Certificate::parse($certificate);
})->toArray();
}, $input);

return $this;
}
Expand Down
4 changes: 2 additions & 2 deletions src/Models/Datacenters/Datacenters.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,9 @@ public function getByName(string $name): ?Datacenter
*/
public function setAdditionalData($input)
{
$this->datacenters = collect($input)->map(function ($datacenter, $key) {
$this->datacenters = array_map(function ($datacenter) {
return Datacenter::parse($datacenter);
})->toArray();
}, $input);

return $this;
}
Expand Down
36 changes: 19 additions & 17 deletions src/Models/Firewalls/Firewall.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,14 @@ public static function parse($input): ?self
$rules = [];

foreach ($input->rules as $r) {
$rules[] = new FirewallRule($r->direction, $r->protocol, $r->source_ips, $r->destination_ips, (string) $r->port);
$rules[] = new FirewallRule($r->direction, $r->protocol, (isset($r->source_ips)) ? $r->source_ips : [], (isset($r->destination_ips)) ? $r->destination_ips : [], (isset($r->port)) ? (string) $r->port : '', (isset($r->description)) ? $r->description : '');
}

foreach ($input->applied_to as $a) {
if ($a->type === 'server') {
$appliedTo[] = new FirewallResource($a->type, new Server($a->server->id));
} elseif ($a->type === 'label_selector') {
$appliedTo[] = new FirewallResource($a->type, null, get_object_vars($a->label_selector));
}
}

Expand All @@ -145,18 +147,18 @@ public function setRules(array $rules): ?ApiResponse
{
$response = $this->httpClient->post('firewalls/'.$this->id.'/actions/set_rules', [
'json' => [
'rules' => collect($rules)->map(function ($r) {
'rules' => array_map(function ($r) {
return $r->toRequestSchema();
}),
}, $rules),
],
]);
if (! HetznerAPIClient::hasError($response)) {
$payload = json_decode((string) $response->getBody());

return APIResponse::create([
'actions' => collect($payload->actions)->map(function ($action) {
'actions' => array_map(function ($action) {
return Action::parse($action);
})->toArray(),
}, $payload->actions),
], $response->getHeaders());
}

Expand All @@ -168,18 +170,18 @@ public function setRules(array $rules): ?ApiResponse
*
* @see https://docs.hetzner.cloud/#firewalls-delete-a-firewall
*
* @return bool
* @return APIResponse|null
*
* @throws \LKDev\HetznerCloud\APIException
*/
public function delete(): bool
public function delete(): ?APIResponse
{
$response = $this->httpClient->delete('firewalls/'.$this->id);
if (! HetznerAPIClient::hasError($response)) {
return true;
return APIResponse::create([], $response->getHeaders());
}

return false;
return null;
}

/**
Expand All @@ -196,18 +198,18 @@ public function applyToResources(array $resources): ?APIResponse
{
$response = $this->httpClient->post('firewalls/'.$this->id.'/actions/apply_to_resources', [
'json' => [
'apply_to' => collect($resources)->map(function ($r) {
'apply_to' => array_map(function ($r) {
return $r->toRequestSchema();
}),
}, $resources),
],
]);
if (! HetznerAPIClient::hasError($response)) {
$payload = json_decode((string) $response->getBody());

return APIResponse::create([
'actions' => collect($payload->actions)->map(function ($action) {
'actions' => array_map(function ($action) {
return Action::parse($action);
})->toArray(),
}, $payload->actions),
], $response->getHeaders());
}

Expand All @@ -228,18 +230,18 @@ public function removeFromResources(array $resources): ?APIResponse
{
$response = $this->httpClient->post('firewalls/'.$this->id.'/actions/remove_from_resources', [
'json' => [
'remove_from' => collect($resources)->map(function ($r) {
'remove_from' => array_map(function ($r) {
return $r->toRequestSchema();
}),
}, $resources),
],
]);
if (! HetznerAPIClient::hasError($response)) {
$payload = json_decode((string) $response->getBody());

return APIResponse::create([
'actions' => collect($payload->actions)->map(function ($action) {
'actions' => array_map(function ($action) {
return Action::parse($action);
})->toArray(),
}, $payload->actions),
], $response->getHeaders());
}

Expand Down
Loading
Loading