chore: add inline documentation
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user