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

Fix \Knp\Menu\MenuItem @label issue #6031

Closed
wants to merge 14 commits into from

Conversation

maxplatonov
Copy link

@maxplatonov maxplatonov commented Apr 6, 2020

Subject

I am targeting this branch, because it is a bug fix.

Changelog

### Fixed
- Call \Knp\Menu\MenuItem::getLabel() method directly in twig template to avoid a possible side effect from \ArrayAccess.

Description:

\Knp\Menu\MenuItem in knplabs/knp-menu-bundle implements \ArrayAccess and defines its offsetGet method as follows:

    /**
     * Implements ArrayAccess
     */
    public function offsetGet($name)
    {
        return $this->getChild($name);
    }

    public function getChild($name)
    {
        return isset($this->children[$name]) ? $this->children[$name] : null;
    }

It has a property $label with its getter:

    /**
     * Label to output, name is used by default
     *
     * @var string|null
     */
    protected $label;

    public function getLabel()
    {
        return (null !== $this->label) ? $this->label : $this->name;
    }

Problem :

If a MenuItem object has a child with the key label, the twig expression menu.label will trigger the offsetGet method of the \ArrayAccess and return the child instead of the $label property of the MenuItem object.

This would result in Catchable Fatal Error: Object of class Knp\Menu\MenuItem could not be converted to string.

Solution:

Call the getter of the $label property directly:

menu.getLabel()

@phansys
Copy link
Member

phansys commented Apr 7, 2020

Thank you @maxplatonov!
Could you please add a test in order to prevent regressions?
You should also update the PR's description in order to use the provided template.

@VincentLanglet
Copy link
Member

Do you think this behaviour should be reported to twig repository ?

IMHO I expect menu.label to look for a getLabel method before using offsetGet.

@maxplatonov
Copy link
Author

Do you think this behaviour should be reported to twig repository ?

IMHO I expect menu.label to look for a getLabel method before using offsetGet.

Hello @VincentLanglet,

I suppose it would be wrong to expect that. As per Twig (2, 3) documentation:

For convenience’s sake foo.bar does the following things on the PHP layer:

check if foo is an array and bar a valid element;

⬇️ This is where \ArrayAccess comes into play.

if not, and if foo is an object, check that bar is a valid property;

⬆️

https://github.com/twigphp/Twig/blob/2.x/src/Extension/CoreExtension.php#L1340-L1346

if not, and if foo is an object, check that bar is a valid method (even if bar is the constructor - use __construct() instead);
if not, and if foo is an object, check that getBar is a valid method;
if not, and if foo is an object, check that isBar is a valid method;
if not, and if foo is an object, check that hasBar is a valid method;
if not, return a null value.

IMHO Twig's behaviour may change in the future, I would rely on the getter.

VincentLanglet
VincentLanglet previously approved these changes Apr 10, 2020
Copy link
Member

@VincentLanglet VincentLanglet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you want to add a test @phansys ?

@phansys
Copy link
Member

phansys commented Apr 10, 2020

I think a functional test should be enough to cover the exposed issue.

Copy link
Member

@phansys phansys left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @maxplatonov.

I've left a comment about the test, since I'd like to focus the effort on ensuring the issue is solved with our real codebase.

tests/Menu/Integration/TabMenuTest.php Outdated Show resolved Hide resolved
@franmomu
Copy link
Member

franmomu commented Apr 12, 2020

Let me try to add a functional test @maxplatonov to see how hard it is, I'll make you a PR if I'll achieve something.

@maxplatonov
Copy link
Author

Let me try to add a functional test @maxplatonov to see how hard it is, I'll make you a PR if I'll achieve something.

Thanks, @franmomu !

@franmomu
Copy link
Member

I've just did the PR adding the test, it actually shows that there is another place to change it in standard_layout.html.twig and in sonata_menu.html.twig.

And I guess it means that it will fail in the KnpMenu library.

@maxplatonov
Copy link
Author

Hello @franmomu,
Thanks for your PR! I was disconnected for a week, I'll review it today.

@SonataCI
Copy link
Collaborator

SonataCI commented May 2, 2020

Could you please rebase your PR and fix merge conflicts?

maxplatonov and others added 7 commits May 5, 2020 11:44
## Description:

\Knp\Menu\MenuItem in knplabs/knp-menu-bundle implements \ArrayAccess and defines its offsetGet method as follows:
```php
    /**
     * Implements ArrayAccess
     */
    public function offsetGet($name)
    {
        return $this->getChild($name);
    }

    public function getChild($name)
    {
        return isset($this->children[$name]) ? $this->children[$name] : null;
    }

```

It has a property @Label with its getter: 
```php
    /**
     * Label to output, name is used by default
     *
     * @var string|null
     */
    protected $label;

    public function getLabel()
    {
        return (null !== $this->label) ? $this->label : $this->name;
    }
```
## Problem :

If a MenuItem object has a child with the key `label`, the twig expression `menu.label` will trigger the `offsetGet` method of the \ArrayAccess and return the child instead of the `label` property of the MenuItem object.

## Solution:

Call the getter of the `label` property directly:
```twig
menu.getLabel()
```
@maxplatonov
Copy link
Author

Hello, @franmomu,

Could you please review? I rebased the branch on the upstream. I had to restore the \Sonata\AdminBundle\Tests\App\Model\ModelManager::getModelInstance as it broke a new test.

Thank you.

franmomu
franmomu previously approved these changes May 5, 2020
Copy link
Member

@franmomu franmomu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! You could do a PR to fix it in KnpMenu as well if you feel like doing it!

src/Resources/views/standard_layout.html.twig Show resolved Hide resolved
src/Resources/views/standard_layout.html.twig Show resolved Hide resolved
tests/App/Model/ModelManager.php Outdated Show resolved Hide resolved
tests/App/Model/PR6031Repository.php Outdated Show resolved Hide resolved
maxplatonov and others added 2 commits May 6, 2020 12:55
Co-authored-by: Javier Spagnoletti <[email protected]>
Co-authored-by: Fran Moreno <[email protected]>
src/Resources/views/Core/tab_menu_template.html.twig Outdated Show resolved Hide resolved
src/Resources/views/Menu/sonata_menu.html.twig Outdated Show resolved Hide resolved
src/Resources/views/standard_layout.html.twig Outdated Show resolved Hide resolved
src/Resources/views/standard_layout.html.twig Outdated Show resolved Hide resolved
@VincentLanglet
Copy link
Member

@maxplatonov Please rebase 3.x in order to have working tests.

@@ -21,11 +21,17 @@

final class ModelManager implements ModelManagerInterface
{
private $repository;
/**
* @var array array<class-string, RepositoryInterface>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this annotation right? (array array)

@@ -119,8 +119,9 @@
{% endblock %}

{% block label %}
{{-
item.label|trans(
{# We use method accessor instead of ".label" since `item` implements `ArrayAccess` and could have a property called "label".#}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{# We use method accessor instead of ".label" since `item` implements `ArrayAccess` and could have a property called "label".#}
{# We use method accessor instead of ".label" since `item` implements `ArrayAccess` and could have a property called "label". #}

@@ -47,4 +47,5 @@
{% endapply %}
{% endblock %}

{% block label %}{% if is_link is defined and is_link %}{{ icon|default|raw }}{% endif %}{% if options.allow_safe_labels and item.extra('safe_label', false) %}{{ item.label|raw }}{% else %}{{ item.label|trans({}, translation_domain|default('messages')) }}{% endif %}{% endblock %}
{# We use method accessor instead of ".label" since `item` implements `ArrayAccess` and could have a property called "label".#}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{# We use method accessor instead of ".label" since `item` implements `ArrayAccess` and could have a property called "label".#}
{# We use method accessor instead of ".label" since `item` implements `ArrayAccess` and could have a property called "label". #}

@@ -96,7 +96,8 @@ file that was distributed with this source code.
{% endif %}

{%- set translation_domain = menu.extra('translation_domain', 'messages') -%}
{%- set label = menu.label -%}
{# We use method accessor instead of ".label" since `menu` implements `ArrayAccess` and could have a property called "label".#}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{# We use method accessor instead of ".label" since `menu` implements `ArrayAccess` and could have a property called "label".#}
{# We use method accessor instead of ".label" since `menu` implements `ArrayAccess` and could have a property called "label". #}


final class PR6031Test extends WebTestCase
{
public function testLabelInShowAction(): void
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this test is not performing any direct assertion against the label, we should add a description explaining how it's related.

@VincentLanglet
Copy link
Member

@maxplatonov Hi ! Do you have some time to finish this PR ? :)

@SonataCI
Copy link
Collaborator

Could you please rebase your PR and fix merge conflicts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants