Skip to content

Bug: Parser - Equal key name replace conflict #9245

@CosDiabos

Description

@CosDiabos

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugVerified issues on the current code behavior or pull requests that will fix them

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions