engeni/api-client-2

Engeni - API client V2

Maintainers

Package info

bitbucket.org/engeni-team/api-client-2

Homepage

pkg:composer/engeni/api-client-2

Statistics

Installs: 482

Dependents: 0

Suggesters: 0

v1.0.11 2026-04-27 02:24 UTC

This package is auto-updated.

Last update: 2026-04-27 02:39:52 UTC


README

This refactor delivers a brand-new, SOLID-oriented Engeni API client that mirrors Laravel Eloquent's read/query and write behavior while remaining purely HTTP-driven. The legacy implementation now lives under old/, letting the package evolve without backward-compatibility constraints.

Highlights

  • Complete CRUD support: Fluent, Eloquent-style operations for reading (first, firstOrFail, find, findOrFail, get, paginate, simplePaginate, pluck, value, count, exists, doesntExist, cursor, chunk) and writing (create, save, update, delete, destroy, upsert).
  • Mass assignment protection: Laravel-style $fillable and $guarded properties for secure attribute handling.
  • Change tracking: Full support for isDirty(), wasChanged(), getChanges(), getOriginal(), and getPrevious() methods.
  • Dependency inversion throughout: a swappable HttpClientInterface keeps the runtime and tests fully mockable.
  • Lightweight resource models that hydrate from Engeni's REST responses while offering familiar Eloquent ergonomics.
  • Laravel-first integration with automatic service-provider discovery; once installed, the client is resolved from the container without manual wiring.
  • Requires PHP 8.3+, letting us lean on modern language features such as typed class constants for safer configuration.
  • Comprehensive PHPUnit + Orchestra Testbench suite, plus Bitbucket Pipelines that run pint --test and phpunit on pull requests and master merges.
  • Observability helpers such as Engeni\ApiClient\Http\TracingHttpClient let you capture the exact URL, method, and query parameters generated by the client without enabling verbose debug output.

Requirements

  • PHP 8.3 or newer
  • Laravel support components 12.x or 13.x (pulled in automatically)

Installation

composer require engeni/api-client

Quick Start

use Engeni\ApiClient\Resources\LaGuiaOnline\Business;

// Configure credentials via .env (see config/engeni.php).

// Reading data
$popularBusinesses = Business::query()
    ->where('country_id', 54)
    ->orderBy('name')
    ->paginate(25);

// Creating records
$business = Business::create([
    'name' => 'Acme Corp',
    'country_id' => 54,
    'category_id' => 123,
]);

// Updating records
$business->name = 'Updated Corp Name';
$business->save();

// Deleting records
$business->delete();

Configuration

  • The package ships with config/engeni.php (publish via php artisan vendor:publish --tag=engeni-api-client-config).
  • By default it reads the following environment variables (with sensible fallbacks):
    • ENGENI_API_CLIENT_BASE_URI (falls back to ENGENI_SERVICE_BASE_URI)
    • ENGENI_API_CLIENT_TOKEN (falls back to ENGENI_SERVICE_API_TOKEN)
    • ENGENI_API_CLIENT_AUTH_MODE
    • ENGENI_API_CLIENT_TIMEOUT
    • ENGENI_API_CLIENT_DEBUG
    • ENGENI_API_CLIENT_HTTP_DEBUG
  • ENGENI_SERVICE_ID (used for the default User-Agent)
  • The service provider binds both Client::class and HttpClientInterface::class as singletons and shares the instance with every ResourceModel, so you can call any resource class without manual bootstrapping.
  • Need custom transport? Bind your own implementation of Engeni\ApiClient\Contracts\Http\HttpClientInterface or tweak the published config.

Write Semantics

ResourceModel::save() keeps REST-style create/update semantics:

  • new models are created with POST
  • existing models are updated with PATCH
  • existing models with no dirty attributes skip the HTTP request entirely
  • existing model updates only send dirty attributes

This keeps create operations explicit while making updates line up with the package's change-tracking API and the documented API convention that PATCH represents partial updates.

Request-Scoped Auth Forwarding

Some Laravel apps use this package as an authenticated proxy in front of api.engeni.com. In that setup, the inbound request may already carry the real credential that should reach the upstream API:

  • Authorization: Bearer <user-token>
  • X-Api-Token: <client-token>

If the client always sends the package's configured service token instead, the upstream gateway sees the wrong auth context. That can lead to permission mismatches, wrong tenant resolution, or non-JSON upstream failures because the request is being evaluated as a different caller.

To support that proxy use case, api-client-2 provides request-scoped auth forwarding by default:

// config/engeni.php
'api_client' => [
    'base_uri' => env('ENGENI_API_CLIENT_BASE_URI'),
    'token' => env('ENGENI_API_CLIENT_TOKEN'),
    'auth' => [
        'mode' => env('ENGENI_API_CLIENT_AUTH_MODE', 'forward_request'),
        'forward_headers' => ['Authorization', 'X-Api-Token'],
    ],
],

The default is forward_request. Queue workers, commands, and service-to-service jobs still keep using the configured service credential when there is no current HTTP request or when the inbound request does not include forwarded auth headers.

The package also treats its shipped config as the baseline and recursively merges app-level overrides on top. That matters when a consuming app publishes config/engeni.php and only defines a partial api_client array: omitted nested keys such as auth.mode and auth.forward_headers still inherit the package defaults instead of being dropped.

When ENGENI_API_CLIENT_AUTH_MODE=forward_request, the package resolves auth at request time using this precedence:

  1. Forward inbound Authorization if present.
  2. Else forward inbound X-Api-Token if present.
  3. Else fall back to the configured service token.

Important behavior:

  • forwarded auth is the default mode
  • when Authorization is forwarded, the configured X-Api-Token is not injected
  • when X-Api-Token is forwarded, the configured Authorization is not injected
  • without an inbound Laravel request, the client falls back to the configured static token

Example with a user bearer token:

// Inbound request to your app
Authorization: Bearer user-access-token

// Outbound request sent by api-client-2
Authorization: Bearer user-access-token

Example with a client API token:

// Inbound request to your app
X-Api-Token: 29694|token-from-client

// Outbound request sent by api-client-2
X-Api-Token: 29694|token-from-client

Example with no inbound request auth, such as a queue job:

// Configured token
ENGENI_API_CLIENT_TOKEN=ThisIsAFakeToken

// Outbound request sent by api-client-2
Authorization: Bearer ThisIsAFakeToken
X-Api-Token: ThisIsAFakeToken

Typical Laravel .env setup for proxy applications:

ENGENI_API_CLIENT_AUTH_MODE=forward_request
ENGENI_API_CLIENT_TOKEN=ThisIsAFakeToken

In non-proxy applications, or when all outbound calls should always use the service account, leave the mode as static.

Tracing Requests

Need to inspect the exact HTTP request being dispatched without enabling verbose dumps? Wrap any existing HTTP transport in the TracingHttpClient decorator. It records every call, exposing helper methods that you can assert against in tests or print in diagnostic scripts.

use Engeni\ApiClient\Client;
use Engeni\ApiClient\Http\TracingHttpClient;
use Engeni\ApiClient\Contracts\Http\HttpClientInterface;
use Engeni\ApiClient\Resources\LaGuiaOnline\Country;

// In Laravel, the package transport is backed by `Illuminate\Http\Client\Factory`,
// so it is compatible with `Http::fake()`. To trace requests, decorate the
// resolved transport:
$http = new TracingHttpClient(app(HttpClientInterface::class));

$client = new Client(
    http: $http,
    baseUri: config('services.engeni.base_uri'),
);

Country::setClient($client);
Country::query()->with('states')->limit(3)->get();

dump($http->lastRequest());
// [
//     'method'  => 'GET',
//     'uri'     => 'https://api.engeni.com/lgo/countries',
//     'options' => [
//         'query' => [
//             'embed' => 'states',
//             'limit'   => 3,
//         ],
//     ],
// ]

$http->clear(); // reset the log once you have asserted what you need

Because TracingHttpClient implements the same contract as the production transport you can drop it into integration tests, CLI tools, or debugging sessions. The examples/countries.php playground ships with the tracer enabled so you can see every generated URL after each scenario.

Toby Resources

The package also ships Toby resources under Engeni\ApiClient\Resources\Toby.

