Use new %TagClass%() instead of %TagClass%::tag()#264
Use new %TagClass%() instead of %TagClass%::tag()#264razvbir wants to merge 31 commits intoyiisoft:masterfrom
Conversation
razvbir
commented
Mar 4, 2026
| Q | A |
|---|---|
| Is bugfix? | ❌ |
| New feature? | ✔️ |
| Breaks BC? | ✔️ |
| Fixed issues | #243 |
This reverts commit 8149d93.
This is drops support for php 8.1, 8.2, 8.3
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #264 +/- ##
=============================================
- Coverage 100.00% 99.41% -0.59%
- Complexity 829 830 +1
=============================================
Files 89 90 +1
Lines 2229 2233 +4
=============================================
- Hits 2229 2220 -9
- Misses 0 13 +13 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
CHANGELOG.md
Outdated
| @@ -1,5 +1,9 @@ | |||
| # Yii HTML Change Log | |||
|
|
|||
| ## ?.?.0 | |||
There was a problem hiding this comment.
Not sure about the version in which this should be merged. It will not work with any version of PHP below 8.4 after the merge.
There was a problem hiding this comment.
We can add () around new Tag() in some places. Then it will work with any currently supported versions.
There was a problem hiding this comment.
That didn't cross my mind, will do.
There was a problem hiding this comment.
Pull request overview
Updates the library to prefer direct instantiation (new ...) over static “constructors” like ::tag() / ::create() / CustomTag::name(), aligning with the PHP 8.4 deprecation attribute and the direction described in issue #243.
Changes:
- Made constructors public for
NormalTag,VoidTag,CustomTag, and key widgets; marked static constructors (tag(),create(),name()) as deprecated via#[Deprecated]. - Replaced internal usages and test suites to instantiate via
new ...instead of static constructors. - Updated documentation and CI matrices to focus on PHP 8.4+.
Reviewed changes
Copilot reviewed 109 out of 109 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Widget/RadioListTest.php | Switches to new RadioList(...); adds deprecation reflection test. |
| tests/Widget/CheckboxListTest.php | Switches to new CheckboxList(...); adds deprecation reflection test. |
| tests/Widget/ButtonGroupTest.php | Switches to new ButtonGroup(); adds deprecation reflection test. |
| tests/Tag/VideoTest.php | Replaces ::tag() usage with new ... for media tags. |
| tests/Tag/UlTest.php | Replaces list tag construction with new .... |
| tests/Tag/TrackTest.php | Replaces Track::tag() with new Track(). |
| tests/Tag/TrTest.php | Replaces Tr::tag() and cell tags with new .... |
| tests/Tag/TitleTest.php | Replaces Title::tag() with new Title(). |
| tests/Tag/TheadTest.php | Replaces Thead::tag() / Tr::tag() with new .... |
| tests/Tag/ThTest.php | Replaces Th::tag() with new Th(). |
| tests/Tag/TfootTest.php | Replaces Tfoot::tag() / Tr::tag() with new .... |
| tests/Tag/TextareaTest.php | Replaces Textarea::tag() with new Textarea(). |
| tests/Tag/TdTest.php | Replaces Td::tag() with new Td(). |
| tests/Tag/TbodyTest.php | Replaces Tbody::tag() / Tr::tag() with new .... |
| tests/Tag/TableTest.php | Replaces Table::tag() and related tag factories with new .... |
| tests/Tag/StyleTest.php | Replaces Style::tag() with new Style(). |
| tests/Tag/StrongTest.php | Replaces Strong::tag() with new Strong(). |
| tests/Tag/SpanTest.php | Replaces Span::tag() with new Span(). |
| tests/Tag/SourceTest.php | Replaces Source::tag() with new Source(). |
| tests/Tag/SmallTest.php | Replaces Small::tag() with new Small(). |
| tests/Tag/SelectTest.php | Replaces Select::tag() / Option::tag() / Optgroup::tag() with new .... |
| tests/Tag/SectionTest.php | Replaces Section::tag() and nested tags with new .... |
| tests/Tag/ScriptTest.php | Replaces Script::tag() and nested Noscript/Div with new .... |
| tests/Tag/PreTest.php | Replaces Pre::tag() with new Pre(). |
| tests/Tag/PictureTest.php | Replaces Picture::tag() / Img::tag() / Source::tag() with new .... |
| tests/Tag/PTest.php | Replaces P::tag() with new P(). |
| tests/Tag/OptionTest.php | Replaces Option::tag() with new Option(). |
| tests/Tag/OptgroupTest.php | Replaces Optgroup::tag() / Option::tag() with new .... |
| tests/Tag/OlTest.php | Replaces ordered list construction with new .... |
| tests/Tag/NoscriptTest.php | Replaces Noscript::tag() / Img::tag() with new .... |
| tests/Tag/NavTest.php | Replaces Nav::tag() and nested tags with new .... |
| tests/Tag/MetaTest.php | Replaces Meta::tag() with new Meta(). |
| tests/Tag/LinkTest.php | Replaces Link::tag() with new Link(). |
| tests/Tag/LiTest.php | Replaces Li::tag() with new Li(). |
| tests/Tag/LegendTest.php | Replaces Legend::tag() with new Legend(). |
| tests/Tag/LabelTest.php | Replaces Label::tag() with new Label(). |
| tests/Tag/InputTest.php | Replaces Input::tag() with new Input(). |
| tests/Tag/Input/RangeTest.php | Replaces Range::tag() with new Range(). |
| tests/Tag/Input/RadioTest.php | Replaces Radio::tag() with new Radio(). |
| tests/Tag/Input/FileTest.php | Replaces File::tag() with new File(). |
| tests/Tag/Input/ColorTest.php | Replaces Color::tag() with new Color(). |
| tests/Tag/Input/CheckboxTest.php | Replaces Checkbox::tag() with new Checkbox(). |
| tests/Tag/ImgTest.php | Replaces Img::tag() with new Img(). |
| tests/Tag/ITest.php | Replaces I::tag() with new I(). |
| tests/Tag/HtmlTest.php | Replaces Html::tag() (tag class) with new Html(). |
| tests/Tag/HrTest.php | Replaces Hr::tag() with new Hr(). |
| tests/Tag/HgroupTest.php | Replaces Hgroup::tag() and nested heading tags with new .... |
| tests/Tag/HeaderTest.php | Replaces Header::tag() and nested tags with new .... |
| tests/Tag/H6Test.php | Replaces H6::tag() with new H6(). |
| tests/Tag/H5Test.php | Replaces H5::tag() with new H5(). |
| tests/Tag/H4Test.php | Replaces H4::tag() with new H4(). |
| tests/Tag/H3Test.php | Replaces H3::tag() with new H3(). |
| tests/Tag/H2Test.php | Replaces H2::tag() with new H2(). |
| tests/Tag/H1Test.php | Replaces H1::tag() with new H1(). |
| tests/Tag/FormTest.php | Replaces Form::tag() with new Form(). |
| tests/Tag/FooterTest.php | Replaces Footer::tag() and nested tags with new .... |
| tests/Tag/FieldsetTest.php | Replaces Fieldset::tag() / Legend::tag() with new .... |
| tests/Tag/EmTest.php | Replaces Em::tag() with new Em(). |
| tests/Tag/DivTest.php | Replaces Div::tag() with new Div(). |
| tests/Tag/DatalistTest.php | Replaces Datalist::tag() / Option::tag() with new .... |
| tests/Tag/CustomTagTest.php | Replaces CustomTag::name() with new CustomTag(...); adds deprecation reflection test. |
| tests/Tag/ColgroupTest.php | Replaces Colgroup::tag() / Col::tag() with new .... |
| tests/Tag/ColTest.php | Replaces Col::tag() with new Col(). |
| tests/Tag/CodeTest.php | Replaces Code::tag() with new Code(). |
| tests/Tag/CaptionTest.php | Replaces Caption::tag() / CustomTag::name() with new .... |
| tests/Tag/ButtonTest.php | Replaces Button::tag() with new Button(). |
| tests/Tag/BrTest.php | Replaces Br::tag() with new Br(). |
| tests/Tag/BodyTest.php | Replaces Body::tag() with new Body(). |
| tests/Tag/Base/VoidTagTest.php | Uses new TestVoidTag(); adds deprecation reflection test for VoidTag::tag(). |
| tests/Tag/Base/TagTest.php | Updates base tag tests to use new TestTag() instead of TestTag::tag(). |
| tests/Tag/Base/TagSourcesTraitTest.php | Updates trait tests to use new ... for sources. |
| tests/Tag/Base/TagContentTraitTest.php | Updates trait tests to use new ... for content tags. |
| tests/Tag/Base/TableRowsContainerTagTest.php | Updates container tag tests to use new ... for rows/cells. |
| tests/Tag/Base/TableCellTagTest.php | Updates cell tag tests to use new TestTableCellTag(). |
| tests/Tag/Base/NormalTagTest.php | Uses new TestNormalTag(); adds deprecation reflection test for NormalTag::tag(). |
| tests/Tag/Base/MediaTagTest.php | Updates media tag tests to use new ... for sources/tracks. |
| tests/Tag/Base/ListTagTest.php | Updates list tag tests to use new ... for items. |
| tests/Tag/Base/InputTagTest.php | Updates input tag tests to use new TestInputTag(). |
| tests/Tag/Base/BooleanInputTagTest.php | Updates boolean input tag tests to use new .... |
| tests/Tag/BTest.php | Replaces B::tag() with new B(). |
| tests/Tag/AudioTest.php | Replaces Audio::tag() and nested tags with new .... |
| tests/Tag/AsideTest.php | Replaces Aside::tag() and nested tags with new .... |
| tests/Tag/ArticleTest.php | Replaces Article::tag() and nested tags with new .... |
| tests/Tag/AddressTest.php | Replaces Address::tag() and nested A with new .... |
| tests/Tag/ATest.php | Replaces A::tag() with new A(). |
| tests/HtmlTest.php | Updates Html::noscript() test to pass new Div() instead of Div::tag(). |
| src/Widget/RadioList/RadioList.php | Makes constructor public; deprecates RadioList::create(). |
| src/Widget/CheckboxList/CheckboxList.php | Makes constructor public; deprecates CheckboxList::create(). |
| src/Widget/ButtonGroup.php | Makes constructor public; deprecates ButtonGroup::create(). |
| src/Tag/Tr.php | Replaces internal Td::tag() / Th::tag() creation with new .... |
| src/Tag/Table.php | Replaces Caption::tag() with new Caption() in helper. |
| src/Tag/Select.php | Replaces Optgroup::tag() / Option::tag() with new .... |
| src/Tag/Script.php | Replaces Noscript::tag() with new Noscript(). |
| src/Tag/Optgroup.php | Replaces Option::tag() with new Option(). |
| src/Tag/Meta.php | Replaces self::tag() with new self() in static factories. |
| src/Tag/Link.php | Replaces self::tag() with new self() in static factory. |
| src/Tag/Input/Range.php | Replaces CustomTag::name(...) with new CustomTag(...). |
| src/Tag/Input.php | Replaces ::tag() usage in static factories with new .... |
| src/Tag/CustomTag.php | Makes constructor public; deprecates CustomTag::name(). |
| src/Tag/Button.php | Replaces self::tag() with new self() in factories. |
| src/Tag/Base/VoidTag.php | Makes constructor public; deprecates VoidTag::tag(). |
| src/Tag/Base/NormalTag.php | Makes constructor public; deprecates NormalTag::tag(). |
| src/Tag/Base/ListTag.php | Replaces Li::tag() with new Li() when building items. |
| src/Html.php | Updates helpers to instantiate tag objects with new ... (and widgets with constructors). |
| README.md | Updates examples to use new ... instead of static constructors. |
| CHANGELOG.md | Adds an entry for the change (currently with a placeholder version header). |
| .github/workflows/static.yml | Reduces PHP matrix to 8.4 only. |
| .github/workflows/composer-require-checker.yml | Reduces PHP matrix to 8.4–8.5. |
| .github/workflows/build.yml | Reduces PHP matrix to 8.4–8.5. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
CHANGELOG.md
Outdated
| @@ -1,5 +1,9 @@ | |||
| # Yii HTML Change Log | |||
|
|
|||
| ## ?.?.0 | |||
There was a problem hiding this comment.
The version header ## ?.?.0 looks like a placeholder and doesn't match the existing changelog format (e.g. x.y.z under development / dated releases). Please replace it with the intended next version (likely the next major due to BC break) and/or mark it as "under development" to avoid publishing an invalid version entry.
| ## ?.?.0 | |
| ## 4.0.0 under development |
.github/workflows/build.yml
Outdated
| ['ubuntu-latest', 'windows-latest'] | ||
| php: >- | ||
| ['8.1', '8.2', '8.3', '8.4', '8.5'] | ||
| ['8.4', '8.5'] |
There was a problem hiding this comment.
CI matrices were reduced to PHP 8.4+ here, but composer.json still allows PHP 8.1–8.5. Please align CI and declared platform support (either restore testing for supported versions, or bump the package PHP requirement so users on 8.1–8.3 don't install a potentially incompatible release).
| ['8.4', '8.5'] | |
| ['8.1', '8.2', '8.3', '8.4', '8.5'] |
CHANGELOG.md
Outdated
| @@ -1,5 +1,9 @@ | |||
| # Yii HTML Change Log | |||
|
|
|||
| ## ?.?.0 | |||
There was a problem hiding this comment.
We can add () around new Tag() in some places. Then it will work with any currently supported versions.
Co-authored-by: Alexander Makarov <sam@rmcreative.ru>
Co-authored-by: Alexander Makarov <sam@rmcreative.ru>
Co-authored-by: Alexander Makarov <sam@rmcreative.ru>
Co-authored-by: Alexander Makarov <sam@rmcreative.ru>
Co-authored-by: Alexander Makarov <sam@rmcreative.ru>
Co-authored-by: Alexander Makarov <sam@rmcreative.ru>
Co-authored-by: Alexander Makarov <sam@rmcreative.ru>
This reverts commit d58950a.
This reverts commit c9377b6.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 109 out of 109 changed files in this pull request and generated 9 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| #[Attribute] | ||
| final class Deprecated | ||
| { | ||
| public function __construct( | ||
| public readonly ?string $message, | ||
| public readonly ?string $since, | ||
| ) { | ||
| trigger_error($message ?? '', E_USER_DEPRECATED); | ||
| } | ||
| } | ||
|
|
||
| if (!class_exists('Deprecated')) { | ||
| class_alias(Deprecated::class, 'Deprecated'); | ||
| } |
There was a problem hiding this comment.
Deprecated is used via a global class alias, but this file is only executed once Yiisoft\Html\Attribute\Deprecated is autoloaded. Since other code/tests reference Deprecated directly (and PHPUnit runs tests in random order), this can lead to Class "Deprecated" not found on PHP < 8.4. Consider ensuring the polyfill is always loaded (e.g., Composer autoload.files) or stop relying on a global alias and reference the namespaced attribute explicitly.
| public function testClassAlias(): void | ||
| { | ||
| $this->assertTrue(class_exists('Deprecated')); | ||
|
|
||
| $attribute = $this->make('Test message', null); | ||
|
|
||
| $this->assertInstanceOf(Deprecated::class, $attribute); | ||
| } |
There was a problem hiding this comment.
testClassAlias() depends on other tests running first to autoload Yiisoft\\Html\\Attribute\\Deprecated and create the global alias, but PHPUnit is configured with executionOrder="random". Load/instantiate the namespaced attribute inside this test before asserting the alias (or otherwise ensure the alias is defined deterministically), so the test doesn't fail when run in isolation or earlier in the random order.
| { | ||
| $attributes = (new ReflectionFunction(RadioList::create(...)))->getAttributes(Deprecated::class); | ||
| $this->assertNotEmpty($attributes); | ||
| $this->assertSame('Use the constructor instead.', $attributes[0]->newInstance()->message); |
There was a problem hiding this comment.
This deprecation assertion uses Deprecated::class and ReflectionAttribute::newInstance(). On PHP < 8.4 the global Deprecated attribute class may not exist (and test order is random), causing failures; additionally newInstance() will execute the attribute constructor, which in the polyfill triggers an E_USER_DEPRECATED. Prefer checking by attribute name (string) and inspecting ReflectionAttribute::getArguments() instead of instantiating the attribute.
| public function testDeprecation(): void | ||
| { | ||
| $attributes = (new ReflectionFunction(ButtonGroup::create(...)))->getAttributes(Deprecated::class); | ||
| $this->assertNotEmpty($attributes); | ||
| $this->assertSame('Use the constructor instead.', $attributes[0]->newInstance()->message); | ||
| } |
There was a problem hiding this comment.
This deprecation assertion uses Deprecated::class and ReflectionAttribute::newInstance(). On PHP < 8.4 the global Deprecated attribute class may not exist (and test order is random), causing failures; additionally newInstance() will execute the attribute constructor, which in the polyfill triggers an E_USER_DEPRECATED. Prefer checking by attribute name (string) and inspecting ReflectionAttribute::getArguments() instead of instantiating the attribute.
| public function testDeprecation(): void | ||
| { | ||
| $attributes = (new ReflectionFunction(NormalTag::tag(...)))->getAttributes(Deprecated::class); | ||
| $this->assertNotEmpty($attributes); | ||
| $this->assertSame('Use the constructor instead.', $attributes[0]->newInstance()->message); | ||
| } |
There was a problem hiding this comment.
This deprecation assertion uses Deprecated::class and ReflectionAttribute::newInstance(). On PHP < 8.4 the global Deprecated attribute class may not exist (and test order is random), causing failures; additionally newInstance() will execute the attribute constructor, which in the polyfill triggers an E_USER_DEPRECATED. Prefer checking by attribute name (string) and inspecting ReflectionAttribute::getArguments() instead of instantiating the attribute.
| public function testDeprecation(): void | ||
| { | ||
| $attributes = (new ReflectionFunction(CheckboxList::create(...)))->getAttributes(Deprecated::class); | ||
| $this->assertNotEmpty($attributes); | ||
| $this->assertSame('Use the constructor instead.', $attributes[0]->newInstance()->message); | ||
| } |
There was a problem hiding this comment.
This deprecation assertion uses Deprecated::class and ReflectionAttribute::newInstance(). On PHP < 8.4 the global Deprecated attribute class may not exist (and test order is random), causing failures; additionally newInstance() will execute the attribute constructor, which in the polyfill triggers an E_USER_DEPRECATED. Prefer checking by attribute name (string) and inspecting ReflectionAttribute::getArguments() instead of instantiating the attribute.
| public function testDeprecation(): void | ||
| { | ||
| $attributes = (new ReflectionFunction(CustomTag::name(...)))->getAttributes(Deprecated::class); | ||
| $this->assertNotEmpty($attributes); | ||
| $this->assertSame('Use the constructor instead.', $attributes[0]->newInstance()->message); | ||
| } |
There was a problem hiding this comment.
This deprecation assertion uses Deprecated::class and ReflectionAttribute::newInstance(). On PHP < 8.4 the global Deprecated attribute class may not exist (and test order is random), causing failures; additionally newInstance() will execute the attribute constructor, which in the polyfill triggers an E_USER_DEPRECATED. Prefer checking by attribute name (string) and inspecting ReflectionAttribute::getArguments() instead of instantiating the attribute.
| public function testDeprecation(): void | ||
| { | ||
| $attributes = (new ReflectionFunction(VoidTag::tag(...)))->getAttributes(Deprecated::class); | ||
| $this->assertNotEmpty($attributes); | ||
| $this->assertSame('Use the constructor instead.', $attributes[0]->newInstance()->message); | ||
| } |
There was a problem hiding this comment.
This deprecation assertion uses Deprecated::class and ReflectionAttribute::newInstance(). On PHP < 8.4 the global Deprecated attribute class may not exist (and test order is random), causing failures; additionally newInstance() will execute the attribute constructor, which in the polyfill triggers an E_USER_DEPRECATED. Prefer checking by attribute name (string) and inspecting ReflectionAttribute::getArguments() instead of instantiating the attribute.
| ## 3.13.0 | ||
|
|
||
| - Enh #243: Use `new tagName()` instead of static constructor `tagName::tag()` (@razvbir) | ||
|
|
||
| ## 3.12.1 under development | ||
|
|
||
| - New #260: Add `$attributes` parameter to `Html::li()` method (@gauravkumar2525) | ||
| - Enh #261: Enhance `RadioList::addRadioWrapClass()` method for cleaner class addition (@vjik) | ||
| - Enh #263: Explicitly import classes and constants in "use" section (@mspirkov) | ||
| - Enh #243: Use `new tagName()` without parentheses instead of static constructor `tagName::tag()` (@razvbir) |
There was a problem hiding this comment.
The changelog mentions the same enhancement (#243) in both the new 3.13.0 section and under 3.12.1 under development, and the latter also claims usage "without parentheses" even though the codebase uses (new Class()) for PHP 8.1+ compatibility. Consider keeping the entry only in the intended release section (likely 3.13.0 for a BC break) and aligning the wording with the actual syntax change.
|
|
||
| ## 3.13.0 | ||
|
|
||
| - Enh #243: Use `new tagName()` instead of static constructor `tagName::tag()` (@razvbir) |
| - New #260: Add `$attributes` parameter to `Html::li()` method (@gauravkumar2525) | ||
| - Enh #261: Enhance `RadioList::addRadioWrapClass()` method for cleaner class addition (@vjik) | ||
| - Enh #263: Explicitly import classes and constants in "use" section (@mspirkov) | ||
| - Enh #243: Use `new tagName()` without parentheses instead of static constructor `tagName::tag()` (@razvbir) |
| "symbol-whitelist": [ | ||
| "BackedEnum" | ||
| "BackedEnum", | ||
| "Deprecated" |
There was a problem hiding this comment.
Package works with PHP 8.1, Deprecated attribute appears in PHP 8.4 only. Use PHP annotation @deprecated instead.
| use const E_USER_DEPRECATED; | ||
|
|
||
| #[Attribute] | ||
| final class Deprecated |
There was a problem hiding this comment.
This is not an entirely correct polyfill implementation since it doesn't handle reflection and overall behaves differently.
Symfony has it in their polyfills https://github.com/symfony/polyfill/tree/1.x/src/Php84/Resources/stubs but it's more complicated than what they did:
symfony/polyfill#551.
I don't think we need to introduce polyfills for this pull request at all. Let's use @deprecated instead. Should be enough.