commit 86b1e39360863208dad9c97bb9de315cc2d1c7b1 Author: magdev Date: Thu Oct 9 00:30:38 2025 +0200 initial version, implemented StatusApi diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..bb53136 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d839839 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +vendor/ + +play.php diff --git a/README.md b/README.md new file mode 100644 index 0000000..a679f7e --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# PHP-API-Client for Dolibarr ERP \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..9bcfbd7 --- /dev/null +++ b/composer.json @@ -0,0 +1,21 @@ +{ + "name": "magdev/dolibarr-api", + "description": "API-Client for Dolibarr ERP", + "type": "library", + "license": "MIT", + "minimum-stability": "stable", + "authors": [ + { + "name": "magdev", + "email": "magdev3.0@gmail.com" + } + ], + "require": { + "symfony/http-client": "^7.3" + }, + "autoload": { + "psr-4": { + "Magdev\\DolibarrApi\\": "src/" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..0616d30 --- /dev/null +++ b/composer.lock @@ -0,0 +1,530 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "605899efcc3f582b7c86103aad0bbaf9", + "packages": [ + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/http-client", + "version": "v7.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/4b62871a01c49457cf2a8e560af7ee8a94b87a62", + "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/polyfill-php83": "^1.29", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "amphp/amp": "<2.5", + "amphp/socket": "<1.1", + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.4" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/http-client": "^4.2.1|^5.0", + "amphp/http-tunnel": "^1.0|^2.0", + "guzzlehttp/promises": "^1.4|^2.0", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/amphp-http-client-meta": "^1.0|^2.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v7.3.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-09-11T10:12:26+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "75d7043853a42837e68111812f4d964b01e5101c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c", + "reference": "75d7043853a42837e68111812f4d964b01e5101c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-29T11:18:49+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-08T02:45:35+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-25T09:37:31+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/src/Api/AbstractApi.php b/src/Api/AbstractApi.php new file mode 100644 index 0000000..4dfe809 --- /dev/null +++ b/src/Api/AbstractApi.php @@ -0,0 +1,36 @@ +setClient(client: $client); + } + } + + public function setClient(HttpClientInterface $client): static + { + $this->client = $client; + + return $this; + } + + protected function getClient(): ?HttpClientInterface + { + return $this->client; + } + + protected function getPath(string $path): string + { + return self::PATH_PREFIX.$path; + } +} diff --git a/src/Api/ApiInterface.php b/src/Api/ApiInterface.php new file mode 100644 index 0000000..b63c4a6 --- /dev/null +++ b/src/Api/ApiInterface.php @@ -0,0 +1,10 @@ +getClient()) { + $response = $client->request(method: 'GET', url: $this->getPath(path: '/status')); + + if ($response->getStatusCode() == 200) { + $result = json_decode($response->getContent(), true); + + if (isset($result['success'])) { + return new StatusModel($result['success']); + } + + throw new DolibarrApiException( + message: sprintf('Invalid response: %s', $response->getContent()), + code: DolibarrApiClientInterface::E_INVALID_RESPONSE + ); + } + + throw new DolibarrApiException( + message: sprintf('Response Code %s from %s', $response->getStatusCode(), __METHOD__), + code: DolibarrApiClientInterface::E_INVALID_RESPONSE + ); + } + + return null; + } +} diff --git a/src/DolibarrApiClient.php b/src/DolibarrApiClient.php new file mode 100644 index 0000000..c476597 --- /dev/null +++ b/src/DolibarrApiClient.php @@ -0,0 +1,104 @@ +setClient(client: $client) + ->setToken(token: $token) + ->setBaseUri(baseUri: $baseUri) + ->setVerifyHttps(verifyHttps: $verifyHttps); + } + + public function setToken(?string $token = null): static + { + $this->token = $token; + + return $this; + } + + public function setBaseUri(?string $baseUri = null): static + { + $this->baseUri = $baseUri; + + return $this; + } + + + public function setClient(?HttpClientInterface $client = null): static + { + $this->client = $client; + + return $this; + } + + public function status(): StatusApi + { + return new StatusApi(client: $this->getClient()); + } + + private function getBaseUri(): string + { + return $this->baseUri; + } + + private function getClient(): HttpClientInterface + { + if (!$this->baseUri) { + throw new DolibarrApiClientException( + message: 'The property baseUri is mandatory', + code: DolibarrApiClientInterface::E_INVALID_VALUE + ); + } + + if (!$this->token) { + throw new DolibarrApiClientException( + message: 'The property token is mandatory', + code: DolibarrApiClientInterface::E_INVALID_VALUE + ); + } + + if (!$this->client) { + $this->client = HttpClient::createForBaseUri(baseUri: $this->getBaseUri(), defaultOptions: [ + 'headers' => [ + self::APIKEY_HEADER => $this->token, + 'Accept' => 'application/json', + ], + 'verify_host' => $this->verifyHttps, + 'verify_peer' => $this->verifyHttps, + ]); + + return $this->client; + } + + return $this->client->withOptions(options: [ + 'base_uri' => $this->getBaseUri(), + 'headers' => [ + self::APIKEY_HEADER => $this->token, + 'Accept' => 'application/json', + ], + 'verify_host' => $this->verifyHttps, + 'verify_peer' => $this->verifyHttps, + ]); + } + + public function setVerifyHttps(?bool $verifyHttps): self + { + $this->verifyHttps = $verifyHttps; + + return $this; + } +} diff --git a/src/DolibarrApiClientException.php b/src/DolibarrApiClientException.php new file mode 100644 index 0000000..b18198a --- /dev/null +++ b/src/DolibarrApiClientException.php @@ -0,0 +1,8 @@ +statusCode; + } + + public function setStatusCode(int $statusCode): static + { + $this->statusCode = $statusCode; + + return $this; + } +} diff --git a/src/Model/ModelInterface.php b/src/Model/ModelInterface.php new file mode 100644 index 0000000..a0087b1 --- /dev/null +++ b/src/Model/ModelInterface.php @@ -0,0 +1,11 @@ +fromArray(data: $data); + } + } + + public function fromArray(array $data): static + { + $this->setDolibarrVersion(dolibarVersion: $data['dolibarr_version']) + ->setAccessLocked(accessLocked: (bool) $data['access_locked']) + ->setEnvironment(environment: $data['environment']) + ->setTimestampNowUtc(timestampNowUtc: new DateTimeImmutable('@'.$data['timestamp_now_utc'], new DateTimeZone('UTC'))) + ->setDateTz(dateTz: $data['date_tz']) + ->setTimestampPhpTz(timestampPhpTz: $data['timestamp_php_tz']) + ->setStatusCode(statusCode: $data['code']) + ; + return $this; + } + + public function toArray(): array + { + return [ + 'dolibarr_version' => $this->getDolibarrVersion(), + 'access_locked' => (int) $this->isAccessLocked(), + 'environment' => $this->getEnvironment(), + 'timestamp_now_utc' => $this->getTimestampNowUtc(), + 'timestamp_php_tz' => $this->getTimestampPhpTz(), + 'date_tz' => $this->getDateTz(), + 'code' => $this->getStatusCode(), + ]; + } + + public function getDolibarrVersion(): ?string + { + return $this->dolibarVersion; + } + + public function setDolibarrVersion(?string $dolibarVersion): self + { + $this->dolibarVersion = $dolibarVersion; + + return $this; + } + + public function isAccessLocked(): ?bool + { + return $this->accessLocked; + } + + public function setAccessLocked(?bool $accessLocked): self + { + $this->accessLocked = $accessLocked; + + return $this; + } + + public function getEnvironment(): ?string + { + return $this->environment; + } + + public function setEnvironment(?string $environment): self + { + $this->environment = $environment; + + return $this; + } + + public function getTimestampNowUtc(): ?DateTimeImmutable + { + return $this->timestampNowUtc; + } + + public function setTimestampNowUtc(DateTimeImmutable|string|null $timestampNowUtc): self + { + if (is_string($timestampNowUtc)) { + $timestampNowUtc = new DateTimeImmutable($timestampNowUtc, new DateTimeZone('UTC')); + } + + $this->timestampNowUtc = $timestampNowUtc; + + return $this; + } + + public function getTimestampPhpTz(): ?DateTimeZone + { + return $this->timestampPhpTz; + } + + public function setTimestampPhpTz(DateTimeZone|string|null $timestampPhpTz): self + { + if (is_string($timestampPhpTz)) { + $timestampPhpTz = new DateTimeZone($timestampPhpTz); + } + + $this->timestampPhpTz = $timestampPhpTz; + + return $this; + } + + public function getDateTz(): ?DateTimeImmutable + { + return $this->dateTz; + } + + public function setDateTz(DateTimeImmutable|string|null $dateTz): self + { + if (is_string($dateTz)) { + $dateTz = new DateTimeImmutable($dateTz); + } + + $this->dateTz = $dateTz; + + return $this; + } +}