8000 DI generates an invalid code. 'Undefined variable $containerRef' · Issue #50257 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content
8000

DI generates an invalid code. 'Undefined variable $containerRef' #50257

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

Closed
marphi opened this issue May 7, 2023 · 3 comments · Fixed by #50262
Closed

DI generates an invalid code. 'Undefined variable $containerRef' #50257

marphi opened this issue May 7, 2023 · 3 comments · Fixed by #50262

Comments

@marphi
Copy link
Contributor
marphi commented May 7, 2023

Symfony version(s) affected

6.3.0-BETA2

Description

I tested the latest 6.3.0-BETA2 release on my project and found something worth to report.

I've got this warning during cache:clear
Warning: Undefined variable $containerRef

I suspect the issue is related to !tagged_iterator and factories.

How to reproduce

Example DI configuration in this case part of my SonataAdmin configuration.

   app_admin.widget_template:
        class: App\Bundle\AdminBundle\Admin\WidgetTemplateAdmin
        tags:
            - { name: sonata.admin, manager_type: orm, group: "Widgets", label: "Templates" }
        arguments:
            - ~
            - App\Component\WidgetTemplate\Entity\WidgetTemplateConfig
            - ~
        calls:
            - [ setTranslationDomain, [ AppAdminWidgetTemplate ] ]
            - [ setWidgetTypes, [ !tagged_iterator app.widget.template_type ]]

Generates invalid code:

<?php

namespace ContainerPq0jNwe;

use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;

/**
 * @internal This class has been auto-generated by the Symfony Dependency Injection Component.
 */
class getAppAdmin_WidgetTemplateService extends App_KernelDevDebugContainer
{
    /**
     * Gets the private 'app_admin.widget_template' autowired service.
     *
     * @return \App\Bundle\AdminBundle\Admin\WidgetTemplateAdmin
     */
    public static function do($container, $lazyLoad = true)
    {
        $containerRef = $container->ref;

        include_once \dirname(__DIR__, 4).'/vendor/sonata-project/admin-bundle/src/Templating/MutableTemplateRegistryAwareInterface.php';
        include_once \dirname(__DIR__, 4).'/vendor/sonata-project/admin-bundle/src/DependencyInjection/Admin/TaggedAdminInterface.php';
        include_once \dirname(__DIR__, 4).'/vendor/sonata-project/admin-bundle/src/DependencyInjection/Admin/AbstractTaggedAdmin.php';
        include_once \dirname(__DIR__, 4).'/vendor/sonata-project/admin-bundle/src/Admin/AccessRegistryInterface.php';
        include_once \dirname(__DIR__, 4).'/vendor/sonata-project/admin-bundle/src/FieldDescription/FieldDescriptionRegistryInterface.php';
        include_once \dirname(__DIR__, 4).'/vendor/sonata-project/admin-bundle/src/Admin/LifecycleHookProviderInterface.php';
        include_once \dirname(__DIR__, 4).'/vendor/sonata-project/admin-bundle/src/Admin/ParentAdminInterface.php';
        include_once \dirname(__DIR__, 4).'/vendor/sonata-project/admin-bundle/src/Admin/UrlGeneratorInterface.php';
        include_once \dirname(__DIR__, 4).'/vendor/sonata-project/admin-bundle/src/Admin/AdminInterface.php';
        include_once \dirname(__DIR__, 4).'/vendor/symfony/security-acl/Model/DomainObjectInterface.php';
        include_once \dirname(__DIR__, 4).'/vendor/sonata-project/admin-bundle/src/Admin/AdminTreeInterface.php';
        include_once \dirname(__DIR__, 4).'/vendor/sonata-project/admin-bundle/src/Admin/AbstractAdmin.php';
        include_once \dirname(__DIR__, 4).'/src/App/Bundle/AdminBundle/Admin/Admin.php';
        include_once \dirname(__DIR__, 4).'/src/App/Bundle/AdminBundle/Admin/WidgetTemplateAdmin.php';
        include_once \dirname(__DIR__, 4).'/vendor/sonata-project/admin-bundle/src/Exporter/DataSourceInterface.php';
        include_once \dirname(__DIR__, 4).'/vendor/sonata-project/doctrine-orm-admin-bundle/src/Exporter/DataSource.php';
        include_once \dirname(__DIR__, 4).'/vendor/sonata-project/admin-bundle/src/Translator/LabelTranslatorStrategyInterface.php';
        include_once \dirname(__DIR__, 4).'/vendor/sonata-project/admin-bundle/src/Translator/NativeLabelTranslatorStrategy.php';

        $container->factories['service_container']['app_admin.widget_template'] = function ($container) {
            $instance = new \App\Bundle\AdminBundle\Admin\WidgetTemplateAdmin('app_admin.widget_template', 'App\\Component\\WidgetTemplate\\Entity\\WidgetTemplateConfig', 'sonata.admin.controller.crud');

            $instance->setManagerType('orm');
            $instance->setModelManager(($container->services['sonata.admin.manager.orm'] ?? $container->load('getSonata_Admin_Manager_OrmService')));
            $instance->setDataSource(($container->privates['sonata.admin.data_source.orm'] ??= new \Sonata\DoctrineORMAdminBundle\Exporter\DataSource()));
            $instance->setFieldDescriptionFactory(($container->privates['sonata.admin.field_description_factory.orm'] ?? $container->load('getSonata_Admin_FieldDescriptionFactory_OrmService')));
            $instance->setFormContractor(($container->privates['sonata.admin.builder.orm_form'] ?? $container->load('getSonata_Admin_Builder_OrmFormService')));
            $instance->setShowBuilder(($container->privates['sonata.admin.builder.orm_show'] ?? $container->load('getSonata_Admin_Builder_OrmShowService')));
            $instance->setListBuilder(($container->privates['sonata.admin.builder.orm_list'] ?? $container->load('getSonata_Admin_Builder_OrmListService')));
            $instance->setDatagridBuilder(($container->privates['sonata.admin.builder.orm_datagrid'] ?? $container->load('getSonata_Admin_Builder_OrmDatagridService')));
            $instance->setTranslator(($container->services['translator'] ?? self::getTranslatorService($container)), false);
            $instance->setConfigurationPool(($container->privates['sonata.admin.pool'] ?? self::getSonata_Admin_PoolService($container)));
            $instance->setRouteGenerator(($container->privates['sonata.admin.route.default_generator'] ?? $container->load('getSonata_Admin_Route_DefaultGeneratorService')));
            $instance->setSecurityHandler(($container->privates['sonata.admin.security.handler.role'] ?? $container->load('getSonata_Admin_Security_Handler_RoleService')));
            $instance->setMenuFactory(($container->services['knp_menu.factory'] ?? $container->load('getKnpMenu_FactoryService')));
            $instance->setRouteBuilder(($container->privates['sonata.admin.route.path_info'] ?? $container->load('getSonata_Admin_Route_PathInfoService')));
            $instance->setLabelTranslatorStrategy(($container->privates['sonata.admin.label.strategy.native'] ??= new \Sonata\AdminBundle\Translator\NativeLabelTranslatorStrategy()));
            $instance->setPagerType('default');
            $instance->setLabel('Templates');
            $instance->setTranslationDomain('messages');
            $instance->setListModes(['list' => ['icon' => '<i class="fas fa-list fa-fw" aria-hidden="true"></i>', 'class' => 'fas fa-list fa-fw'], 'mosaic' => ['icon' => '<i class="fas fa-th-large fa-fw" aria-hidden="true"></i>', 'class' => 'fas fa-th-large fa-fw']]);
            $instance->setSecurityInformation($container->parameters['sonata.admin.configuration.security.information']);
            $instance->setFormTheme(['@SonataDoctrineORMAdmin/Form/form_admin_fields.html.twig']);
            $instance->setFilterTheme(['@SonataDoctrineORMAdmin/Form/filter_admin_fields.html.twig']);
            $instance->setTranslationDomain('AppAdminWidgetTemplate');
            $instance->setWidgetTypes(new RewindableGenerator(function () use ($containerRef) {
                $container = $containerRef->get();

                yield 0 => ($container->privates['App\\Component\\WidgetTemplate\\Service\\WidgetType\\UserDetailsWidgetType'] ??= new \App\Component\WidgetTemplate\Service\WidgetType\UserDetailsWidgetType());
                yield 1 => ($container->privates['App\\Component\\WidgetTemplate\\Service\\WidgetType\\UserPhoneWidge
8000
tType'] ?? $container->load('getUserPhoneWidgetTypeService'));
                yield 2 => ($container->privates['App\\Component\\WidgetTemplate\\Service\\WidgetType\\UserPlaceWidgetType'] ??= new \App\Component\WidgetTemplate\Service\WidgetType\UserPlaceWidgetType());
                yield 3 => ($container->privates['App\\Component\\WidgetTemplate\\Service\\WidgetType\\UserPreferencesWidgetType'] ??= new \App\Component\WidgetTemplate\Service\WidgetType\UserPreferencesWidgetType());
                yield 4 => ($container->privates['App\\Component\\WidgetTemplate\\Service\\WidgetType\\UserProfileWidgetType'] ??= new \App\Component\WidgetTemplate\Service\WidgetType\UserProfileWidgetType());
            }, 5));
            $instance->setTemplateRegistry(($container->services['app_admin.widget_template.template_registry'] ?? $container->load('getAppAdmin_WidgetTemplate_TemplateRegistryService')));
            $instance->addExtension(($container->privates['sonata.admin.event.extension'] ?? $container->load('getSonata_Admin_Event_ExtensionService')));
            $instance->addExtension(($container->privates['app_admin.user_task_extra_extension'] ?? $container->load('getAppAdmin_UserTaskExtraExtensionService')));
            $instance->initialize();

            return $instance;
        };

        return $container->factories['service_container']['app_admin.widget_template']($container);
    }
}

Take a look at line:

            $instance->setWidgetTypes(new RewindableGenerator(function () use ($containerRef) {

$containerRef this variable exists, but the code is located in into anonymous function where origin variable $containerRef is not accessible.

Possible Solution

I'm not sure and not have enough knowledge about internal DI matters. I suppose PhpDumper have an issue and needs to rewrite:

from:


                    $code = [];
                    $code[] = 'new RewindableGenerator(function () use ($containerRef) {';
                    $code[] = '            $container = $containerRef->get();';
                    $code[] = '';

to:


                    $code = [];
                    $code[] = 'new RewindableGenerator(function () use ($container) {';
                    $code[] = '';

But will it always work?

Additional Context

No response

@nicolas-grekas
Copy link
Member

Here is the fix I think:

--- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
@@ -945,8 +945,9 @@ EOF;
             if (!$isProxyCandidate && !$definition->isShared()) {
                 $c = implode("\n", array_map(fn ($line) => $line ? '    '.$line : $line, explode("\n", $c)));
                 $lazyloadInitialization = $definition->isLazy() ? ', $lazyLoad = true' : '';
+                $useContainerRef = $this->addContainerRef ? ' use ($containerRef)' : '';
 
-                $c = sprintf("        %s = function (\$container%s) {\n%s        };\n\n        return %1\$s(\$container);\n", $factory, $lazyloadInitialization, $c);
+                $c = sprintf("        %s = function (\$container%s)%s {\n%s        };\n\n        return %1\$s(\$container);\n", $factory, $lazyloadInitialization, $useContainerRef, $c);
             }
 
             $code .= $c;

Can you come up with a test case? If yes, can you submit a PR with the fix + the test case 🙏 ?

@marphi
Copy link
Contributor Author
marphi commented May 7, 2023

I will try. It will be a pleasure.

@marphi
Copy link
Contributor Author
marphi commented May 12, 2023

Hi @nicolas-grekas, just letting you know that the PR is ready for your review. Let me know if you have any concerns or suggestions.

@fabpot fabpot closed this as completed May 12, 2023
fabpot added a commit that referenced this issue May 12, 2023
…h TaggedIteratorArgument (marphi)

This PR was squashed before being merged into the 6.3 branch.

Discussion
----------

[DependencyInjection] Fix dumping non-shared factories with TaggedIteratorArgument

Fix dumping non-shared factories which have a configured `methodCall` with `TaggedIteratorArgument`.

| Q             | A
| ------------- | ---
| Branch?       | 6.3
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | Fix #50257
| License       | MIT

I've updated my project to the fresh 6.3-beta versions and discovered that DI generates an invalid code. I've reported issue #50257, where you can find more info and context.

The PHPDumper class generated code where use the `$containerRef` variable located in into anonymous function where this variable is not accessible.

`@nicolas`-grekas quickly hinted to me where probably the bug is located. It works! I've prepared PR with this fix, and a PHPUnit test covers this case.

Before the fix, my test case generated this code:
```
    protected static function getFooService($container)
    {
        $containerRef = $container->ref;

        $container->factories['foo'] = function ($container) {
            $instance = new \Symfony\Component\DependencyInjection\Tests\Dumper\FooClass();

            $instance->setOtherInstances(new RewindableGenerator(function () use ($containerRef) {
                $container = $containerRef->get();

                yield 0 => ($container->privates['Bar'] ??= new \Bar());
                yield 1 => ($container->privates['stdClass'] ??= new \stdClass());
            }, 2));

            return $instance;
        };

        return $container->factories['foo']($container);
    }
```
Which threw an error: `Warning: Undefined variable $containerRef` at this line:
```
            $instance->setOtherInstances(new RewindableGenerator(function () use ($containerRef) {
```

After the fix, looks good and works.
```
    protected static function getFooService($container)
    {
        $containerRef = $container->ref;

        $container->factories['foo'] = function ($container) use ($containerRef) {
            $instance = new \Symfony\Component\DependencyInjection\Tests\Dumper\FooClass();

            $instance->setOtherInstances(new RewindableGenerator(function () use ($containerRef) {
                $container = $containerRef->get();

                yield 0 => ($container->privates['Bar'] ??= new \Bar());
                yield 1 => ($container->privates['stdClass'] ??= new \stdClass());
            }, 2));

            return $instance;
        };

        return $container->factories['foo']($container);
    }
```

Thx `@nicolas`-grekas for your help!

Commits
-------

a60dfcf [DependencyInjection] Fix dumping non-shared factories with TaggedIteratorArgument
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants
0