use Engeni\ApiClient\Resources\Toby\Invoice;
use Engeni\ApiClient\Resources\Toby\PaymentMethod;
use Engeni\ApiClient\Resources\Toby\AccountPaymentMethod;

// Read global Toby catalogs
$paymentMethods = PaymentMethod::all();

// Read and write account-scoped Toby resources
$accountMethods = AccountPaymentMethod::forAccount(2503)->get();
$method = AccountPaymentMethod::createForAccount(2503, [
    'name' => 'Stripe',
    'payment_method_id' => 'STRIPE',
    'account_invoice_method_id' => 3,
    'enabled' => true,
    'public' => false,
]);

// Trigger invoice actions exposed by Toby
$invoice = Invoice::findOrFail(99);
$invoice->charge(asyncMode: true);

Account-scoped Toby resources such as AccountPaymentMethod, AccountInvoiceMethod, and AccountPaymentType must be accessed through forAccount(...) or createForAccount(...) so the client can target the nested accounts/{account_id}/... endpoints correctly.

For CXM-style account payment type lists, PaymentTypeEnum includes getCaseByAccountTypes() to map the Toby types payloads into enum cases while ignoring TELECOM, matching the legacy behavior.

Legacy Namespace Coverage

This package now includes the first compatibility layer for the main legacy resource families:

  • Engeni\ApiClient\Resources\CXM\*
  • Engeni\ApiClient\Resources\CXM\Product\*
  • Engeni\ApiClient\Resources\CXM\SystemEvent\*
  • Engeni\ApiClient\Resources\Landkit\*
  • Engeni\ApiClient\Resources\Cleepo\*
  • Engeni\ApiClient\Resources\Flokee\*
  • Engeni\ApiClient\Resources\MediaManager\*
  • Engeni\ApiClient\Resources\LaGuiaOnline\*
  • Engeni\ApiClient\Resources\Toby\*

This compatibility layer now covers the legacy helper methods for the main CXM, Cleepo, Flokee, Landkit, MediaManager, and Toby resource families, while keeping the new typed Api Client resource model. The focus is still on migration-safe endpoint coverage rather than reproducing every legacy internal pattern.

Eloquent Patches

The package also ships Laravel 12 Eloquent patch files under patches/eloquent/12.0 and advertises them in composer.json via netresearch/composer-patches-plugin.

These patches are global consumer-app changes. They extend Eloquent so Laravel models can define belongsTo() and hasMany() relations that point to ApiClient resources, and they add eager-loading support for those patched relation types.

Architecture at a Glance

  • Client – Holds the base URI, default headers, and exposes newQuery() for every resource definition.
  • HttpClientInterface – Abstraction over the transport layer (backed by Laravel's HTTP client).
  • Query\Builder – Fluent query object that translates Eloquent-style calls into Engeni-friendly query strings.
  • ResourceModel – Minimal model layer that hydrates responses, offers attribute access, collections, and paginator interop.
  • Support utilities – Helpers for pagination, response parsing, and tests (see tests/Support).

Documentation

See context/guide.md for an extensive reference covering:

  • Contract diagram, dependency injection guidelines, and how to provide your own HTTP transport.
  • Every query builder method with signatures, behaviour notes, and examples.
  • Pagination semantics (mapping Engeni meta/links into Laravel paginators).
  • Testing recipes (mocking HttpClientInterface, using Orchestra, asserting request shapes).
  • Pipelines, coding standards, and the roadmap for introducing write operations.

See context/resources.md for the concrete resource catalog, including every shipped resource class, its path root, and each compatibility/helper method exposed by that resource.

Quality Gates

composer install
composer lint                   # runs Pint in --test mode
composer test                   # runs PHPUnit + Orchestra suite
composer test:coverage          # runs tests with HTML and text coverage reports
composer test:coverage:clover   # runs tests with Clover XML coverage report

After running composer test:coverage, open coverage/html/index.html in your browser to view the detailed coverage report.

bitbucket-pipelines.yml executes the same commands on pull requests and on merges to master, ensuring styling and tests stay green before code lands.

Contributing

  • Keep contributions read-only until write operations are designed.
  • Prefer new contracts over deep inheritance; the package leans on dependency injection to ease mocking.
  • Update docs and tests whenever you add behaviour.

License

GNU GPLv3 © Engeni International LLC.