Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 3.12.1 under development

- New #260: Add `$attributes` parameter to `Html::li()` method (@gauravkumar2525)
- Chg #234: Remove tag attributes sorting (@FrankiFixx)
- Enh #261: Enhance `RadioList::addRadioWrapClass()` method for cleaner class addition (@vjik)

## 3.12.0 December 13, 2025
Expand Down
49 changes: 0 additions & 49 deletions src/Html.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,45 +99,6 @@
*/
final class Html
{
/**
* The preferred order of attributes in a tag. This mainly affects the order of the attributes that are
* rendered by {@see renderTagAttributes()}.
*/
private const ATTRIBUTE_ORDER = [
'type',
'id',
'class',
'name',
'value',

'href',
'loading',
'src',
'srcset',
'form',
'action',
'method',

'selected',
'checked',
'readonly',
'disabled',
'multiple',

'size',
'maxlength',
'minlength',
'width',
'height',
'rows',
'cols',

'alt',
'title',
'rel',
'media',
];

/**
* List of tag attributes that should be specially handled when their values are of array type.
* In particular, if the value of the `data` attribute is `['name' => 'xyz', 'age' => 13]`, two attributes will be
Expand Down Expand Up @@ -1674,16 +1635,6 @@
*/
public static function renderTagAttributes(array $attributes): string
{
if (count($attributes) > 1) {
$sorted = [];
foreach (self::ATTRIBUTE_ORDER as $name) {
if (isset($attributes[$name])) {
$sorted[$name] = $attributes[$name];
}
}
$attributes = array_merge($sorted, $attributes);
}

$html = '';
/**
* @var string $name
Expand All @@ -1698,11 +1649,11 @@
/** @psalm-var array<array-key, scalar[]|string|Stringable|null> $value */
foreach ($value as $n => $v) {
if (!isset($v)) {
continue;

Check warning on line 1652 in src/Html.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.5-ubuntu-latest

Escaped Mutant for Mutator "Continue_": @@ @@ /** @psalm-var array<array-key, scalar[]|string|Stringable|null> $value */ foreach ($value as $n => $v) { if (!isset($v)) { - continue; + break; } $fullName = "$name-$n"; if (in_array($fullName, self::ATTRIBUTES_WITH_CONCATENATED_VALUES, true)) {
}
$fullName = "$name-$n";
if (in_array($fullName, self::ATTRIBUTES_WITH_CONCATENATED_VALUES, true)) {
$html .= self::renderAttribute(

Check warning on line 1656 in src/Html.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.5-ubuntu-latest

Escaped Mutant for Mutator "Assignment": @@ @@ } $fullName = "$name-$n"; if (in_array($fullName, self::ATTRIBUTES_WITH_CONCATENATED_VALUES, true)) { - $html .= self::renderAttribute( + $html = self::renderAttribute( $fullName, self::encodeAttribute( is_array($v) ? implode(' ', $v) : $v,
$fullName,
self::encodeAttribute(
is_array($v) ? implode(' ', $v) : $v,
Expand Down
80 changes: 40 additions & 40 deletions tests/HtmlTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public function testMeta(): void
{
$this->assertSame('<meta>', Html::meta()->render());
$this->assertSame(
'<meta id="main" name="keywords" content="yii">',
'<meta name="keywords" content="yii" id="main">',
Html::meta(['name' => 'keywords', 'content' => 'yii', 'id' => 'main'])->render(),
);
}
Expand All @@ -154,15 +154,15 @@ public function testLink(): void
public function testCssFile(): void
{
$this->assertSame(
'<link href="http://example.com" rel="stylesheet">',
'<link rel="stylesheet" href="http://example.com">',
Html::cssFile('http://example.com')->render(),
);
$this->assertSame(
'<link href rel="stylesheet">',
'<link rel="stylesheet" href>',
Html::cssFile('')->render(),
);
$this->assertSame(
'<link id="main" href="http://example.com" rel="stylesheet">',
'<link rel="stylesheet" href="http://example.com" id="main">',
Html::cssFile('http://example.com', ['id' => 'main'])->render(),
);
}
Expand All @@ -178,7 +178,7 @@ public function testJavaScriptFile(): void
Html::javaScriptFile('')->render(),
);
$this->assertSame(
'<script id="main" src="http://example.com"></script>',
'<script src="http://example.com" id="main"></script>',
Html::javaScriptFile('http://example.com', ['id' => 'main'])->render(),
);
}
Expand Down Expand Up @@ -206,7 +206,7 @@ public function testMailto(): void
Html::mailto('contact me', 'info@example.com')->render(),
);
$this->assertSame(
'<a id="contact" href="mailto:info@example.com">contact me</a>',
'<a href="mailto:info@example.com" id="contact">contact me</a>',
Html::mailto('contact me', 'info@example.com', ['id' => 'contact'])->render(),
);
}
Expand Down Expand Up @@ -385,15 +385,15 @@ public function testTextInput(): void
public function testColorInput(): void
{
$this->assertSame('<input type="color">', Html::color()->render());
$this->assertSame('<input type="color" name>', Html::color('')->render());
$this->assertSame('<input type="color" value>', Html::color(null, '')->render());
$this->assertSame('<input type="color" name="test">', Html::color('test')->render());
$this->assertSame('<input name type="color">', Html::color('')->render());
$this->assertSame('<input value type="color">', Html::color(null, '')->render());
$this->assertSame('<input name="test" type="color">', Html::color('test')->render());
$this->assertSame(
'<input type="color" name="test" value="#ff0000">',
'<input name="test" value="#ff0000" type="color">',
Html::color('test', '#ff0000')->render(),
);
$this->assertSame(
'<input type="color" name="test" value="#ff0000" required>',
'<input name="test" value="#ff0000" required type="color">',
Html::color('test', '#ff0000', ['required' => true])->render(),
);
}
Expand All @@ -409,7 +409,7 @@ public function testHiddenInput(): void
Html::hiddenInput('test', '43')->render(),
);
$this->assertSame(
'<input type="hidden" id="ABC" name="test" value="43">',
'<input type="hidden" name="test" value="43" id="ABC">',
Html::hiddenInput('test', '43', ['id' => 'ABC'])->render(),
);
}
Expand All @@ -433,63 +433,63 @@ public function testPasswordInput(): void
public function testFile(): void
{
$this->assertSame('<input type="file">', Html::file()->render());
$this->assertSame('<input type="file" name>', Html::file('')->render());
$this->assertSame('<input type="file" value>', Html::file(null, '')->render());
$this->assertSame('<input type="file" name="test">', Html::file('test')->render());
$this->assertSame('<input name type="file">', Html::file('')->render());
$this->assertSame('<input value type="file">', Html::file(null, '')->render());
$this->assertSame('<input name="test" type="file">', Html::file('test')->render());
$this->assertSame(
'<input type="file" name="test" value="43">',
'<input name="test" value="43" type="file">',
Html::file('test', '43')->render(),
);
$this->assertSame(
'<input type="file" class="photo" name="test" value="43">',
'<input name="test" value="43" class="photo" type="file">',
Html::file('test', '43', ['class' => 'photo'])->render(),
);
}

public function testRadio(): void
{
$this->assertSame('<input type="radio">', Html::radio()->render());
$this->assertSame('<input type="radio" name>', Html::radio('')->render());
$this->assertSame('<input type="radio" value>', Html::radio(null, '')->render());
$this->assertSame('<input type="radio" name="test">', Html::radio('test')->render());
$this->assertSame('<input name type="radio">', Html::radio('')->render());
$this->assertSame('<input value type="radio">', Html::radio(null, '')->render());
$this->assertSame('<input name="test" type="radio">', Html::radio('test')->render());
$this->assertSame(
'<input type="radio" name="test" value="43">',
'<input name="test" value="43" type="radio">',
Html::radio('test', '43')->render(),
);
$this->assertSame(
'<input type="radio" name="test" value="43" readonly>',
'<input name="test" value="43" readonly type="radio">',
Html::radio('test', '43', ['readonly' => true])->render(),
);
}

public function testCheckbox(): void
{
$this->assertSame('<input type="checkbox">', Html::checkbox()->render());
$this->assertSame('<input type="checkbox" name>', Html::checkbox('')->render());
$this->assertSame('<input type="checkbox" value>', Html::checkbox(null, '')->render());
$this->assertSame('<input type="checkbox" name="test">', Html::checkbox('test')->render());
$this->assertSame('<input name type="checkbox">', Html::checkbox('')->render());
$this->assertSame('<input value type="checkbox">', Html::checkbox(null, '')->render());
$this->assertSame('<input name="test" type="checkbox">', Html::checkbox('test')->render());
$this->assertSame(
'<input type="checkbox" name="test" value="43">',
'<input name="test" value="43" type="checkbox">',
Html::checkbox('test', '43')->render(),
);
$this->assertSame(
'<input type="checkbox" name="test" value="43" readonly>',
'<input name="test" value="43" readonly type="checkbox">',
Html::checkbox('test', '43', ['readonly' => true])->render(),
);
}

public function testRange(): void
{
$this->assertSame('<input type="range">', Html::range()->render());
$this->assertSame('<input type="range" name>', Html::range('')->render());
$this->assertSame('<input type="range" value>', Html::range(null, '')->render());
$this->assertSame('<input type="range" name="test">', Html::range('test')->render());
$this->assertSame('<input name type="range">', Html::range('')->render());
$this->assertSame('<input value type="range">', Html::range(null, '')->render());
$this->assertSame('<input name="test" type="range">', Html::range('test')->render());
$this->assertSame(
'<input type="range" name="test" value="43">',
'<input name="test" value="43" type="range">',
Html::range('test', '43')->render(),
);
$this->assertSame(
'<input type="range" name="test" value="43" readonly>',
'<input name="test" value="43" readonly type="range">',
Html::range('test', '43', ['readonly' => true])->render(),
);
}
Expand Down Expand Up @@ -532,9 +532,9 @@ public function testCheckboxList(): void
$this->assertSame(
'<input type="hidden" name="test" value="0">' . "\n"
. '<div id="main">' . "\n"
. '<label><input type="checkbox" name="test[]" value="1"> One</label>' . "\n"
. '<label><input type="checkbox" name="test[]" value="2" checked> Two</label>' . "\n"
. '<label><input type="checkbox" name="test[]" value="5" checked> Five</label>' . "\n"
. '<label><input name="test[]" value="1" type="checkbox"> One</label>' . "\n"
. '<label><input name="test[]" value="2" checked type="checkbox"> Two</label>' . "\n"
. '<label><input name="test[]" value="5" checked type="checkbox"> Five</label>' . "\n"
. '</div>',
Html::checkboxList('test')
->items([1 => 'One', 2 => 'Two', 5 => 'Five'])
Expand All @@ -550,9 +550,9 @@ public function testRadioList(): void
$this->assertSame(
'<input type="hidden" name="test" value="0">' . "\n"
. '<div id="main">' . "\n"
. '<label><input type="radio" name="test" value="1"> One</label>' . "\n"
. '<label><input type="radio" name="test" value="2" checked> Two</label>' . "\n"
. '<label><input type="radio" name="test" value="5"> Five</label>' . "\n"
. '<label><input name="test" value="1" type="radio"> One</label>' . "\n"
. '<label><input name="test" value="2" checked type="radio"> Two</label>' . "\n"
. '<label><input name="test" value="5" type="radio"> Five</label>' . "\n"
. '</div>',
Html::radioList('test')
->items([1 => 'One', 2 => 'Two', 5 => 'Five'])
Expand Down Expand Up @@ -706,7 +706,7 @@ public function testLi(): void
$this->assertSame('<li><span>Hello</span></li>', Html::li(Html::span('Hello'))->render());

$this->assertSame(
'<li id="item-1" class="item">Content</li>',
'<li class="item" id="item-1">Content</li>',
Html::li('Content', ['class' => 'item', 'id' => 'item-1'])->render(),
);
$this->assertSame('<li class="empty"></li>', Html::li(attributes: ['class' => 'empty'])->render());
Expand Down Expand Up @@ -857,7 +857,7 @@ public static function dataRenderTagAttributes(): array
[' class="first second"', ['class' => ['first', 'second']]],
['', ['class' => []]],
[' style="width: 100px; height: 200px;"', ['style' => ['width' => '100px', 'height' => '200px']]],
[' name="position" value="42"', ['value' => 42, 'name' => 'position']],
[' value="42" name="position"', ['value' => 42, 'name' => 'position']],
[
' id="x" class="a b" data-a="1" data-b="2" style="width: 100px;" any=\'[1,2]\'',
[
Expand Down
30 changes: 15 additions & 15 deletions tests/Tag/Base/BooleanInputTagTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ final class BooleanInputTagTest extends TestCase
{
public function testChecked(): void
{
$this->assertSame('<input type="test" checked>', (string) TestBooleanInputTag::tag()->checked());
$this->assertSame('<input checked type="test">', (string) TestBooleanInputTag::tag()->checked());
$this->assertSame('<input type="test">', (string) TestBooleanInputTag::tag()->checked(false));
$this->assertSame('<input type="test">', (string) TestBooleanInputTag::tag()
->checked(true)
Expand Down Expand Up @@ -64,7 +64,7 @@ public function testLabel(string $expected, ?string $label, array $attributes):
public function testLabelNoWrap(): void
{
$this->assertSame(
'<input type="test" id="ID"> <label for="ID">Voronezh</label>',
'<input id="ID" type="test"> <label for="ID">Voronezh</label>',
(string) TestBooleanInputTag::tag()
->id('ID')
->label('Voronezh', wrap: false),
Expand All @@ -74,7 +74,7 @@ public function testLabelNoWrap(): void
public function testLabelWithId(): void
{
$this->assertSame(
'<label><input type="test" id="Test"> One</label>',
'<label><input id="Test" type="test"> One</label>',
TestBooleanInputTag::tag()
->id('Test')
->label('One')
Expand All @@ -85,7 +85,7 @@ public function testLabelWithId(): void
public function testSideLabel(): void
{
$this->assertMatchesRegularExpression(
'~<input type="test" id="i(\d*?)"> <label for="i\1">One</label>~',
'~<input id="i(\d*?)" type="test"> <label for="i\1">One</label>~',
TestBooleanInputTag::tag()
->sideLabel('One')
->render(),
Expand All @@ -95,7 +95,7 @@ public function testSideLabel(): void
public function testSideLabelEmpty(): void
{
$this->assertMatchesRegularExpression(
'~<input type="test" id="i(\d*?)"> <label for="i\1"></label>~',
'~<input id="i(\d*?)" type="test"> <label for="i\1"></label>~',
TestBooleanInputTag::tag()
->sideLabel('')
->render(),
Expand All @@ -115,7 +115,7 @@ public function testSideLabelNull(): void
public function testSideLabelWithId(): void
{
$this->assertSame(
'<input type="test" id="Test"> <label for="Test">One</label>',
'<input id="Test" type="test"> <label for="Test">One</label>',
TestBooleanInputTag::tag()
->id('Test')
->sideLabel('One')
Expand All @@ -126,7 +126,7 @@ public function testSideLabelWithId(): void
public function testSideLabelWithAttributes(): void
{
$this->assertMatchesRegularExpression(
'~<input type="test" id="i(\d*?)"> <label class="red" for="i\1">One</label>~',
'~<input id="i(\d*?)" type="test"> <label class="red" for="i\1">One</label>~',
TestBooleanInputTag::tag()
->sideLabel('One', ['class' => 'red'])
->render(),
Expand All @@ -136,7 +136,7 @@ public function testSideLabelWithAttributes(): void
public function testSideLabelId(): void
{
$this->assertSame(
'<input type="test" id="count"> <label for="count">One</label>',
'<input id="count" type="test"> <label for="count">One</label>',
TestBooleanInputTag::tag()
->sideLabel('One')
->id('count')
Expand All @@ -160,15 +160,15 @@ public static function dataUncheckValue(): array
return [
['<input type="test">', null, null],
['<input type="test">', null, 7],
['<input type="test" name="color">', 'color', null],
['<input type="test" name="color[]">', 'color[]', null],
['<input name="color" type="test">', 'color', null],
['<input name="color[]" type="test">', 'color[]', null],
[
'<input type="hidden" name="color" value="7"><input type="test" name="color">',
'<input type="hidden" name="color" value="7"><input name="color" type="test">',
'color',
7,
],
[
'<input type="hidden" name="color" value="7"><input type="test" name="color[]">',
'<input type="hidden" name="color" value="7"><input name="color[]" type="test">',
'color[]',
7,
],
Expand All @@ -191,7 +191,7 @@ public function testUncheckValueDisabled(): void
{
$this->assertSame(
'<input type="hidden" name="color" value="7" disabled>'
. '<input type="test" name="color" disabled>',
. '<input name="color" disabled type="test">',
TestBooleanInputTag::tag()
->name('color')
->uncheckValue(7)
Expand All @@ -204,7 +204,7 @@ public function testUncheckValueForm(): void
{
$this->assertSame(
'<input type="hidden" name="color" value="7" form="post">'
. '<input type="test" name="color" form="post">',
. '<input name="color" form="post" type="test">',
TestBooleanInputTag::tag()
->name('color')
->uncheckValue(7)
Expand All @@ -217,7 +217,7 @@ public function testUncheckValueWithLabel(): void
{
$this->assertSame(
'<input type="hidden" name="color" value="7">'
. '<label><input type="test" name="color"> Seven</label>',
. '<label><input name="color" type="test"> Seven</label>',
TestBooleanInputTag::tag()
->name('color')
->uncheckValue(7)
Expand Down
Loading
Loading