Symfony Bundle · PHP 8.2+ · Packagist

Stop writing the same HTTP client over and over again

An integration engine for Symfony that centralises your external APIs under clear contracts.

undefined composer require carlosgude/integration-engine
View on GitHub →

HttpClient sends requests. IntegrationEngine structures integrations.

Use HttpClient for one or two simple calls. Use IntegrationEngine when the API is part of your architecture and you want clear contracts, typed responses, and an Anti-Corruption Layer between the provider and your domain.

Every integration ends up as an isolated case

Different formats, inconsistent authentication, duplicated cache logic. The codebase fragments and every new API means starting from scratch.

Duplicated auth

Tokens and cache reimplemented in every integration.

No shared contract

Every HTTP client has its own structure.

Hard to test

HTTP coupled to the domain. Impossible to isolate.

No consistency

Every developer solves the problem their own way.

Zero visibility

No traceability, no unified logs, no shared context.

A single flow for all your integrations

A single entry point. Every step has a clear responsibility.

Registry
IntegrationEngine
Action
Auth
HTTP
Mapper
Response DTO

Dynamic auth with cache

OAuth, sessions, API keys. The engine resolves and caches them automatically.

Path context

/orders/{id} is resolved at call time. Explicit failure if a parameter is missing.

Headers in three layers

YAML → auth → call layer. Each layer overrides the previous. No magic.

Typed responses

Every action defines its own Response DTO with a guaranteed contract.

Fully extensible

Client, cache and config source replaceable with one line in YAML.

Scaffolding included

make:integration generates Mapper, Response DTO and YAML in seconds.

From zero to a typed integration in seconds.

The command asks the questions. You only write the logic.

$ php bin/console make:integration Github GetUser
config/packages/integration_engine.yaml
src/Infrastructure/Integrations/Github/GithubIntegration.php
src/Infrastructure/Integrations/Github/Github.yaml
src/Infrastructure/Integrations/Github/GetUser/Request/GetUserAction.php
src/Infrastructure/Integrations/Github/GetUser/Response/GetUserMapper.php
src/Infrastructure/Integrations/Github/GetUser/Response/GetUserResponse.php

One line. Always the same.

No magic strings — everything through contracts.

// Action path: GET /orders
$this->engine->send(
    GetOrdersAction::getName()
);
// → GET /orders
// Action path: GET /orders/{id}
$response = $this->engine->send(
    actionName: GetOrderAction::getName(),
    context: DefaultActionContext::create(['id' => $id]),
);
// → GET /orders/42

\assert($response instanceof GetOrderResponse);
// GetOrderResponse { id: 42, reference: 'ORD-001', items: [...] }
// Action path: POST /orders
$response = $this->engine->send(
    actionName: CreateOrderAction::getName(),
    body: CreateOrderBody::create(['reference' => 'ORD-001']),
    headers: new CorrelationHeaders($correlationId),
);
// → POST /orders { "reference": "ORD-001" }

\assert($response instanceof CreateOrderResponse);
// CreateOrderResponse { id: 99, reference: 'ORD-001', status: 'pending' }
// Action endpoint: POST /graphql
$response = $this->engine->send(
    actionName: GetOrderAction::getName(),
    body: GetOrderBody::create(['id' => $id]),
);
// → POST /graphql { "query": "...", "variables": { "id": 42 } }

\assert($response instanceof GetOrderResponse);
// GetOrderResponse { id: 42, reference: 'ORD-001', items: [...] }

For the full pattern (facade → service → domain) → README →

The bundle proposes. It does not impose.

Three levels that emerge naturally. Use whichever you need.

ClassResponsibilityScope
CreateChargeAction Only declares the method, path and response DTO. No HTTP logic. Concrete action
GithubAction Auth, base path and common GitHub headers. Reused by all GitHub actions. Integration
AbstractAction Base contract provided by the engine. Extensible without touching the core. Bundle

The make:integration command creates the config, classes and YAML in a single step.

Get started in one command

No boilerplate. No arbitrary decisions. Just your business logic.