Skip to content

Improve attribute parser - #6

Merged
calebporzio merged 6 commits into
mainfrom
josh/improve-attribute-parser
Oct 20, 2025
Merged

Improve attribute parser#6
calebporzio merged 6 commits into
mainfrom
josh/improve-attribute-parser

Conversation

@joshhanley

@joshhanley joshhanley commented Oct 20, 2025

Copy link
Copy Markdown
Member

This PR improves the attribute parser and adds more tests around it.

The scenario

One major change is in how dynamic attributes are handled.

Previously, if you had a component like this:

<flux:link :href="route('dashboard'")>Dashboard</flux:link>

Which internally looked like this:

@props(['href'])

<a href="{{ $href }}">{{ $slot }}</a>

When it got rendered, it was outputting:

<a href="route('dashboard')">Dashboard</a>

The problem

When the dynamic attributes were swapped with placeholders, the value for href would become ATTR_PLACEHOLDER_0.

So then when the component is rendered by Blaze to fold it, the rendered version before placeholders were restored, was:

<a href="ATTR_PLACEHOLDER_0">

Now the issue is that when Blaze goes to restore the placeholder, because the {{ }} that was around $url previously has now been removed by Blade when it rendered $url, the attribute's raw value is being output in the HTML. Hence why we are getting <a href="route('dashboard')">.

I know that the AttributeParser code is something that you mentioned could be improved. So I reviewed the parseAndReplaceDynamics() method closely, I found that it was doing extra work that probably was unnecessary and it was also handling dynamic content in a couple of different ways depending on what the content was that probably wasn't needed.

There was previously 3 different ways the value of a dynamic attribute could be handled.

if (preg_match('/^"\$([a-zA-Z0-9_]+)"$/', $attributeValue, $m)) {
    $attributePlaceholders[$placeholder] = '{{ $' . $m[1] . ' }}';
} elseif ($attributeValue !== '' && $attributeValue[0] === '$') {
    $attributePlaceholders[$placeholder] = '{{ ' . $attributeValue . ' }}';
} else {
    $attributePlaceholders[$placeholder] = $attributeValue;
}
  1. The first was a regex to determine of the value was surrounded by "" and stared with a $.

For some reason it was handling both :name="$name" and :name=$name, which I don't believe is valid in Blade anyway.

We'd actually by passed that regex check by manually stripping the "" before processing, so it was now redundant and being skipped.

This left two remaining conditionals.

  1. The second conditional was if the value of the dynamic attribute started with a $.

In this scenario, presuming the first scenario was false, it would then take the attribute value and wrap it in {{ }}.

This meant that when the placeholder was restored in the HTML, if the dynamic value started with a $ like $foo, then it would be output as {{ $foo }} in the HTML.

  1. The final condition was if there was no match on the first two, then just output the value directly, without any processing.

The problem with this final condition's strategy is that the content is dynamic, which means that it should be executed as PHP. But outputting the value directly without processing it means the raw PHP values will be output, which is unexpected and what is happening in the example above with the link component.

The solution

The solution in this PR is to wrap all dynamic attribute values in {{ }}, to ensure it can still be rendered properly after the placeholders have been removed.

I've also remove the handling for both :name="$name" and :name=$name, which has simplified the code.

Now there is no conditional logic as all dynamic attributes are handled in the same way.

I added some tests for the parseAndReplaceDynamics() function too; and reviewed and cleaned up the parseAttributeStringToArray() function, that I added the other day, to match the changes in parseAndReplaceDynamics() as they both use similar logic.

Note: The caveat for this change is that, now people can pass any dynamic attributes into the component, but the way they are used inside the component must just be a basic echo statement. Anything more complex and it will fail.

One other thing we're not handling is if it is echoed using {!! !!} instead of {{ }}. I reckon we could add detection for this though if we wanted, but that will require inspecting the source of the component.

@calebporzio
calebporzio merged commit 1e5f930 into main Oct 20, 2025
6 checks passed
@calebporzio
calebporzio deleted the josh/improve-attribute-parser branch October 20, 2025 20:53
@github-actions github-actions Bot mentioned this pull request Mar 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants