chore: add inline documentation

This commit is contained in:
Björn Fromme
2026-03-23 19:22:18 +01:00
parent be43d30cc7
commit 7e686cef6b
3 changed files with 29 additions and 0 deletions
+14
View File
@@ -13,6 +13,18 @@ use Doctrine\ORM\Mapping as ORM;
#[ORM\Index(name: 'idx_snapshot_product_id', columns: ['product_id'])]
#[ORM\Index(name: 'idx_snapshot_date_to', columns: ['date_to'])]
#[ORM\Index(name: 'idx_snapshot_extended_refreshed_at', columns: ['extended_refreshed_at'])]
/**
* Persisted copy of a Travel aggregate for travels whose source XML may no longer be available.
*
* Each row stores the full serialized Travel graph as a JSON payload alongside a set of
* denormalized metadata columns (code, label, dates, hotel) that allow filtering and
* mapping lookups without deserializing the payload.
*
* Three timestamps track the lifecycle of the snapshot:
* - capturedAt: when the snapshot was first written
* - updatedAt: when the payload or metadata was last modified
* - extendedRefreshedAt: when extended availability (VERFUEGBARKEIT2) was last applied
*/
class TravelSnapshot
{
#[ORM\Id]
@@ -50,9 +62,11 @@ class TravelSnapshot
#[ORM\Column(type: 'date_immutable', nullable: true)]
private ?\DateTimeImmutable $dateTo = null;
/** Full JSON-serialized Travel aggregate. */
#[ORM\Column(type: 'text')]
private string $payload;
/** SHA-256 of payload; compared before every write to skip no-op flushes. */
#[ORM\Column(type: 'string', length: 64)]
private string $payloadHash;
@@ -98,7 +98,9 @@ class TravelSnapshotRepository extends ServiceEntityRepository
int $limit,
): array {
return $this->createQueryBuilder('s')
// dateTo IS NULL: snapshots without a known end date are always considered active.
->where('s.dateTo IS NULL OR s.dateTo >= :dateToThreshold')
// extendedRefreshedAt IS NULL: never-refreshed snapshots rank as oldest.
->andWhere('s.extendedRefreshedAt IS NULL OR s.extendedRefreshedAt < :refreshBefore')
->setParameter('dateToThreshold', $dateToThreshold)
->setParameter('refreshBefore', $refreshBefore)
@@ -116,6 +118,8 @@ class TravelSnapshotRepository extends ServiceEntityRepository
{
return (int) $this->createQueryBuilder('s')
->delete()
// Snapshots with no known end date are excluded from purging as a safety net;
// they must be cleaned up manually if they become stale.
->where('s.dateTo IS NOT NULL')
->andWhere('s.dateTo < :beforeDate')
->setParameter('beforeDate', $beforeDate)
+11
View File
@@ -97,6 +97,7 @@ class TravelSnapshotService
*/
public function upsertFromTravel(Travel $travel): void
{
// Both fields form the composite unique key; nothing to persist without them.
if (null === $travel->id || null === $travel->hotelId) {
return;
}
@@ -191,6 +192,10 @@ class TravelSnapshotService
$extendedResponseByDateId = [];
foreach ($candidates as $snapshot) {
// Skip travels whose XML is still live; their snapshot is kept authoritative
// by the XML load path (getTravelDataFromXml → upsertFromTravel). An empty
// $xmlAvailableDateIds means the file map could not be loaded, so fall back
// to refreshing everything rather than skipping all candidates.
if ([] !== $xmlAvailableDateIds && in_array($snapshot->getDateId(), $xmlAvailableDateIds, true)) {
continue;
}
@@ -203,6 +208,8 @@ class TravelSnapshotService
continue;
}
// Cache the API response by dateId: multiple hotel snapshots share one travel
// date, so a single API call covers all of them within the same batch.
if (false === array_key_exists($dateId, $extendedResponseByDateId)) {
$response = null;
try {
@@ -215,6 +222,8 @@ class TravelSnapshotService
]);
}
// A Notification response signals a business-level "no data" answer
// from the API rather than an error; treat it the same as a null response.
if (null !== $response && false === $response instanceof Notification) {
$extendedResponseByDateId[$dateId] = $response;
} elseif (false === isset($extendedResponseByDateId[$dateId])) {
@@ -236,6 +245,8 @@ class TravelSnapshotService
$snapshot->setPayload($payload)->setPayloadHash($payloadHash);
$this->applyTravelMetadata($snapshot, $travel);
}
// Always stamp the refresh timestamp even when the payload was unchanged,
// so the candidate query does not re-select this snapshot on the next run.
$snapshot->setExtendedRefreshedAt(new \DateTimeImmutable())->touchUpdatedAt();
$this->entityManager->flush();