Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: Parser - Equal key name replace conflict #9245

Closed
CosDiabos opened this issue Oct 30, 2024 · 2 comments · Fixed by #9246
Closed

Bug: Parser - Equal key name replace conflict #9245

CosDiabos opened this issue Oct 30, 2024 · 2 comments · Fixed by #9246
Labels
bug Verified issues on the current code behavior or pull requests that will fix them

Comments

@CosDiabos
Copy link
Contributor

PHP Version

8.3

CodeIgniter4 Version

4.5.5

CodeIgniter4 Installation Method

Composer (using codeigniter4/appstarter)

Which operating systems have you tested for this bug?

macOS

Which server did you use?

apache

Database

MySQL 8.3.0

What happened?

Using the same key name in an array although on different levels, leads to a premature substitution of {key}, when that {key} is referring in a nested one on a loop substitution.

(Based on the Steps to Reproduce section)

With the current Parser behaviour, both {id} vars will be replaced with their respective value but given that the second {id} is inside {blog_entries}, by 'blindly' replacing all instances of {variable}, {blog_entries}{id}{/blog_entries} will turn into {blog_entries}1{/blog_entries} which will fail to replace the {blog_entries}{id}{/blog_entries} with the proper data when it reaches to the blog_entries array.

Steps to Reproduce

$data = [
    'id'   => 'id_1',
    'blog_title'   => 'My Blog Title',
    'blog_heading' => 'My Blog Heading',
    'blog_entries' => [
        ['id' => 'sub_id_1', 'title' => 'Title 1', 'body' => 'Body 1'],
        ['id' => 'sub_id_2', 'title' => 'Title 2', 'body' => 'Body 2'],
        ['id' => 'sub_id_3', 'title' => 'Title 3', 'body' => 'Body 3'],
        ['id' => 'sub_id_4', 'title' => 'Title 4', 'body' => 'Body 4'],
        ['id' => 'sub_id_5', 'title' => 'Title 5', 'body' => 'Body 5']
    ]
];

$template = <<<EOD
    ID: {id}
    title: {blog_title}
    Blog Entries:
    {blog_entries}
    {id} - {title} - {body}
{/blog_entries}
EOD;

$parser = service('parser');

print $parser->setData($data)->renderString($template);

Returns:

ID: id_1
title: My Blog Title Blog 
Entries: 
id_1 - Title 1 - Body 1
id_1 - Title 2 - Body 2
id_1 - Title 3 - Body 3
id_1 - Title 4 - Body 4
id_1 - Title 5 - Body 5

Going a level down

$data = [
    'id'   => 'id_1',
    'blog_title'   => 'My Blog Title',
    'blog_heading' => 'My Blog Heading',
    'blog_entries' => [
        ['items' =>
            [
                ['id' => 'sub_id_1', 'title' => 'Title 1', 'body' => 'Body 1'],
                ['id' => 'sub_id_2', 'title' => 'Title 2', 'body' => 'Body 2'],
                ['id' => 'sub_id_3', 'title' => 'Title 3', 'body' => 'Body 3'],
                ['id' => 'sub_id_4', 'title' => 'Title 4', 'body' => 'Body 4'],
                ['id' => 'sub_id_5', 'title' => 'Title 5', 'body' => 'Body 5']
            ]
        ]
    ]
];

$template = <<<EOD
  ID: {id}
  title: {blog_title}
  Blog Entries:
  {blog_entries}
{items}
  {id} - {title} - {body}
{/items}
{/blog_entries}
EOD;

$parser = service('parser');

echo $parser->setData($data)->renderString($template);

Returns:

ID: id_1
title: My Blog Title
Blog Entries: 
{items} id_1 - {title} - {body} {/items}

And it fails to properly replace the rest of the data.

Expected Output

ID: id_1
title: My Blog Title
Blog Entries: 
sub_id_1 - Title 1 - Body 1
sub_id_2 - Title 2 - Body 2
sub_id_3 - Title 3 - Body 3
sub_id_4 - Title 4 - Body 4
sub_id_5 - Title 5 - Body 5

Anything else?

This behaviour seems to stem from how the Parser::parse() function acts. From my understanding, when the parsing occurs the method loops over the data passed to it and if the data to be replaced isn't an array (Pair) then, it immediately replaces the content (Single). Which means, if there is multiple keys with the same identifier down the line, they will get replaced all at once, breaking further processing specially when using Pairs like {blog_entries}{items}{id}{items}{/blog_entries}.

I propose a solution in which the return of parsePair and parseSingle are stored independently, then merged together (Pairs with Single), and finally the replacement loop happens. With this approach, the replacement happens from the Pairs to Single and the output is as expected since {blog_entries}{items}{id}{items}{/blog_entries} will be {blog_entries}{items}sub_id_1{items}{/blog_entries} before the top {id} replace is triggered.

@CosDiabos CosDiabos added the bug Verified issues on the current code behavior or pull requests that will fix them label Oct 30, 2024
@michalsn
Copy link
Member

Confirmed. Thank you for reporting it.

Feel free to send a PR.

@CosDiabos
Copy link
Contributor Author

PR sent!

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Verified issues on the current code behavior or pull requests that will fix them
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants