carlosgude / integration-engine
Connect external APIs to Symfony through configurable adapters, without writing repetitive clients.
Package info
github.com/CarlosGude/integrationEngine
Type:symfony-bundle
pkg:composer/carlosgude/integration-engine
Requires
- php: >=8.2
- psr/clock: ^1.0
- psr/event-dispatcher: ^1.0
- psr/log: ^2.0|^3.0
- symfony/console: ^6.4|^7.0|^8.0
- symfony/dependency-injection: ^6.4|^7.0|^8.0
- symfony/http-client: ^6.4|^7.0|^8.0
- symfony/http-foundation: ^6.4|^7.0|^8.0
- symfony/yaml: ^6.4|^7.0|^8.0
Requires (Dev)
- deptrac/deptrac: ^4.7
- friendsofphp/php-cs-fixer: ^3.0
- infection/infection: ^0.33.2
- phpstan/phpstan: ^2.2
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-symfony: ^2.0
- phpunit/phpunit: ^11.0
- symfony/framework-bundle: ^6.4|^7.0|^8.0
- symfony/http-kernel: ^6.4|^7.0|^8.0
- symfony/remote-event: ^6.4|^7.0|^8.0
- symfony/webhook: ^6.4|^7.0|^8.0
Suggests
- symfony/messenger: Dispatch remote events through the webhook endpoint.
- symfony/remote-event: Typed inbound remote events.
- symfony/webhook: Inbound webhook parsing and Symfony endpoint integration.
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v9.0.0
- v8.0.4
- v8.0.3
- v8.0.2
- v8.0.1
- v8.0.0
- v7.0.2
- v7.0.1
- v7.0.0
- v6.0.0
- v5.4.0
- v5.3.1
- v5.3.0
- v5.2.0
- v5.1.0
- v5.0.0
- v4.1.0
- v4.0.0
- v3.0.0
- v2.3.1
- v2.3.0
- v2.2.1
- v2.2.0
- v2.1.0
- v2.0.0
- v1.27.1
- v1.27.0
- v1.26.0
- v1.25.5
- v1.25.4
- v1.25.3
- v1.25.2
- v1.25.1
- v1.25.0
- v1.24.0
- v1.23.0
- v1.22.0
- v1.20.0
- v1.19.0
- v1.18.0
- v1.17.0
- v1.16.1
- v1.16.0
- v1.15.1
- v1.14.0
- v1.13.0
- v1.12.0
- v1.11.0
- v1.10.0
- v1.9.0
- v1.8.0
- v1.7.0
- v1.6.0
- v1.5.0
- v1.3.0
- v1.2.0
- v1.1.0
- v1.0.1
- v1.0.0
- dev-docs/align-v8.0.4-status
This package is auto-updated.
Last update: 2026-09-30 04:14:48 UTC
README
Latest tagged release: v9.0.0 Requirements: PHP 8.2+ · Symfony 6.4 / 7.x / 8.x Website: integrationengine.dev Documentation: docs/DOCUMENTATION.md Roadmap: docs/ROADMAP.md
IntegrationEngine is a Symfony bundle for keeping outbound API integrations predictable: actions describe requests, mappers turn external responses into typed DTOs, and the engine owns the repetitive transport/authentication pipeline.
This README documents the current code in the repository. CHANGELOG.md is the source of truth for release history and unreleased changes.
What the bundle standardizes
An integration is built from a small set of contracts:
- an
AbstractActionper operation; - optional
ActionBodyInterface/ActionContextInterfacevalues for runtime input; - one
AbstractMapperfor each action that returns data; - a typed
ResponseInterfaceDTO; - an integration facade in the consuming application, backed by
IntegrationRegistry.
The bundle then handles configuration lookup, path resolution, static or dynamic auth, connection overrides, middleware, HTTP transport, batching, response mapping and lifecycle events.
It intentionally does not own your domain model. Keep the external DTOs behind a Gateway / Anti-Corruption Layer when they cross into application or domain code.
Install
composer require carlosgude/integration-engine
With Symfony Flex, the bundle is registered automatically.
First integration
Generate the integration shell and first action:
php bin/console make:integration MyApi GetEmployee
The command creates the integration facade, action, mapper/response when applicable, and the integration YAML. On the first integration it can also create config/packages/integration_engine.yaml.
Bundle configuration is per integration:
# config/packages/integration_engine.yaml integration_engine: integrations: my_api: base_url: 'https://api.example.com' config_path: '%kernel.project_dir%/src/Infrastructure/Integrations/MyApi/MyApi.yaml'
Action configuration lives in the integration YAML:
GetEmployee: action: App\Infrastructure\Integrations\MyApi\GetEmployee\Request\GetEmployeeAction method: GET path: /employees/{id}
A small facade keeps IntegrationRegistry out of controllers and application services:
use IntegrationEngine\Core\Contract\Action\DefaultActionContext; use IntegrationEngine\Core\Registry\IntegrationName; use IntegrationEngine\Core\Registry\IntegrationRegistry; final class MyApiIntegration implements IntegrationName { public const NAME = 'my_api'; public function __construct(private IntegrationRegistry $registry) {} public function getEmployee(int $id): GetEmployeeResponse { $response = $this->registry->get(self::NAME)->send( actionName: GetEmployeeAction::getName(), context: DefaultActionContext::create(['id' => $id]), ); if (!$response instanceof GetEmployeeResponse) { throw new \LogicException('Unexpected response type.'); } return $response; } }
See Getting started for the request, context, auth, mapping and batch contracts.
Current capabilities
| Area | Current contract | Reference |
|---|---|---|
| REST | Built-in Symfony HttpClient adapter; JSON by default | HTTP clients |
| Form bodies | FormEncodedBodyInterface, or client: form_encoded for an all-form API |
Form bodies |
| GraphQL | GraphQLBodyInterface; body is sent as query + variables |
HTTP clients |
| Batch | sendMany() isolates failures; built-in REST, GraphQL and form adapters support concurrent dispatch when request middleware does not force the sequential fallback |
Batch requests |
| Auth | bearer/basic/API key; dynamic token action with cache + one fresh-token retry after a cached-token 401 | Authorization |
| Multi-connection | ConnectionResolverInterface can override base URL/auth and namespace token caches per call |
HTTP clients |
| Middleware | action-level client middleware plus full-request middleware for signing | Architecture |
| Resilience | transport timeout, max duration and Symfony retry configuration | Resilience |
| Outgoing security | hostname allowlist and optional private-network blocking for built-in transports | Security |
| Observability | scalar-only request, response, failure, token and webhook events | Lifecycle events |
| Webhooks | YAML definition, HMAC verification, typed mapper, Symfony Webhook/RemoteEvent integration | Webhooks |
| Static analysis | optional PHPStan rules for mapper reciprocity, response modifiers and facade return types | PHPStan |
| Debugging | debug:integration [name] and Symfony profiler integration in debug mode |
Debugging |
Configuration split
There are two YAML scopes and they solve different problems:
config/packages/integration_engine.yaml
└── integration wiring: base_url, client, transport, headers, cache,
middlewares, request_middlewares, connection_resolver
src/.../MyApi.yaml
├── action definitions: class, method, path, body, authorization,
│ cache_ttl, timeout
└── optional webhooks definition
Keeping those scopes separate matters: bundle configuration decides how an integration is wired; integration YAML describes what operations that integration exposes.
Architecture in one sentence
Core defines contracts and orchestration; Infrastructure implements adapters; Bundle wires Symfony; compatibility shims stay outside Core; optional PHPStan rules live in their own extension layer. The dependency direction is enforced by Deptrac.
The detailed dependency rules and runtime pipeline are in ARCHITECTURE.md.
Quality
Repository gates cover style, PHPStan, PHPUnit, Deptrac and Infection. Measured results belong in docs/advanced/QUALITY.md, not in this README, so release copy does not become a museum of stale numbers.
Demo
integrationEngine-demo is the consuming Symfony application used to demonstrate and contract-test the bundle.
Further reading
Start from docs/DOCUMENTATION.md. It is the only documentation index; archived plans, spikes and release-preparation notes live under docs/archived/ and are explicitly non-current.
When not to use it
IntegrationEngine is a poor fit when an API needs a highly stateful SDK, protocol-specific streaming/session behavior, or a vendor library that already owns transport, retries and object mapping coherently. In those cases, wrapping the SDK behind your own application boundary is usually simpler than forcing it into this action/mapper model.