IntegrationEngine
Bundle open source para Symfony · Autor y mantenedor
Una estructura común para construir y mantener integraciones con APIs externas en Symfony.
- Requiere
- PHP 8.2+ · Symfony 6.4 / 7 / 8
- Licencia
- MIT
- Action + Context
- IntegrationEngine
- API externa
- Mapper
- Response tipada
Por qué lo construí
Cada vez que integraba una API externa en Symfony, volvía a resolver piezas parecidas: autenticación, peticiones HTTP, mapeo de respuestas y gestión de errores. Además, cada integración tendía a organizarse con sus propias convenciones. IntegrationEngine nació de esa experiencia.
Cómo está organizado
- Cada operación contra la API externa es una Action.
- Un Mapper transforma la respuesta externa en un objeto Response tipado.
- El bundle se encarga del flujo común: configuración, autenticación y transporte HTTP.
- Cada integración conserva los detalles específicos de su proveedor.
Una forma de trabajar repetible —contratos claros y una estructura coherente— basada en problemas que me he encontrado en producción. Los proveedores siguen siendo distintos; el bundle da a esas diferencias un lugar predecible.
Ejemplo real: Obtener una película de TMDB Tomado de la aplicación de demo
-
Declarar la operación
Método, ruta y mapper de la action get_movie.
get_movie: action: 'App\Integrations\Tmdb\GetMovie\GetMovieAction' method: GET path: /3/movie/{movie_id} mapper: 'App\Integrations\Tmdb\Mappers\GetMovieMapper' -
Llamarla desde la aplicación
Una fachada pequeña mantiene el engine fuera de controladores y servicios.
public function getMovie(int $movieId): GetMovieResponse { $engine = $this->registry->get('tmdb'); $context = DefaultActionContext::create(['movie_id' => $movieId]); $response = $engine->send('get_movie', $context); \assert($response instanceof GetMovieResponse); return $response; } -
TMDB responde con su propio modelo
El fixture de test que usa la demo para esta llamada: 25 campos, la mayoría innecesarios para la aplicación.
{ "adult": false, "backdrop_path": "/fhvyh6G8M55gkTIPCbeFjvyAh4c.jpg", "belongs_to_collection": null, "budget": 250000000, "genres": [ { "id": 28, "name": "Action" }, { "id": 12, "name": "Adventure" } ], "homepage": "https://www.marvel.com/movies/avengers-endgame", "id": 299536, "imdb_id": "tt4154796", "original_language": "en", "original_title": "Avengers: Endgame", "overview": "After the devastating events that decimated the planet and wiped out half of all life, the Avengers assemble once more in order to reverse Thanos' actions and restore balance to the universe.", "popularity": 408.72, "poster_path": "/or06FN4Hf2tfsWASP2Oa6aSy0l1.jpg", "production_companies": [ { "id": 420, "logo_path": "/hUzeosd33nzE5MCNsZxCGEKTW5l.png", "name": "Marvel Studios", "origin_country": "US" } ], "production_countries": [ { "iso_3166_1": "US", "name": "United States of America" } ], "release_date": "2019-04-26", "revenue": 2798200000, "runtime": 181, "spoken_languages": [ { "english_name": "English", "iso_639_1": "en", "name": "English" } ], "status": "Released", "tagline": "Part of the journey is the end.", "title": "Avengers: Endgame", "video": false, "vote_average": 8.3, "vote_count": 32000 } -
El Mapper se queda con lo que usa la aplicación
Los nombres de campo del proveedor se quedan aquí; el resto del código solo ve la respuesta tipada.
protected static function transform(AbstractAction $action, array $response, array $headers): ResponseInterface { /** @var array{id: int, title: string, overview: string, poster_path: string, vote_average: float, release_date: string} $response */ return new GetMovieResponse( id: $response['id'], title: $response['title'], overview: $response['overview'], posterPath: $response['poster_path'], voteAverage: $response['vote_average'], releaseDate: $response['release_date'], ); } -
Respuesta tipada
GetMovieResponse::toArray() para el fixture anterior.
{ "id": 299536, "title": "Avengers: Endgame", "overview": "After the devastating events that decimated the planet and wiped out half of all life, the Avengers assemble once more in order to reverse Thanos' actions and restore balance to the universe.", "poster_path": "/or06FN4Hf2tfsWASP2Oa6aSy0l1.jpg", "vote_average": 8.3, "release_date": "2019-04-26" }
Fuente: integrationEngine-demo/tree/main/src/Integrations/Tmdb (se abre en una pestaña nueva)