Skip to content

Commit 0a1904b

Browse files
committed
fix(state): cast empty/null boolean parameters to false
ValueCaster::toBool returned empty and "null" string values unchanged, so they reached the database as raw strings each driver coerced differently (SQLite matched nothing, MySQL/PostgreSQL coerced to false). Cast them to false in PHP for deterministic ExactFilter behavior across drivers; genuinely invalid values are still returned untouched so constraint validation keeps rejecting them.
1 parent e7f1966 commit 0a1904b

3 files changed

Lines changed: 52 additions & 5 deletions

File tree

src/State/Parameter/ValueCaster.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public static function toBool(mixed $v): mixed
3030

3131
return match (strtolower($v)) {
3232
'1', 'true' => true,
33-
'0', 'false' => false,
33+
'0', 'false', '', 'null' => false,
3434
default => $v,
3535
};
3636
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\State\Tests\Parameter;
15+
16+
use ApiPlatform\State\Parameter\ValueCaster;
17+
use PHPUnit\Framework\Attributes\DataProvider;
18+
use PHPUnit\Framework\TestCase;
19+
20+
class ValueCasterTest extends TestCase
21+
{
22+
#[DataProvider('boolProvider')]
23+
public function testToBool(mixed $value, mixed $expected): void
24+
{
25+
$this->assertSame($expected, ValueCaster::toBool($value));
26+
}
27+
28+
public static function boolProvider(): \Generator
29+
{
30+
yield 'true string' => ['true', true];
31+
yield 'numeric 1' => ['1', true];
32+
yield 'false string' => ['false', false];
33+
yield 'numeric 0' => ['0', false];
34+
// Empty and "null" cast to false deterministically across drivers, instead of
35+
// reaching the database as a raw string that each driver coerces differently.
36+
yield 'empty string' => ['', false];
37+
yield 'null string' => ['null', false];
38+
// A genuinely invalid value is returned untouched so constraint validation rejects it.
39+
yield 'invalid string' => ['string', 'string'];
40+
yield 'non-string passthrough' => [true, true];
41+
}
42+
}

tests/Functional/Parameters/BooleanFilterTest.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,10 @@ public static function booleanFilterScenariosProvider(): \Generator
7777
}
7878

7979
/**
80-
* With the canonical ExactFilter, a null/empty value is matched literally and yields no
81-
* result — unlike the deprecated BooleanFilter which skipped filtering (returning all rows,
82-
* covered by the legacy regression suite).
80+
* With the canonical ExactFilter, an empty or unparseable boolean value casts to false
81+
* (deterministically across drivers), so it matches the inactive rows — unlike the
82+
* deprecated BooleanFilter which skipped filtering (returning all rows, covered by the
83+
* legacy regression suite).
8384
*/
8485
#[DataProvider('booleanFilterNullAndEmptyScenariosProvider')]
8586
public function testBooleanFilterWithNullAndEmptyValues(string $url): void
@@ -90,7 +91,11 @@ public function testBooleanFilterWithNullAndEmptyValues(string $url): void
9091
$responseData = $response->toArray();
9192
$filteredItems = $responseData['hydra:member'];
9293

93-
$this->assertCount(0, $filteredItems, \sprintf('Expected no item for URL %s', $url));
94+
$this->assertCount(1, $filteredItems, \sprintf('Expected one inactive item for URL %s', $url));
95+
96+
foreach ($filteredItems as $item) {
97+
$this->assertFalse($item['active'], 'Expected an empty/null boolean value to cast to false');
98+
}
9499
}
95100

96101
public static function booleanFilterNullAndEmptyScenariosProvider(): \Generator

0 commit comments

Comments
 (0)