From 1eb271186ea270614377fe1d6f5b34c39c7404f9 Mon Sep 17 00:00:00 2001 From: Mister-42 Date: Thu, 11 Jun 2026 21:33:02 +0200 Subject: [PATCH 1/5] Add support for conditional CSS classes in Html::addCssClass() (#279) --- CHANGELOG.md | 2 +- src/Html.php | 7 +++++++ tests/HtmlTest.php | 20 ++++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91f9e7e0..9c4716f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 4.2.1 under development -- no changes in this release. +- New #279: Add support for conditional CSS classes in `Html::addCssClass()` (@Mister-42) ## 4.2.0 June 05, 2026 diff --git a/src/Html.php b/src/Html.php index 481b9cd1..4e5c17a9 100644 --- a/src/Html.php +++ b/src/Html.php @@ -1731,6 +1731,13 @@ public static function addCssClass(array &$attributes, BackedEnum|array|string|n $value = is_string($value->value) ? $value->value : null; } + if (is_bool($value)) { + if ($value && is_string($key)) { + $filteredClass[] = $key; + } + continue; + } + if ($value !== null) { $filteredClass[$key] = $value; } diff --git a/tests/HtmlTest.php b/tests/HtmlTest.php index eaf0ffd2..b9a4c72b 100644 --- a/tests/HtmlTest.php +++ b/tests/HtmlTest.php @@ -1049,6 +1049,12 @@ public static function dataAddCssClass(): array 16 => [['id' => 'w2', 'class' => 't1'], ['id' => 'w2'], 't1'], 17 => [['id' => 'w2', 'class' => ['t3', 2 => 't4']], ['id' => 'w2'], ['t3', null, 't4']], 18 => [['id' => 'w2', 'class' => ['t3', 2 => 't4']], ['id' => 'w2'], ['t3', null, 't4', null]], + 19 => [['class' => ['btn-active']], [], ['btn-active' => true]], + 20 => [[], [], ['btn-active' => false]], + 21 => [['class' => ['btn', 'btn-active']], [], ['btn', 'btn-active' => true]], + 22 => [['class' => ['btn']], [], ['btn', 'btn-active' => false]], + 23 => [['class' => ['btn', 'btn-active']], ['class' => 'btn'], ['btn-active' => true]], + 24 => [['class' => 'btn'], ['class' => 'btn'], ['btn-active' => false]], ]; } @@ -1091,6 +1097,20 @@ public function testMergeCssClass(): void $this->assertSame(['persistent' => 'test1', 'additional' => 'test2'], $attributes['class']); } + public function testAddCssClassConditional(): void + { + $attributes = ['class' => 'btn']; + Html::addCssClass($attributes, ['btn-active' => true, 'hidden' => false]); + $this->assertSame(['class' => ['btn', 'btn-active']], $attributes); + + Html::addCssClass($attributes, ['btn' => true]); + $this->assertSame(['class' => ['btn', 'btn-active']], $attributes); + + $attributes = ['class' => ['persistent' => 'widget']]; + Html::addCssClass($attributes, ['btn', 'btn-active' => true]); + $this->assertSame(['class' => ['persistent' => 'widget', 'btn', 'btn-active']], $attributes); + } + public function testAddCssClassArrayToString(): void { $attributes = ['class' => 'test']; From 01b15a04b6b7142563435cd2e2465a36f7395970 Mon Sep 17 00:00:00 2001 From: Mister-42 Date: Thu, 11 Jun 2026 21:44:20 +0200 Subject: [PATCH 2/5] Add Psalm suppression for DocblockTypeContradiction on is_bool check --- src/Html.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Html.php b/src/Html.php index 4e5c17a9..9bb9b6a9 100644 --- a/src/Html.php +++ b/src/Html.php @@ -1731,6 +1731,7 @@ public static function addCssClass(array &$attributes, BackedEnum|array|string|n $value = is_string($value->value) ? $value->value : null; } + /** @psalm-suppress DocblockTypeContradiction */ if (is_bool($value)) { if ($value && is_string($key)) { $filteredClass[] = $key; From 2cab015544fc6029afbfb22d18d7be20bbbb3f93 Mon Sep 17 00:00:00 2001 From: Mister-42 Date: Fri, 12 Jun 2026 11:22:17 +0200 Subject: [PATCH 3/5] Address vjik review feedback on PR #279 - Improve type annotation instead of psalm suppression - Move is_bool check before BackedEnum handling - Add phpdoc about conditional boolean behavior - Add conditional CSS test cases to dataAddCssClass data provider - Remove separate testAddCssClassConditional test --- src/Html.php | 13 ++++++------- tests/HtmlTest.php | 19 ++++--------------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/src/Html.php b/src/Html.php index 9bb9b6a9..fea64dc6 100644 --- a/src/Html.php +++ b/src/Html.php @@ -1707,9 +1707,9 @@ public static function renderTagAttributes(array $attributes): string * @param array $attributes The attributes to be modified. All string values in the array must be valid UTF-8 * strings. * @param BackedEnum|BackedEnum[]|null[]|string|string[]|null $class The CSS class(es) to be added. Null values will - * be ignored. + * be ignored. When passing an array, use a boolean value to conditionally include/exclude a class by its key. * - * @psalm-param BackedEnum|string|array|null $class + * @psalm-param BackedEnum|string|array|null $class */ public static function addCssClass(array &$attributes, BackedEnum|array|string|null $class): void { @@ -1727,11 +1727,6 @@ public static function addCssClass(array &$attributes, BackedEnum|array|string|n if (is_array($class)) { $filteredClass = []; foreach ($class as $key => $value) { - if ($value instanceof BackedEnum) { - $value = is_string($value->value) ? $value->value : null; - } - - /** @psalm-suppress DocblockTypeContradiction */ if (is_bool($value)) { if ($value && is_string($key)) { $filteredClass[] = $key; @@ -1739,6 +1734,10 @@ public static function addCssClass(array &$attributes, BackedEnum|array|string|n continue; } + if ($value instanceof BackedEnum) { + $value = is_string($value->value) ? $value->value : null; + } + if ($value !== null) { $filteredClass[$key] = $value; } diff --git a/tests/HtmlTest.php b/tests/HtmlTest.php index b9a4c72b..e1ac4274 100644 --- a/tests/HtmlTest.php +++ b/tests/HtmlTest.php @@ -1054,7 +1054,10 @@ public static function dataAddCssClass(): array 21 => [['class' => ['btn', 'btn-active']], [], ['btn', 'btn-active' => true]], 22 => [['class' => ['btn']], [], ['btn', 'btn-active' => false]], 23 => [['class' => ['btn', 'btn-active']], ['class' => 'btn'], ['btn-active' => true]], - 24 => [['class' => 'btn'], ['class' => 'btn'], ['btn-active' => false]], + 24 => [['class' => 'btn'], ['class' => 'btn'], ['btn-active' => false]], + 25 => [['class' => ['btn', 'btn-active']], ['class' => 'btn'], ['btn-active' => true, 'hidden' => false]], + 26 => [['class' => ['btn']], ['class' => 'btn'], ['btn' => true]], + 27 => [['class' => ['persistent' => 'widget', 'btn', 'btn-active']], ['class' => ['persistent' => 'widget']], ['btn', 'btn-active' => true]], ]; } @@ -1097,20 +1100,6 @@ public function testMergeCssClass(): void $this->assertSame(['persistent' => 'test1', 'additional' => 'test2'], $attributes['class']); } - public function testAddCssClassConditional(): void - { - $attributes = ['class' => 'btn']; - Html::addCssClass($attributes, ['btn-active' => true, 'hidden' => false]); - $this->assertSame(['class' => ['btn', 'btn-active']], $attributes); - - Html::addCssClass($attributes, ['btn' => true]); - $this->assertSame(['class' => ['btn', 'btn-active']], $attributes); - - $attributes = ['class' => ['persistent' => 'widget']]; - Html::addCssClass($attributes, ['btn', 'btn-active' => true]); - $this->assertSame(['class' => ['persistent' => 'widget', 'btn', 'btn-active']], $attributes); - } - public function testAddCssClassArrayToString(): void { $attributes = ['class' => 'test']; From 0376672714c1fffc02287fd2efa3d13831106094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B2=D0=B0=D0=BD?= <68641750+Mister-42@users.noreply.github.com> Date: Fri, 12 Jun 2026 12:24:37 +0200 Subject: [PATCH 4/5] Update src/Html.php Co-authored-by: Sergei Predvoditelev --- src/Html.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Html.php b/src/Html.php index fea64dc6..43ce093c 100644 --- a/src/Html.php +++ b/src/Html.php @@ -1706,7 +1706,7 @@ public static function renderTagAttributes(array $attributes): string * * @param array $attributes The attributes to be modified. All string values in the array must be valid UTF-8 * strings. - * @param BackedEnum|BackedEnum[]|null[]|string|string[]|null $class The CSS class(es) to be added. Null values will + * @param (BackedEnum|null|bool|string)[]|BackedEnum|string|null $class The CSS class(es) to be added. Null values will * be ignored. When passing an array, use a boolean value to conditionally include/exclude a class by its key. * * @psalm-param BackedEnum|string|array|null $class From 95c62301226a12eb1a6045aeeaa879a5219fded9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B2=D0=B0=D0=BD?= <68641750+Mister-42@users.noreply.github.com> Date: Fri, 12 Jun 2026 12:26:32 +0200 Subject: [PATCH 5/5] Fix inconsistent spacing Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tests/HtmlTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/HtmlTest.php b/tests/HtmlTest.php index e1ac4274..572cef2c 100644 --- a/tests/HtmlTest.php +++ b/tests/HtmlTest.php @@ -1054,10 +1054,10 @@ public static function dataAddCssClass(): array 21 => [['class' => ['btn', 'btn-active']], [], ['btn', 'btn-active' => true]], 22 => [['class' => ['btn']], [], ['btn', 'btn-active' => false]], 23 => [['class' => ['btn', 'btn-active']], ['class' => 'btn'], ['btn-active' => true]], - 24 => [['class' => 'btn'], ['class' => 'btn'], ['btn-active' => false]], - 25 => [['class' => ['btn', 'btn-active']], ['class' => 'btn'], ['btn-active' => true, 'hidden' => false]], - 26 => [['class' => ['btn']], ['class' => 'btn'], ['btn' => true]], - 27 => [['class' => ['persistent' => 'widget', 'btn', 'btn-active']], ['class' => ['persistent' => 'widget']], ['btn', 'btn-active' => true]], + 24 => [['class' => 'btn'], ['class' => 'btn'], ['btn-active' => false]], + 25 => [['class' => ['btn', 'btn-active']], ['class' => 'btn'], ['btn-active' => true, 'hidden' => false]], + 26 => [['class' => ['btn']], ['class' => 'btn'], ['btn' => true]], + 27 => [['class' => ['persistent' => 'widget', 'btn', 'btn-active']], ['class' => ['persistent' => 'widget']], ['btn', 'btn-active' => true]], ]; }