Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
19914c6
Fix API action names to match HostFact API v2 docs, remove psalm
hyperized Apr 10, 2026
63c9510
Update PHP version requirements to supported versions only
hyperized Apr 10, 2026
b293a41
Update all dependencies to latest major versions
hyperized Apr 11, 2026
c0c728b
Update GitHub workflow actions and dependabot config
hyperized Apr 11, 2026
e1b1200
Remove deprecated delete_head_branch from mergify config
hyperized Apr 11, 2026
ada37bc
Update dependencies and drop PHP 8.2 from CI
hyperized Apr 11, 2026
42bf130
Add typed response system with DataBag, ApiResponse subclasses, and R…
hyperized Apr 11, 2026
dcec2ec
Refactor API layer to return typed responses
hyperized Apr 11, 2026
8a4c737
Add typed entity classes for HostFact API v3.1
hyperized Apr 11, 2026
dfed547
Add tests, examples, and updated documentation
hyperized Apr 11, 2026
f38569a
Add backed enums for HostFact API value sets
hyperized Apr 11, 2026
92ee907
Add nullableBool, nullableDateTime, and nullableEnum helpers
hyperized Apr 11, 2026
fbaf614
Replace string properties with strict types in all entity classes
hyperized Apr 11, 2026
7adbf14
Update docs and examples to use typed entities
hyperized Apr 11, 2026
67ba1ee
Add tests for Group, Order, PriceQuote, and Ticket entities
hyperized Apr 11, 2026
2b371ff
Add tests for ServiceProvider::provides and Facade accessor
hyperized Apr 11, 2026
7a037e3
Enable prefer-lowest in CI matrix
hyperized Apr 11, 2026
9dffe57
Reduce extractErrors cyclomatic complexity to satisfy PHPMD
hyperized Apr 11, 2026
1623327
Bump phpmd/phpmd to ^2.14 for readonly class support
hyperized Apr 11, 2026
d12552a
Update composer.lock
hyperized Apr 11, 2026
48dac38
Drop PHP 8.3, require PHP 8.4+
hyperized Apr 11, 2026
034f1e9
Add #[\Override] to entity fromBag() methods
hyperized Apr 11, 2026
d415cfe
Raise PHPMD cyclomatic complexity threshold to 10
hyperized Apr 11, 2026
64a6363
Bump hyperized/value-objects to ^2.0.0
hyperized Apr 11, 2026
de05b76
Remove thecodingmachine/safe, use native JSON_THROW_ON_ERROR
hyperized Apr 11, 2026
3c9f046
Preserve controller/action in JSON parse error responses
hyperized Apr 11, 2026
1186010
Use ReflectionNamedType for enum backing type check
hyperized Apr 11, 2026
b65fab1
Prevent credential leaks in exception messages
hyperized Apr 11, 2026
70d2293
Fix dependabot typo in mergify config
hyperized Apr 11, 2026
16020af
Remove phpcbf from CI, bump phpcs minimum to 3.8
hyperized Apr 11, 2026
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
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ updates:
interval: weekly
time: "08:00"
open-pull-requests-limit: 10
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
time: "08:00"
open-pull-requests-limit: 5
13 changes: 5 additions & 8 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ jobs:
fail-fast: false
matrix:
php:
- 8.1
- 8.2
- 8.4
- 8.5
dependency-version:
# - prefer-lowest
- prefer-lowest
- prefer-stable
os:
- ubuntu-latest
Expand All @@ -24,19 +24,16 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v1
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: curl, json
coverage: pcov
- name: Composer self-update
run: |
composer self-update
- name: Composer update
run: |
composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest
composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction
- name: Composer ${{ matrix.test }}-${{ matrix.os }}
run: |
composer ${{ matrix.test }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
/.idea/
clover.xml
.phpunit.result.cache
CLAUDE.md
8 changes: 1 addition & 7 deletions .mergify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,10 @@ pull_request_rules:
type: APPROVE
message: Automatically approving dependabot

- name: automatic merge on depenabot pull requests
- name: automatic merge on dependabot pull requests
conditions:
- author=dependabot[bot]
- check-success~=^test*
actions:
merge:
method: merge

- name: delete head branch after merge
conditions:
- merged
actions:
delete_head_branch:
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2105 Gerben Geijteman
Copyright (c) 2015 Gerben Geijteman

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
215 changes: 161 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,95 +1,202 @@
# Hostfact API 3.0 for Laravel
# Hostfact API v3.1 for Laravel

[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fhyperized%2Fhostfact.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fhyperized%2Fhostfact?ref=badge_shield)
[![Run tests](https://github.com/hyperized/hostfact/actions/workflows/main.yml/badge.svg)](https://github.com/hyperized/hostfact/actions/workflows/main.yml)

Official documentation:
-----------------------
Unofficial Laravel package for the [HostFact API v2](https://www.hostfact.nl/developer/api/).

* [Hostfact API documentation](https://www.hostfact.nl/developer/api/)
## Requirements

Installation
------------
- PHP 8.4+
- Laravel 13 and above (auto-discovery supported)

Install using composer:
## Installation

```bash
composer require hyperized/hostfact
```

This package supports Package Auto-Discovery (Laravel 5.5+) so it doesn't require you to manually add the
ServiceProvider and alias.
Publish the configuration:

If you are using a lower version of Laravel or not using Auto-Discovery you can add the Hostfact Service Provider to
the `config/app.php` file
```bash
php artisan vendor:publish --provider="Hyperized\Hostfact\Providers\HostfactServiceProvider" --tag="config"
```

```php
Hyperized\Hostfact\HostfactServiceProvider::class,
## Configuration

Add these to your `.env` file:

```env
HOSTFACT_URL=https://yoursite.tld/Pro/apiv2/api.php
HOSTFACT_KEY=your-api-token
HOSTFACT_TIMEOUT=20
```

Register an alias for Hostfact, also in `config/app.php`:
Or edit `config/Hostfact.php` directly:

```php
'Hostfact' => Hyperized\Hostfact\HostfactServiceProvider::class,
return [
'api_v2_url' => env('HOSTFACT_URL', 'https://yoursite.tld/Pro/apiv2/api.php'),
'api_v2_key' => env('HOSTFACT_KEY', 'token'),
'api_v2_timeout' => env('HOSTFACT_TIMEOUT', 20),
];
```

Now publish the Hostfact package into your installation:
## Usage

```bash
php artisan vendor:publish --provider="Hyperized\Hostfact\HostfactServiceProvider" --tag="config"
```
Every controller provides a static `new()` factory that reads configuration from Laravel automatically:

```php
use Hyperized\Hostfact\Api\Controllers\Product;
use Hyperized\Hostfact\Api\Entity\Product as ProductEntity;
use Hyperized\Hostfact\Api\Response\ListResponse;

This should give you a message
like: `Copied File [/vendor/hyperized/hostfact/config/Hostfact.php] To [/config/Hostfact.php]`
$response = Product::new()->list(['searchfor' => 'hosting']);

It's possible to edit your configuration variables in the `config/Hostfact.php` file or you can use the `HOSTFACT_URL`
and `HOSTFACT_KEY` environment variables to store sensitive information in the `.env` file
if ($response instanceof ListResponse) {
echo $response->pagination->totalResults . ' results';

foreach ($response->entities as $product) {
assert($product instanceof ProductEntity);
echo $product->ProductName;
echo $product->PriceExcl;
}
}
```

For dependency injection or testing, use `fromHttpClient()`:

```php
// config/Hostfact.php
'api_v2_url' => env('HOSTFACT_URL', 'https://yoursite.tld/Pro/apiv2/api.php'),
'api_v2_key' => env('HOSTFACT_KEY', 'token'),
'api_v2_timeout' => env('HOSTFACT_TIMEOUT', 20),
use Hyperized\Hostfact\Api\Controllers\Invoice;

// .env/.env.example
HOSTFACT_URL=https://yoursite.tld/Pro/apiv2/api.php
HOSTFACT_KEY=token
HOSTFACT_TIMEOUT=20
$invoice = Invoice::fromHttpClient($httpClient);
$result = $invoice->show(['Identifier' => 'F0001']);
```

Functionality
---------
See the [examples/](examples/) directory for more complete usage patterns.

## Typed Responses

All API methods return an `ApiResponse` subclass:

| Response Type | When | Properties |
|---|---|---|
| `ShowResponse` | Single entity returned | `entity` (typed entity), `data` (DataBag) |
| `ListResponse` | Multiple entities returned | `entities` (list of typed entities), `items` (list of DataBag), `pagination` |
| `ActionResponse` | Action with no entity data (e.g. markAsPaid) | — |
| `ErrorResponse` | API returned an error | `errors` (list of strings) |

When writing code for this Hostfact package, consider that this package has been written as a basic interface.
All responses share: `controller`, `action`, `status`, `date`, `isSuccess()`, `isError()`, `toArray()`.

This package _will_ do the following:
### Typed Entities

* Provide an easy way to communicate with Hostfact API controllers;
* Document the available API controller endpoints with methods;
* Transport layer (HTTP/HTTPS) error catching;
* Basic error parsing;
Responses include typed entity objects with IDE autocompletion and strict PHP types:

```php
use Hyperized\Hostfact\Api\Controllers\Invoice;
use Hyperized\Hostfact\Api\Entity\Invoice as InvoiceEntity;
use Hyperized\Hostfact\Api\Response\ShowResponse;

$response = Invoice::new()->show(['InvoiceCode' => 'F0001']);
assert($response instanceof ShowResponse);

$invoice = $response->entity;
assert($invoice instanceof InvoiceEntity);

$invoice->Identifier; // ?int
$invoice->InvoiceCode; // ?string
$invoice->Status; // ?InvoiceStatus (enum)
$invoice->Date; // ?DateTimeImmutable
$invoice->AmountExcl; // ?string (for bcmath precision)
$invoice->Sent; // ?bool

// Nested entities
foreach ($invoice->InvoiceLines as $line) {
$line->Description; // ?string
$line->PriceExcl; // ?string
}

// Fallback to DataBag for undocumented fields
$invoice->bag->string('SomeUndocumentedField');
```

This package _will not_:
For list responses:

* Parameter / input validation;
* Output validation;
```php
$response = Product::new()->list(['searchfor' => 'hosting']);
assert($response instanceof ListResponse);

foreach ($response->entities as $product) {
assert($product instanceof ProductEntity);
echo $product->ProductCode;
echo $product->ProductName;
}
```

You will need to consult the [Hostfact API documentation](https://www.hostfact.nl/developer/api/) to understand the
acceptable input and output for each of the API controllers.
Available entity classes: `Product`, `Debtor`, `Invoice`, `Domain`, `Hosting`, `Ssl`, `Vps`, `Ticket`, `Order`, `PriceQuote`, `Creditor`, `Group`. Controllers without documented fields (`Service`, `CreditInvoice`, `Handle`) return a `DataBag` as the entity.

Examples
--------
### DataBag

Example code:
The raw API data is also accessible through typed methods on `DataBag`:

```php
use \Hyperized\Hostfact\Api\Controllers\Product;
$bag->string('ProductCode') // string
$bag->int('Identifier') // int
$bag->float('PriceExcl') // float
$bag->bool('AutoRenew') // bool (handles "yes"/"no", 1/0)
$bag->nullableString('Comment') // ?string
$bag->nullableInt('PackageID') // ?int
$bag->nullableBool('Sent') // ?bool
$bag->nullableDateTime('Created') // ?DateTimeImmutable
$bag->array('Groups') // array
$bag->bag('Subscription') // nested DataBag
$bag->bags('InvoiceLines') // list<DataBag>
$bag->has('SomeField') // bool
$bag['ProductCode'] // mixed (ArrayAccess)
```

## Available Controllers

| Controller | Actions |
|---|---|
| `CreditInvoice` | show, list, add, edit, delete, partialPayment, markAsPaid, lineAdd, lineDelete, attachmentAdd, attachmentDelete, attachmentDownload |
| `Creditor` | show, list, add, edit, delete, attachmentAdd, attachmentDelete, attachmentDownload |
| `Debtor` | show, list, add, edit, checkLogin, updateLoginCredentials, generatePdf, sendEmail, attachmentAdd, attachmentDelete, attachmentDownload |
| `Domain` | show, list, add, edit, terminate, delete, getToken, lock, unlock, changeNameserver, syncWhois, editWhois, check, transfer, register, autoRenew, listDnsTemplates, getDnsZone, editDnsZone |
| `Group` | show, list, add, edit, delete |
| `Handle` | show, list, add, edit, delete, listDomain |
| `Hosting` | show, list, add, edit, terminate, delete, suspend, unsuspend, create, removeFromServer, getDomainList, emailAccountData, upDowngrade |
| `Invoice` | show, list, add, edit, delete, credit, partialPayment, markAsPaid, markAsUnpaid, sendByEmail, sendReminderByEmail, sendSummationByEmail, download, lineAdd, lineDelete, attachmentAdd, attachmentDelete, attachmentDownload, block, unblock, schedule, cancelSchedule, paymentProcessPause, paymentProcessReactivate |
| `Order` | show, list, add, edit, process, lineAdd, lineDelete |
| `PriceQuote` | show, list, add, edit, delete, sendByEmail, download, accept, decline, lineAdd, lineDelete, attachmentAdd, attachmentDelete, attachmentDownload |
| `Product` | show, list, add, edit, delete |
| `Service` | show, list, add, edit, terminate |
| `Ssl` | show, list, add, edit, terminate, request, markAsInstalled, download, reissue, renew, getStatus, resendApproverEmail, revoke, markAsUninstalled |
| `Ticket` | show, list, add, edit, delete, addMessage, changeStatus, changeOwner, attachmentDownload |
| `Vps` | show, list, add, edit, terminate, create, start, pause, restart, suspend, unsuspend, downloadAccountData, emailAccountData |

## Design

This package provides a thin wrapper around the HostFact API. It does **not** validate input parameters. Consult the [HostFact API documentation](https://www.hostfact.nl/developer/api/) for accepted parameters.

Architecture:
- **Controllers** extend the abstract `Api` class and compose capability **traits** (e.g., `CanShow`, `CanList`, `CanAdd`)
- Each controller implements a corresponding **interface**
- All API methods accept `array<string, mixed>` and return typed `ApiResponse` subclasses
- HTTP transport is handled by Guzzle 7.x

## Testing

```bash
# Full test suite (PHPMD, PHPStan, PHPCS, phpmnd, PHPUnit, Infection)
composer test

# PHPUnit only
composer phpunit -- --configuration phpunit.xml.dist

$products = Product::new()
->list([
'searchfor' => 'invoice'
]);
# Static analysis
composer phpstan -- analyse
```

## License

[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fhyperized%2Fhostfact.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fhyperized%2Fhostfact?ref=badge_large)
MIT - see [LICENSE](LICENSE) for details.
24 changes: 8 additions & 16 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,18 @@
}
],
"require": {
"php": "^8.1",
"php": "^8.4",
"guzzlehttp/guzzle": "^7.5",
"hyperized/value-objects": "^0.3.0",
"thecodingmachine/safe": "^2.4"
"hyperized/value-objects": "^2.0.0"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"phpmd/phpmd": "^2.13",
"vimeo/psalm": "^5.1",
"orchestra/testbench": "^7.15 || ^8.0",
"phpstan/phpstan": "^1.9",
"squizlabs/php_codesniffer": "^3.7 || ^4.0",
"phpunit/phpunit": "^13.0",
"phpmd/phpmd": "^2.14",
"orchestra/testbench": "^11.0",
"phpstan/phpstan": "^2.0",
"squizlabs/php_codesniffer": "^3.8 || ^4.0",
"povils/phpmnd": "^3.0",
"infection/infection": "^0.26.16 || ^0.27.0"
"infection/infection": "^0.32"
},
"autoload": {
"psr-4": {
Expand All @@ -55,12 +53,8 @@
"test": [
"@phpmd --version",
"@phpmd --strict src text cyclomatic.xml",
"@psalm --version",
"@psalm",
"@phpstan --version",
"@phpstan analyse",
"@phpcbf --version",
"@phpcbf src",
"@phpcs --version",
"@phpcs src --standard=PSR2",
"@phpmnd --version",
Expand All @@ -75,10 +69,8 @@
],
"phpunit": "vendor/phpunit/phpunit/phpunit",
"phpmd": "vendor/bin/phpmd",
"psalm": "vendor/bin/psalm",
"phpstan": "vendor/bin/phpstan",
"phpcs": "vendor/bin/phpcs",
"phpcbf": "vendor/bin/phpcbf",
"phpmnd": "vendor/bin/phpmnd src",
"infection": "vendor/bin/infection",
"major": [
Expand Down
Loading
Loading