Re: [PHP-DEV] [RFC] [Discussion] Native Markup Expressions

"T.J. L" <[email protected]>
Newsgroups gmane.comp.php.devel
Message-ID <[email protected]>
 > That's fair - PSR-4 isn't binding, and lowercase class names are legal
in PHP - it's a real tradeoff. While in theory I agree, I'd note JSX
uses this exact heuristic (lowercase = regular HTML element,
capitalised = component) and it's held up well with the frontend
ecosystem at scale for more than a decade.

Doing this also means there is no runtime enforcement or type-checking 
of HTML tags or their attributes. One of the original selling points of 
XHP is that it won't let you output invalid HTML, and runtime validation 
is important for that.

JSX gets away with it in large part because of the Typescript ecosystem, 
where static analysis covers a lot of ground. I don't think core PHP 
syntax should be assuming people are using such tools, though. Is the 
performance improvement of not instantiating every HTML tag expected to 
be significant?

-T.J. L

On 7/14/26 8:50 PM, Liam Hammett wrote:
> On Wed, Jul 15, 2026 at 1:33 AM Garrett W. <[email protected]> wrote:
>> On Tue, Jul 14, 2026 at 7:31 PM Liam Hammett
>> <[email protected]> wrote:
>>> Hi internals,
>>>
>>> I'd like to open discussion on a new RFC, "Native Markup Expressions":
>>>
>>> RFC: https://wiki.php.net/rfc/native_markup_expressions
>>> Implementation (with tests): https://github.com/php/php-src/pull/22661
>>>
>>> It proposes a native syntax for HTML fragments as first-class PHP expressions,
>>> akin to how the frontend community has adopted JSX, with composition and
>>> escape-by-default output:
>>>
>>>
>>>      class Greeting implements Markup\Html
>>>      {
>>>          public function __construct(public string $name) {}
>>>
>>>          public function toHtml(): Markup\Html
>>>          {
>>>              return <>
>>>                  <h1 class="title">Hello, {$this->name}!</h1>
>>>                  <p>Welcome to PHP, where markup is a first-class
>>>                  expression.</p>
>>>              </>;
>>>          }
>>>      }
>>>
>>>      echo <Greeting name="Rasmus" />; // rendered HTML, values escaped
>>>
>>>
>>> Despite appearances, this is not a template language grafted onto the engine -
>>> the syntax is pure compile-time sugar. Every markup expression lowers
>>> during compilation to a plain `new` expression:
>>>
>>>
>>>      $html = <button class="btn">Sign in</button>;
>>>      // compiles to exactly:
>>>      $html = new \Markup\Element('button', ['class' => 'btn'], ['Sign in']);
>>>
>>>
>>> The RFC already answers many anticipated questions, and I am open
>>> to making further adjustments so this can become a feature the whole
>>> community benefits from.
>>>
>>> Looking forward to your feedback.
>>>
>>> Best regards,
>>> Liam
>> Cool! I'm reading through it now, and one thing gave me pause:
>>
>>> Any tag whose name is capitalized, contains a namespace separator (), or names a static method (::) is a component; everything else is a literal HTML element.
>> Not so sure about that heuristic. PSR-4 is not a binding standard on
>> the language; class names can be lowercase, and therefore should be
>> valid as a component name. Instead of looking at whether the tag name
>> is capitalized, I'd think it would be better handled using the
>> standard fallback resolution strategy: look in the current namespace,
>> then look at imports, then global/root namespace, and only then
>> fallback to a literal HTML tag.
>>
>> --
>> Garrett W.
> That's fair - PSR-4 isn't binding, and lowercase class names are legal
> in PHP - it's a real tradeoff. While in theory I agree, I'd note JSX
> uses this exact heuristic (lowercase = regular HTML element,
> capitalised = component) and it's held up well with the frontend
> ecosystem at scale for more than a decade. A few reasons I think it's
> the right call here too:
>
> 1. Fallback resolution turns typos into silent bugs. With the
> capitalisation rule, `<Layuot />` fails loudly with a class-not-found
> error. With fallback resolution, it silently renders as a literal
> `<Layuot>` element and you find out in the browser, if you find out at
> all.
> 2. Markup expressions lower to `new` expressions at compile time.
> Fallback resolution requires knowing whether a class exists, which
> means hitting the autoloader - so tag meaning could no longer be
> decided at compile time. It would also make markup
> load-order-dependent: whether `<div>` means an element or a component
> would depend on whether any loaded code defines a class named `div`,
> and defining one later would silently change the meaning of existing
> markup elsewhere.
> 3. The common case is plain HTML with components sprinkled in. The
> heuristic lets the compiler treat lowercase tags as pure data with
> zero resolution work for a performance boon.
>
> Best regards,
> Liam
>
> On Wed, Jul 15, 2026 at 1:33 AM Garrett W. <[email protected]> wrote:
>> On Tue, Jul 14, 2026 at 7:31 PM Liam Hammett
>> <[email protected]> wrote:
>>> Hi internals,
>>>
>>> I'd like to open discussion on a new RFC, "Native Markup Expressions":
>>>
>>> RFC: https://wiki.php.net/rfc/native_markup_expressions
>>> Implementation (with tests): https://github.com/php/php-src/pull/22661
>>>
>>> It proposes a native syntax for HTML fragments as first-class PHP expressions,
>>> akin to how the frontend community has adopted JSX, with composition and
>>> escape-by-default output:
>>>
>>>
>>>      class Greeting implements Markup\Html
>>>      {
>>>          public function __construct(public string $name) {}
>>>
>>>          public function toHtml(): Markup\Html
>>>          {
>>>              return <>
>>>                  <h1 class="title">Hello, {$this->name}!</h1>
>>>                  <p>Welcome to PHP, where markup is a first-class
>>>                  expression.</p>
>>>              </>;
>>>          }
>>>      }
>>>
>>>      echo <Greeting name="Rasmus" />; // rendered HTML, values escaped
>>>
>>>
>>> Despite appearances, this is not a template language grafted onto the engine -
>>> the syntax is pure compile-time sugar. Every markup expression lowers
>>> during compilation to a plain `new` expression:
>>>
>>>
>>>      $html = <button class="btn">Sign in</button>;
>>>      // compiles to exactly:
>>>      $html = new \Markup\Element('button', ['class' => 'btn'], ['Sign in']);
>>>
>>>
>>> The RFC already answers many anticipated questions, and I am open
>>> to making further adjustments so this can become a feature the whole
>>> community benefits from.
>>>
>>> Looking forward to your feedback.
>>>
>>> Best regards,
>>> Liam
>> Cool! I'm reading through it now, and one thing gave me pause:
>>
>>> Any tag whose name is capitalized, contains a namespace separator (), or names a static method (::) is a component; everything else is a literal HTML element.
>> Not so sure about that heuristic. PSR-4 is not a binding standard on
>> the language; class names can be lowercase, and therefore should be
>> valid as a component name. Instead of looking at whether the tag name
>> is capitalized, I'd think it would be better handled using the
>> standard fallback resolution strategy: look in the current namespace,
>> then look at imports, then global/root namespace, and only then
>> fallback to a literal HTML tag.
>>
>> --
>> Garrett W.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.