EspnApiSymfonyBundle
EspnApiSymfonyBundle turns the raw ESPN NFL data exposed by
espn-api-client into a fully persisted, relational dataset inside your
Symfony application. It provides Doctrine entities for every ESPN resource, an
asynchronous import pipeline built on Symfony Messenger, and a flexible
control layer that lets you decide exactly which parts of the ESPN data tree
get imported.
Where the client answers “give me this one ESPN resource as an object”, this bundle answers “keep my database in sync with ESPN”.
What it does
Entities — a Doctrine entity for every ESPN resource (seasons, teams, athletes, events, competitions, injuries, contracts, and more), with the relationships between them modeled explicitly.
Importers & converters — services that fetch a resource through the client, map it onto an entity, and connect it to its related entities.
Messages & handlers — one Symfony Messenger message per resource. The import is a cascade: importing a season dispatches messages for its weeks and teams, importing a team dispatches messages for its athletes, and so on.
Import control — a single nested-array structure decides which branches of the cascade run, so you can import everything, or just one team, or just live scores during a game.
Retry-aware error handling — transient ESPN failures are retried by Messenger; permanent failures (a missing parent, an unresolvable URL) are marked unrecoverable and dropped.
How it fits together
ESPN API
│
▼
espn-api-client ← fetches a resource, returns a DTO
│
▼
Converter ← maps DTO scalars onto an entity
│
▼
Importer ← connects the entity to related entities
│
▼
Message handler ← persists the entity, dispatches child messages
│
▼
Doctrine / your database
Every resource follows this same path. Once you understand it for one entity, you understand it for all of them.
A first import
The most common entry point dispatches two messages — one for the season tree
and one for the (season-independent) positions tree. Each import message takes
the ESPN $ref URL of the resource to import:
use HansPeterOrding\EspnApiClient\ApiClient\EspnApiClientInterface;
use HansPeterOrding\EspnApiSymfonyBundle\Message\EspnSync\ImportEspnSeasonMessage;
use HansPeterOrding\EspnApiSymfonyBundle\Message\EspnSync\ImportEspnPositionsMessage;
use Symfony\Component\Messenger\MessageBusInterface;
public function fullImport(MessageBusInterface $bus): void
{
$seasonRef = EspnApiClientInterface::BASE_URI_SPORTS_CORE . 'seasons/2025';
$bus->dispatch(new ImportEspnSeasonMessage($seasonRef));
$bus->dispatch(new ImportEspnPositionsMessage());
}
That is the whole trigger. Everything else happens asynchronously as the cascade unfolds across your Messenger workers. You are never required to start here, though — see Import control for how to dispatch any single message in the chain to import just a slice of the data.