Download the PHP package setono/economic-php-sdk without Composer

On this page you can find all versions of the php package setono/economic-php-sdk. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package economic-php-sdk

Economic PHP SDK

Latest Version Build Status Code Coverage Mutation testing

A modern PHP SDK for the e-conomic REST API. Typed DTOs for both requests and responses, fully constructor-injected HTTP collaborators (you bring your own PSR-18 client), no surprising magic. Built for PHP 8.4.

What you get

Requirements

Installation

If you don't already have a PSR-18 client in your project, install one alongside the SDK:

Quick start

The two constructor arguments are your app's secret token + agreement grant token; see e-conomic's docs for how to obtain them. 'demo' / 'demo' works against e-conomic's demo agreement if you just want to try the SDK without onboarding.

Reading data

Looking up a single resource

getByNumber(int|string): ?T looks up one item by its natural identifier. Returns null on 404; other client errors throw the typed exception.

The returned DTO types every first-level field of the schema — scalars, dates and reference objects alike — plus $raw as the escape hatch:

Get a single page

CollectionRequestOptions is immutable; chain withX() methods if you want to derive variants:

Filtering

Filter builds e-conomic filter expressions with a static factory per operator — eq, ne, gt, gte, lt, lte, like, in, nin — combined with ->and() / ->or():

Special characters in values ($ ( ) * , [ ]) are escaped automatically, so user input is safe to pass straight through. Two conventions to know:

Paginate (walk all pages)

paginate() follows the server-provided pagination.nextPage.url after the first page — no manual skipPages bookkeeping. Available on every collection endpoint:

