fix: normalize non-numeric body dimension values of existing drafts
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user