.. index:: single: Entities ######## Entities ######## The bundle ships a Doctrine entity for every ESPN resource it imports, plus a set of embeddable value objects. All entities live in ``HansPeterOrding\EspnApiSymfonyBundle\Entity`` and all tables are prefixed ``easb_espn_``. .. contents:: Table of contents :depth: 2 :local: *************** Common conventions *************** Every persisted entity follows the same conventions, so once you have read one you can predict the rest. ``espnId`` ========== ESPN's own identifier is stored in a string column ``espnId`` — separate from the entity's database primary key. Look-ups during import match on ``espnId`` (and, where a resource is season-scoped, on the related season), which is what makes re-imports idempotent. Primary keys ============ Primary keys are bigint identity columns generated by the database. They are internal to your application and have no meaning in the ESPN API. Nullable by default =================== Almost every column is nullable, mirroring the client's DTO philosophy: ESPN is inconsistent about which fields are present, so the bundle never forces a value to exist. Timestamps ========== Every entity uses ``SyncTimestampsTrait``, which adds ``createdAt`` and ``lastSyncedAt`` columns maintained through Doctrine lifecycle callbacks. These let you tell when a row was first imported and when it was last refreshed. .. code-block:: php $team->getCreatedAt(); // first import $team->getLastSyncedAt(); // most recent refresh Enums ===== Fixed vocabularies (injury status, competitor home/away, competition status type, …) are modeled as native PHP enums and stored with Doctrine's ``enumType``. This gives you type-safe values rather than free-form strings. Reference strings ================= The ``{name}Reference`` convention from the client carries through to the entities. A reference column stores the ESPN ``$ref`` URL of a related resource. Importers use these strings to look up and connect the related entity; you can also read them directly if you only need the link. *************** Free-form text *************** Fields that can hold long, unbounded text — note headlines and bodies, injury comments — are mapped as ``TEXT`` columns rather than ``VARCHAR(255)``. This matters because ESPN occasionally "misuses" some feeds (notably injuries) to carry long status updates and news. *************** Embeddable value objects *************** Some structures are genuinely part of their parent resource rather than links to a separate resource. These are mapped as Doctrine embeddables and stored inline with a column prefix: ============================ ========================================= Embeddable Used by ============================ ========================================= ``EspnAddress`` Venue location, athlete birth place ``EspnSource`` Competition data-source descriptors ``EspnImage`` Logos and headshots ``EspnAthleteStatus`` Athlete active/inactive status ``EspnInjuryType`` Injury classification ``EspnCompetitionType`` Competition kind ``EspnCompetitionFormat`` Competition format / periods ``EspnCompetitionStatusType`` Game status descriptor ``EspnOfficialPosition`` Referee role ============================ ========================================= Embeddables are mapped inline because they have no independent identity in the ESPN API — they only ever exist as part of their parent. *************** Key relationships *************** The associations below are worth calling out because they encode deliberate design decisions. Season scoping ============== Most resources are season-scoped: an athlete in 2025 is a different row from the same person in 2026, because ESPN models them as distinct season-bound resources. A handful of resources are **season-independent** and unique by ``espnId`` alone: * ``EspnTeam`` * ``EspnSeasonGroup`` * ``EspnInjury`` * ``EspnPosition`` (league-level, with a self-referencing parent) Team ↔ Franchise (one-to-one) ============================= A franchise maps to exactly one team and vice versa, modeled as a bidirectional ``OneToOne``. ``EspnTeam`` owns the foreign key (``inversedBy``); setting the team on a franchise also sets the franchise on the team via a recursion-guarded setter. Venue ↔ Teams / Franchises (one-to-many) ======================================== A venue can host several teams and franchises (e.g. a shared stadium), so ``EspnVenue`` holds ``OneToMany`` collections back to both, with the foreign key on the owning ``EspnTeam`` / ``EspnFranchise`` side. Injury ↔ Athletes (many-to-many) ================================ Because the same real-world injury is referenced by the season-scoped athlete records of multiple seasons, an injury is connected to athletes through a ``ManyToMany`` join table (``easb_espn_injury_to_espn_athlete``). The injury itself is season-independent; deleting a healed injury removes it for every season at once. CompetitionStatus ↔ Competition (one-to-one) ============================================ The live status of a game (clock, period, status type) is its own entity with a ``OneToOne`` back to the competition, rather than an embeddable. This is deliberate: status changes constantly during a game, so isolating it lets you refresh just the status without touching the competition row. See :doc:`import_chain`. *************** Reserved words *************** Two columns would collide with SQL reserved words and are therefore mapped to renamed columns: * ``EspnCompetitor`` and ``EspnOfficial`` store their ordering in a ``displayOrder`` property mapped to a ``display_order`` column. If you query these tables directly, use the ``display_order`` column name. *************** Inspecting the model *************** Because the entities are standard Doctrine entities, all the usual tooling works: .. code-block:: terminal # show the mapping for one entity $ php bin/console doctrine:mapping:info # validate every mapping and its database sync state $ php bin/console doctrine:schema:validate And you query them through their repositories like any other Doctrine entity: .. code-block:: php use HansPeterOrding\EspnApiSymfonyBundle\Entity\EspnTeam; $team = $entityManager ->getRepository(EspnTeam::class) ->findOneBy(['espnId' => '12']); foreach ($team->getAthletes() as $athlete) { echo $athlete->getFullName() . PHP_EOL; } ********** Read next ********** * :doc:`import_control` — choosing which entities to import * :doc:`import_chain` — the full message catalog