If page N+1 fetch fails mid-walk, the generator is exhausted; items from page N have already been yielded. Recovering requires either retry middleware on your PSR-18 client (so transient errors don't surface here) or tracking processed IDs externally so a fresh paginate() call can dedupe.

Ping / who am I

The /self endpoint returns information about the current API agreement — useful as a smoke test that your tokens work.

Client::self() returns a SelfEndpoint (cheap, no HTTP). SelfEndpoint::get() performs GET /self on first call and memoizes the DTO for subsequent calls — calling it again in the same Client lifetime won't hit the network.

Writing data

Creating a draft order

Schema: orders.drafts.post.schema.json.

Optional fields default to null and are omitted from the JSON sent to e-conomic — there is no risk of accidentally sending an empty notes block:

Identifier is the single foreign-key wrapper for every {<x>Number: int} (or productNumber: string) reference the schema accepts. Named factories cover every reference type: Identifier::layout(), Identifier::customer(), Identifier::customerGroup(), Identifier::vatZone(), Identifier::project(), Identifier::product(), Identifier::unit(), Identifier::employee(), Identifier::customerContact(), Identifier::vendor(), Identifier::deliveryLocation(), Identifier::paymentTerms(), Identifier::departmentalDistribution().

Updating a draft order

update(int $number, DraftOrderRequest $request): Order PUTs to orders/drafts/:number. e-conomic PUT is full-replace: any field absent from the body — including every null property, which the SDK omits from the JSON — is cleared server-side. Use the same read-modify-write flow as for customers (see below): fetch the order, prefill with DraftOrderRequest::fromResponse(), change what you need, PUT the whole thing back:

Creating a customer

Schema: customers.post.schema.json.

A fuller example with optional fields:

$client->customers() also exposes getByNumber(int): ?Customer, getPage(...), and paginate(...) — the same surface as the other top-level resources.

Note on priceGroup: the e-conomic schema describes priceGroup as a { self: uri } reference with no priceGroupNumber field, breaking the universal <x>Number convention. CustomerRequest therefore omits it. Consumers needing to set the price group can drop down to the low-level helper: $client->post('customers', $hand_built_payload).

Updating a customer (read–modify–write)

e-conomic PUT is full-replace: any field absent from the body is cleared server-side. Never build an update request with only the fields you want to change — fetch the customer first, prefill a request from it with CustomerRequest::fromResponse(), mutate what you need, and PUT the whole thing back:

fromResponse() is available on every request DTO (it lives on the Payload base class): it maps the response's full decoded body ($raw) into the request DTO via Valinor. Every field the DTO models is carried over — reference objects like customerGroup or salesPerson become Identifier instances automatically, with the JSON field name inferred from the reference's <x>Number key — and everything else (server-computed fields, HATEOAS links, unmodeled schema fields) is dropped. It therefore requires a response fetched through the SDK — on a hand-constructed instance (empty $raw) it throws. Raw data that is present but malformed also throws instead of being silently dropped, because a dropped field would be wiped by the subsequent PUT.

Caveat: schema fields the SDK doesn't model (priceGroup, customerContact, attention, defaultDeliveryLocation, …) cannot be carried over and will be cleared by an update built this way. If you use those fields, hand-build the body and dispatch via Client::request().

Caveat on eInvoicingDisabledByDefault: the e-conomic docs state this property "is updatable only by using PATCH to /customers/:customerNumber" — the API's one exception to its no-PATCH-on-JSON rule. Its value in a PUT body is ignored, so changing it via update() has no effect (it is not cleared by omission either). To toggle it, send a JSON Patch request via Client::request().

Error handling

The SDK uses two distinct error patterns for reads and writes:

Lookups (getByNumber) return null on 404 — they do NOT throw. This makes optional-lookup code clean. Non-404 client errors (auth, validation) still throw the typed exception.

Writes (create, update), custom request() calls, and get() always throw on any non-2xx. Catch by type:

The exception hierarchy maps HTTP status codes to typed exceptions:

Status Exception
400, 422 ValidationException
401 UnauthorizedException
403 ForbiddenException
404 NotFoundException
405 MethodNotAllowedException
500 InternalServerErrorException
501 NotImplementedException
anything else (415, 429, 502, 504, …) UnexpectedStatusCodeException

All extend ResponseAwareException and implement EconomicException (the marker interface). Lazy-parse getters on every response-aware exception give you the e-conomic error envelope:

Retry policy belongs in a PSR-18 decorator, not in the SDK. UnexpectedStatusCodeException covers transient (429, 502, 504) and permanent (415, …) failures alike — wrap your httpClient: with retry middleware if you want automatic recovery (see Adding retries with Symfony HttpClient).

A 2xx response whose body doesn't fit the SDK's DTO shape (server schema change, version skew) surfaces as MappingException extends MalformedResponseException — same parent as a JSON-decode failure, with the original Valinor MappingError preserved as $previous for the full type-mismatch tree.

PSR-18 network errors (Psr\Http\Client\NetworkExceptionInterface, e.g. connection refused, DNS failure) propagate unwrapped — the SDK doesn't catch them. catch (EconomicException) does NOT net them; add a separate catch (\Psr\Http\Client\ClientExceptionInterface) if you want to handle network and API errors together.

Testing code that calls the SDK

Inject a fake PSR-18 client via the httpClient: named argument. PHP 8.4's anonymous classes make a single-purpose fake compact:

For tests that need to script multiple URL → response mappings, the SDK's own test suite uses a small URL-keyed fake at tests/TestDouble/ScriptedHttpClient.php (~30 lines). It's not publicly autoloaded, but copy-paste-able.

Constructing response DTOs directly in tests (without going through the SDK or Valinor) is also supported — every entry-point DTO has constructor-promoted readonly properties:

Raw access (when a field isn't typed)

Every entry-point DTO (Product, Order, BookedInvoice, Self_, Collection<T>) carries a public array $raw with the full decoded JSON for that response. All first-level schema fields are typed, so $raw is the escape hatch for the rest: HATEOAS link/meta fields (self, contacts, templates, totals, soap, …), second-level blobs the SDK deliberately skips (productGroup.accrual, application.requiredRoles), and anything e-conomic adds to the schema before the SDK does. Nested DTOs don't carry $raw — reach their slice via the parent's $raw['nested-key'].

When re-serializing a DTO (caching, logging, audit trail), use $dto->raw directly — that's the full API response. json_encode($product) would produce a hybrid of the typed-readonly fields plus an embedded raw key, which is rarely what you want.

Long-running processes

Reuse a single Client instance across a batch loop. Client construction discovers PSR-18 / PSR-17 implementations eagerly and builds default Valinor MapperBuilder / NormalizerBuilder instances — paying that cost once per item in a worker / import script is wasteful, and without caching every request also recompiles Valinor's mapping definitions.

Other requests (low-level helpers)

If the endpoint or method you want to call isn't present yet, you have two options: 1) create a PR and add the missing parts, or 2) use the SDK's low-level helpers.

For un-wrapped JSON endpoints, Client::get() returns the decoded body directly. It accepts either a path relative to the e-conomic base URI or a fully-qualified URL pointing at the e-conomic API:

Absolute URLs are validated against the SDK's base host — get() refuses to send auth credentials to any other host.

For non-JSON endpoints (PDF downloads, attachment files) or for full control of the PSR-7 cycle, build a request and use Client::request() — it still returns ResponseInterface:

Auth headers, User-Agent, and status-code dispatch all apply to both paths.

Bringing your own HTTP client

The SDK follows the PSR-18 "bring your own HTTP client" pattern: every collaborator is constructor-injected with sensible defaults. By default it discovers whatever PSR-18 / PSR-17 implementations you already have installed:

Inject your own client when you need control over the transport.

Adding retries with Symfony HttpClient

RetryableHttpClient wraps any Symfony HttpClient and re-fires the request with exponential backoff on transient failures (network errors, 5xx, 429). The SDK sees the retried response as the canonical one.

429 responses still surface as UnexpectedStatusCodeException if all retries are exhausted — the SDK doesn't have a RateLimitException. Inspect $e->getResponse()->getHeaderLine('Retry-After') if you want to back off further at the application layer.

Logging requests

There is no built-in logger. Wrap your PSR-18 client to log — any PSR-18-compatible middleware works, since the SDK never reaches around the injected client.

Strip the query and fragment from the logged URL (as above) so any consumer-supplied secrets in query params don't end up in your logs.

Production usage

The SDK uses CuyZ/Valinor to map JSON ↔ DTOs. The mapping is expensive without a cache: Valinor introspects every target class on first use, then compiles the mapping. For production, share a single Client across the request lifecycle and supply cached Valinor builders.

Two single-call helpers apply the SDK's required configuration to consumer-supplied builders. Client::configureMapperBuilder() wires the mapper side: superfluous-key tolerance (responses carry fields the DTOs don't model) and the date formats e-conomic emits. ($raw stamping happens in the SDK's endpoint layer after mapping, so it works with any builder.) Client::registerNormalizerTransformers() wires the SDK's Identifier serializer and the Payload null-skipping transformer onto a NormalizerBuilder — forget it and the SDK will throw at Client::__construct with a remediation hint.

Avoid registerConverter() on the builder you pass in: until CuyZ/Valinor#800 is fixed, a non-\Closure converter (an invokable object) leaks ~70KB per mapped object into a static cache — fatal to long-running workers iterating large collections (see issue #7).

In development, decorate the cache with Valinor's FileWatchingCache so that source-file edits invalidate compiled mappings without needing to clear the cache manually:

See Valinor: Performance and caching for full details.

Caveat on supportDateFormats(): the SDK maps both timestamp fields (e.g. Customer::$lastUpdated, 2020-02-19T09:18:09Z) and date-only fields (e.g. Order::$date, 2026-05-01) to \DateTimeImmutable. Valinor's default date handling only accepts the timestamp shape — Client::configureMapperBuilder() therefore calls supportDateFormats() with both (the date-only format is !Y-m-d; the ! pins the time to midnight UTC). Because supportDateFormats() replaces whatever was configured before, call your own supportDateFormats() either before configureMapperBuilder() (the SDK's formats win) or not at all — re-declaring formats afterwards without the SDK's full list will make mapping throw MappingException.

Supported endpoints

The SDK currently types the following endpoints. Anything not listed is reachable via the low-level helpers (see Other requests) — pull requests adding more endpoints are welcome.

Resource Read (typed DTO returned) Write
Customers $client->customers()->getByNumber(int), ->getPage(), ->paginate() $client->customers()->create(CustomerRequest), ->update(int, CustomerRequest)
Products $client->products()->getByNumber(string), ->getPage(), ->paginate()
Draft orders $client->orders()->drafts()->getByNumber(int), ->getPage(), ->paginate() $client->orders()->drafts()->create(DraftOrderRequest), ->update(int, DraftOrderRequest)
Sent orders $client->orders()->sent()->getByNumber(int), ->getPage(), ->paginate()
Booked invoices $client->invoices()->booked()->getByNumber(int), ->getPage(), ->paginate()
Self / current agreement $client->self()->get()

Identifier covers every foreign-key reference the typed request DTOs need:

Factory JSON field
Identifier::layout(int) layoutNumber
Identifier::paymentTerms(int) paymentTermsNumber
Identifier::customer(int) customerNumber
Identifier::customerGroup(int) customerGroupNumber
Identifier::vatZone(int) vatZoneNumber
Identifier::project(int) projectNumber
Identifier::deliveryLocation(int) deliveryLocationNumber
Identifier::product(string) productNumber
Identifier::unit(int) unitNumber
Identifier::employee(int) employeeNumber
Identifier::customerContact(int) customerContactNumber
Identifier::vendor(int) vendorNumber
Identifier::departmentalDistribution(int) departmentalDistributionNumber

v2 migration

v2 is a breaking redesign. If you're upgrading from v1.x:

v1.x v2.x
$client->products()->get(...) $client->products()->getPage(...)
$client->products()->get(skipPages: $i++) loop foreach ($client->products()->paginate() as $product)
$client->orders()->getDraft(...) $client->orders()->drafts()->getPage(...)
$client->orders()->getDraftByNumber(5) $client->orders()->drafts()->getByNumber(5)
$client->orders()->getSent(...) $client->orders()->sent()->getPage(...)
$client->orders()->getSentByNumber(5) $client->orders()->sent()->getByNumber(5)
$client->invoices()->getBooked(...) $client->invoices()->booked()->getPage(...)
$client->invoices()->getBookedByNumber(5) $client->invoices()->booked()->getByNumber(5)
new Query([...]) pass array directly
CollectionRequestOptions::asQuery() CollectionRequestOptions::toArray()
implements *EndpointInterface type against the concrete class
$client->setLogger(...) wrap your PSR-18 client to log

pageSize is now capped at 1000 (e-conomic's server maximum) — passing a higher value throws \InvalidArgumentException.

Contributing

Pull requests welcome — especially for missing endpoints. Before submitting:

Each new typed endpoint should ship with: a request DTO under src/Request/<Resource>/, a response DTO under src/Response/<Resource>/, the endpoint class under src/Client/Endpoint/, and end-to-end tests using ScriptedHttpClient (see tests/TestDouble/).

License

MIT. See LICENSE.


All versions of economic-php-sdk with dependencies

PHP Build Version
Package Version
Requires php Version >=8.4
cuyz/valinor Version ^2.2.2
php-http/discovery Version ^1.19
psr/http-client Version ^1.0
psr/http-client-implementation Version ^1
psr/http-factory Version ^1.0
psr/http-factory-implementation Version ^1
psr/http-message Version ^1.0 || ^2.0
webmozart/assert Version ^1.11
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package setono/economic-php-sdk contains the following files

Loading the files please wait ...