fix: normalize non-numeric body dimension values of existing drafts

This commit is contained in:
Björn Fromme
2026-05-28 15:35:34 +02:00
parent 02b1cbfd6c
commit ee8c6a6545
5 changed files with 148 additions and 0 deletions
+7
View File
@@ -378,6 +378,13 @@ class BookingDto
$this->$key = $value; $this->$key = $value;
} }
foreach ($this->participants as $participant) {
if ($participant instanceof ParticipantDto) {
// Temporary cleanup for older serialized participants restored from session.
$participant->normalizeBodyDimensions();
}
}
// Old sessions (before c373a989) still carry a full Travel object; // Old sessions (before c373a989) still carry a full Travel object;
// new sessions carry only the int ID. Handle both formats. // new sessions carry only the int ID. Handle both formats.
// TODO: Remove Travel instance branch once all pre-deploy sessions have expired // TODO: Remove Travel instance branch once all pre-deploy sessions have expired
+55
View File
@@ -360,6 +360,61 @@ class ParticipantDto
return $ageThreshold > $age; return $ageThreshold > $age;
} }
/**
* Normalizes body-dimension fields to canonical string values.
*
* Keeps plain integers and digit-only strings, converting them to a
* normalized string representation. Anything else is treated as legacy
* garbage and reset to null so form hydration can continue safely.
*/
public function normalizeBodyDimensions(): void
{
$this->height = $this->normalizeBodyDimensionValue($this->height);
$this->weight = $this->normalizeBodyDimensionValue($this->weight);
$this->shoeSize = $this->normalizeBodyDimensionValue($this->shoeSize);
}
/**
* Restores the DTO from session/serialization data.
*
* @param array<string, mixed> $data
*/
public function __unserialize(array $data): void
{
foreach ($data as $key => $value) {
if (false === property_exists($this, $key)) {
continue;
}
$this->$key = $value;
}
if (false === isset($this->address)) {
$this->address = new Address();
}
// TODO: Remove after all legacy drafts/session snapshots with body-dimension strings have expired.
$this->normalizeBodyDimensions();
}
private function normalizeBodyDimensionValue(mixed $value): ?string
{
if (true === is_int($value)) {
return (string) $value;
}
if (false === is_string($value)) {
return null;
}
$value = trim($value);
if ('' === $value || false === ctype_digit($value)) {
return null;
}
return (string) (int) $value;
}
/** /**
* Validates that the applicant (index 0) has a complete address. * Validates that the applicant (index 0) has a complete address.
* *
+2
View File
@@ -46,6 +46,8 @@ class BookingEditDraftMerger
// Body dimensions // Body dimensions
if (true === isset($data['bodyDimensions']) && true === is_array($data['bodyDimensions'])) { if (true === isset($data['bodyDimensions']) && true === is_array($data['bodyDimensions'])) {
$this->applyBodyDimensions($participant, $data['bodyDimensions']); $this->applyBodyDimensions($participant, $data['bodyDimensions']);
// TODO: Drop this once all legacy drafts no longer contain non-numeric body-dimension values.
$participant->normalizeBodyDimensions();
} }
// Room assignment // Room assignment
+24
View File
@@ -6,6 +6,7 @@ namespace App\Tests\Form\Model;
use App\BusProNet\Model\Travel; use App\BusProNet\Model\Travel;
use App\Form\Model\BookingDto; use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantDto;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
/** /**
@@ -63,4 +64,27 @@ class BookingDtoTest extends TestCase
$this->assertSame($travel, $dto->travel); $this->assertSame($travel, $dto->travel);
$this->assertSame(42, $dto->travel->id); $this->assertSame(42, $dto->travel->id);
} }
public function testUnserializeNormalizesLegacyBodyDimensionsOnParticipants(): void
{
$travel = new Travel();
$travel->id = 42;
$travel->dateFrom = new \DateTimeImmutable('2025-06-01');
$travel->dateTo = new \DateTimeImmutable('2025-06-08');
$participant = new ParticipantDto();
$participant->height = '149-157';
$participant->weight = '75';
$participant->shoeSize = '043';
$dto = new BookingDto($travel, 1);
$dto->participants = [$participant];
$restored = unserialize(serialize($dto));
$this->assertInstanceOf(BookingDto::class, $restored);
$this->assertNull($restored->participants[0]->height);
$this->assertSame('75', $restored->participants[0]->weight);
$this->assertSame('43', $restored->participants[0]->shoeSize);
}
} }
@@ -422,6 +422,66 @@ class BookingEditDraftManagerMutabilityTest extends TestCase
$this->assertSame('Updated', $mutableParticipant->firstName); $this->assertSame('Updated', $mutableParticipant->firstName);
} }
public function testDraftNormalizesLegacyBodyDimensionsToNull(): void
{
$participant = new ParticipantDto();
$participant->height = null;
$participant->weight = null;
$participant->shoeSize = null;
$travel = $this->createTravel();
$dto = $this->createBookingDto($travel, [$participant]);
$draft = $this->createDraft([
'participants' => [
0 => [
'bodyDimensions' => [
'height' => '149-157',
'weight' => '195+',
'shoeSize' => 'abc',
],
],
],
]);
$applied = $this->service->applyDraftToDto($draft, $dto, $travel);
$this->assertTrue($applied);
$this->assertNull($participant->height);
$this->assertNull($participant->weight);
$this->assertNull($participant->shoeSize);
}
public function testDraftNormalizesNumericBodyDimensionsToCanonicalStrings(): void
{
$participant = new ParticipantDto();
$participant->height = null;
$participant->weight = null;
$participant->shoeSize = null;
$travel = $this->createTravel();
$dto = $this->createBookingDto($travel, [$participant]);
$draft = $this->createDraft([
'participants' => [
0 => [
'bodyDimensions' => [
'height' => '180',
'weight' => '75',
'shoeSize' => '043',
],
],
],
]);
$applied = $this->service->applyDraftToDto($draft, $dto, $travel);
$this->assertTrue($applied);
$this->assertSame('180', $participant->height);
$this->assertSame('75', $participant->weight);
$this->assertSame('43', $participant->shoeSize);
}
// --- Helpers --- // --- Helpers ---
private function createService(int $id, string $label = 'Test', bool $isTransportation = false): Service private function createService(int $id, string $label = 'Test', bool $isTransportation = false): Service