carlosgude/integration-engine
IntegrationEngine standardizes external API integrations in Symfony: each endpoint is a predictable Action with a Request and Response DTO + Mapper. Includes scaffolding commands to generate integrations and actions, keeping HTTP/mapping logic consistent and maintainable.
Installation:
composer require carlosgude/integration-engine
Ensure your project uses PHP 8.2+ and Symfony 7.0+.
Generate a Basic Integration:
php bin/console make:integration MyApi GetEmployee
Follow the interactive prompts to define the base URL, HTTP method, and path.
Configure the Integration:
Add the integration to 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'
First Use Case: Call the integration facade in a service:
use App\Infrastructure\Integrations\MyApi\MyApiIntegration;
$employee = $myApiIntegration->getEmployee(123);
src/Infrastructure/Integrations/{Name}/ for the predictable layout.config/packages/integration_engine.yaml for integration settings.Facade Pattern:
Always expose integrations via facades (e.g., MyApiIntegration) to abstract the IntegrationRegistry. Never call the registry directly from controllers or services.
Action-Driven Design: Each endpoint is a stateless action with:
GetEmployeeAction) handling HTTP logic.GetEmployeeResponse) for typed data.GetEmployeeMapper) to transform raw API responses.Anti-Corruption Layer (ACL):
Use a Gateway (e.g., MyApiGateway) to map integration DTOs to domain objects. This isolates your domain from API changes.
final class MyApiGateway {
public function __construct(private MyApiIntegration $integration) {}
public function find(int $id): Employee {
$dto = $this->integration->getEmployee($id)->employee;
return new Employee(id: $dto->id, name: $dto->name);
}
}
Batch Requests:
Use sendMany() for parallel requests (e.g., fetching multiple employees):
$employees = $this->integration->getManyEmployees([1, 2, 3]);
Handle failures via BatchResultCollection:
if ($results->hasFailures()) {
throw array_values($results->errors())[0];
}
Employee) go in Dto/ at the integration root.{placeholder} in YAML paths for dynamic segments (e.g., /employees/{id}).PathResolvableContextInterface or declare required params in YAML (e.g., /employees?status={status}).authorization: { type: bearer, token: '%env(TOKEN)%' }).authorization: { type: dynamic, action: FetchToken }).ClientAdapterInterface to test request/response logic.IntegrationEngineTestCase to test full API interactions.Direct Registry Calls:
IntegrationRegistry directly into controllers/services.MyApiIntegration) to encapsulate logic.Cache Scope:
cache.app is process-local under PHP-FPM. For shared token caching (e.g., OAuth2), configure a distributed cache (e.g., Redis):
cache_service: cache.redis
GraphQL Limitations:
GraphQLClientAdapter does not support batch requests (sendMany). For concurrency, implement a custom BatchClientInterface.Path Resolution Errors:
{id}) throw PathResolutionException. Validate context data before sending requests.Dynamic Auth Retries:
Log Requests/Responses:
Enable debug mode in config/packages/integration_engine.yaml:
debug: true
Logs appear in Symfony’s profiler or var/log/dev.log.
Inspect Raw Responses:
Override the mapper’s map() method to dump raw data:
public function map(array $data): GetEmployeeResponse {
\Log::debug('Raw API response:', $data);
return new GetEmployeeResponse(...);
}
Validate YAML:
Use php bin/console debug:integration-engine:validate to check for syntax errors in {Name}.yaml.
Custom Clients:
Implement ClientAdapterInterface for unsupported protocols (e.g., SOAP):
final class SoapClientAdapter implements ClientAdapterInterface {
public static function getClientType(): string { return 'soap'; }
public function send(AbstractAction $action, ...): array { ... }
}
Tag the service in services.yaml:
tags:
- { name: integration_engine.client_adapter }
Custom Contexts:
Extend DefaultActionContext for complex path/query logic:
final class FilterContext implements PathResolvableContextInterface {
public function resolvePath(string $path): ?string {
return $path . '?' . http_build_query($this->filters);
}
}
Response Validation: Use PHP 8.2’s read-only properties and constructed properties to enforce DTO immutability:
final readonly class GetEmployeeResponse {
public function __construct(
public int $id,
public string $name,
) {}
}
Error Handling:
Catch IntegrationEngineException for API failures:
try {
$response = $integration->getEmployee(123);
} catch (IntegrationEngineException $e) {
// Handle rate limits, timeouts, etc.
}
https://api.example.com), not just the path.client_service in config replaces the default rest/graphql clients.authorization:
type: dynamic
ttl: 900 # 15 minutes
sendMany() for bulk operations (e.g., fetching 100+ records).base_url config and YAML paths for maintainability.How can I help you explore Laravel packages today?