Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Apicil Client Bundle Laravel Package

mpp/apicil-client-bundle

MPP Apicil Client Bundle integrates the Apicil API client into Symfony applications, offering service configuration, dependency injection, and a convenient setup for connecting PHP projects to Apicil services.

View on GitHub
Deep Wiki
Context7

Getting Started

First, a reality check on stack fit. mpp/apicil-client-bundle is a Symfony bundle, not a Laravel package. It ships a *Bundle class, a DependencyInjection/Configuration.php tree, and service definitions wired through Symfony's container. Laravel has no equivalent of bundle registration, so nothing auto-discovers, no config tree is merged, and none of the service wiring happens for you. You are effectively consuming the underlying PHP classes directly, and re-doing the glue yourself.

Update since the last review: the package is on v3.3.2, not 1.0.0. The previous assessment's "single release, unpublished hobby project" framing is stale — there is a real release history behind it, and v3.3.2 is a patch-level bugfix to a write operation ("client to create an arbitrage", PR #51). That tells you two useful things up front: the library covers mutating calls (fund arbitrage / allocation switches on a contract), not just reads, and the maintainers are shipping targeted fixes to them. It also tells you the wire behaviour of write endpoints has changed inside a patch bump, which is worth pinning for.

That reframing should still drive your decision:

  • If the repo contains a plain client library underneath the bundle (look for src/Client, src/Api, or a composer.json require on a sibling *-client package), depend on that, not the bundle.
  • If everything lives inside the bundle, you can still composer require it and instantiate classes manually — you just lose the config/DI niceties and inherit a Symfony-flavored dependency graph.

Minimal steps (Laravel side):

composer require mpp/apicil-client-bundle:3.3.2

Pin exactly. Do not use ^3.3 while you are still learning the client — v3.3.2 itself demonstrates that a patch release can change how a write request is constructed.

Then immediately read, in this order:

  1. README.md — the only place the intended entry point is likely documented.
  2. src/DependencyInjection/Configuration.php — ground truth for every config key the package expects (base URL, credentials, timeouts). Mirror these names into config/services.php so your mental model matches upstream docs.
  3. src/DependencyInjection/*Extension.php and Resources/config/services.* — shows which class is the public entry service and what it depends on. That dependency list is your construction recipe.
  4. The class touched by PR #51 — this is the arbitrage-creation client. Read the diff, not just the resulting file: the fix tells you what the payload actually needs to look like, which the README may not.
  5. src/ root — identify whether the client takes a PSR-18 ClientInterface, a Symfony HttpClientInterface, or builds its own transport. This determines how much you can fake in tests.
  6. tests/ — your best usage documentation.

First use case: don't build a feature. Build a throwaway php artisan tinker session or an artisan command that authenticates and performs one read call (fetch a single contract/subscriber/reference record). Confirm you get a real response before designing anything. Getting the auth handshake and base URL correct is 80% of the integration risk here.

Second use case, before you ship anything: exercise an arbitrage creation against the sandbox, using a throwaway contract, and diff the outbound payload against the v3.3.2 code path. Write calls are where this package has had recent defects, and they are the calls you cannot safely retry.

Sketch of the Laravel glue:

// config/services.php
'apicil' => [
    'base_uri' => env('APICIL_BASE_URI'),
    'client_id' => env('APICIL_CLIENT_ID'),
    'client_secret' => env('APICIL_CLIENT_SECRET'),
    'timeout' => env('APICIL_TIMEOUT', 10),
],
// app/Providers/ApicilServiceProvider.php
$this->app->singleton(ApicilClient::class, function ($app) {
    $c = $app['config']['services.apicil'];
    // construct exactly as the bundle's services.yaml does
    return new ApicilClient(/* ... */);
});

Note: class names and method signatures still can't be confirmed from release metadata alone — treat the above as the shape of the wiring and confirm names against services.yaml and the PR #51 diff before writing it.

Implementation Patterns

Wrap it, always. Never let the vendor client leak into controllers, jobs, or domain services. Define a narrow interface representing the business operations you actually need — now explicitly including the mutating ones — and adapt:

interface ApicilGateway
{
    public function findContract(string $reference): ?ContractData;
    public function createArbitrage(ArbitrageRequest $request): ArbitrageResult;
}

final class BundleApicilAdapter implements ApicilGateway { /* delegates */ }

The adapter is what makes a future patch, fork, or swap a one-file change. Given that v3.3.2 changed write-request construction, that insurance is now demonstrably worth having.

Bind as a singleton, transport included. HTTP clients should be long-lived so connection reuse works. Register the PSR-18/Symfony client as a singleton and inject it rather than constructing one per call. If the bundle's client creates its own transport internally and you can't inject one, that's a real limitation — note it early, because it blocks Http::fake()-style testing.

Push all calls behind queued jobs — but split reads from writes. Reads can be retried freely and batched. Arbitrage creation cannot: treat it as a non-idempotent side effect.

  • Reads: $tries, backoff(), aggressive caching.
  • Writes: run once, record intent before dispatching (a local arbitrage_requests row with your own correlation key), and reconcile on failure rather than blind-retrying. If the API supports a client-supplied idempotency key, use it; if not, assume a timeout means "unknown state" and verify with a read before re-submitting.

Cache reference data aggressively. Country codes, product/plan lists, contract typologies, eligible support/fund lists for arbitrage — anything read-mostly. Cache::remember() with a long TTL is the single highest-leverage pattern for this kind of API. It cuts latency, dodges rate limits, and reduces blast radius when the API is down.

Log at the transport layer, not the call site. If the client accepts a middleware/handler stack (Symfony HttpClient supports on_progress and middleware; Guzzle-style clients accept a handler stack), attach request/response logging there once. Essential for arbitrage, where the payload is the thing that breaks.

Test with fakes, not the network. If the client accepts an injected HTTP client, use Laravel's Http::fake() (works when the transport is Guzzle/PSR-18) or the package's own mock. If it doesn't accept injection, isolate the adapter behind your interface and fake that. Add a contract test — a single opt-in test, tagged and excluded from CI default, that hits the sandbox and creates one arbitrage — so you find out about the next PR #51-shaped regression before production does.

Normalize errors at the boundary. Translate to your own exception hierarchy (ApicilUnavailable, ApicilRejected, ApicilNotFound) so calling code branches on semantics rather than scattered HTTP codes. For arbitrage specifically, distinguish rejected (safe to fix and resubmit) from unknown (must reconcile first).

Gotchas and Tips

The bundle/container mismatch will bite you repeatedly. No bundle auto-registration means no config validation, no default values, no tagged services, no compiler passes. Any behavior documented as "configure mpp_apicil_client:" must be translated by hand into config/services.php. If the docs only speak in Symfony terms, allocate real time for translation rather than assuming a quick port.

Maturity: better than it looked, still not community-vetted. Correct the previous read — this is a maintained project with a multi-year release line at 3.3.x, published under the mon-petit-placement org, i.e. by the consuming platform itself rather than an anonymous third party. That's the good news: upstream has an incentive to fix things. The caution that remains:

  • Pin the exact version ("mpp/apicil-client-bundle": "3.3.2", no caret). Patch releases have changed write-request construction; a composer update should not be able to alter an arbitrage payload without you noticing.
  • Read the diff, not just the changelog line. "fix: client to create an arbitrage" is one sentence for what may be a payload-shape change. If you integrated before v3.3.2, assume your arbitrage path is affected until you've checked.
  • Keep the fork option open. Low adoption still means you may be the only consumer hitting your particular call combination. Vendor the repo or keep a fork branch if the API surface you use is significant.
  • Check the license before forking; it isn't stated in the metadata available.

Credentials and token lifecycle. Expect OAuth-style client credentials with expiring tokens. If the bundle handles refresh internally, verify it under concurrency (two queue workers refreshing simultaneously is a classic thundering-herd bug). If it doesn't, wrap refresh in Cache::lock().

Sandbox vs production URLs. Two different base URIs, certainly. Enforce the difference through environment variables only — never a hardcoded fallback base URI, which is how staging calls end up switching allocations on real contracts.

Timeouts and retries are not optional. Set a short connect timeout and a sensible total timeout explicitly; library defaults are frequently "infinite," turning one slow endpoint into a wedged queue worker. This matters more for arbitrage than for reads — a hung write leaves you in exactly the unknown-state limbo described above.

GDPR and PII. Insurance and wealth-management data: names, contract numbers, possibly IBANs, and now allocation/arbitrage instructions that constitute financial decisions. Do not let debug-level HTTP logging dump full request bodies into your log stack. Scrub at the middleware level, and set short retention on any payload capture you enable for incident debugging.

Extension points to prefer if you must stay on the bundle: decorate the client service (same interface, extra behavior) rather than editing vendor code; inject a custom HTTP client if the constructor allows; hook Symfony's event dispatcher if the bundle dispatches events. In Laravel, that means binding your wrapper over the singleton — same idea, done by hand.

Debugging checklist when a call fails: confirm the base URI has no trailing-slash double-up; dump the actual outbound request path and headers once (PII scrubbed); check whether the client swallows non-2xx responses into null rather than throwing; verify content-type negotiation (Accept/Content-Type mismatches produce confusing 400s); confirm the token isn't expired-but-cached; and for arbitrage, verify the payload against the v3.3.2 code path before assuming the remote API is at fault — recent history says the client is the likelier culprit.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums