brick/varexporter

Exporting closure containing `self` leads to fatal error

Opened this issue · 2 comments

Hey there,

I have the following (reduced) example of our application which uses this component to dump the application config to a filesystem cache:

<?php

use Brick\VarExporter\VarExporter;

require __DIR__ . '/vendor/autoload.php';

$dataProvider = new class {
    public const CONFIGURATION_ENTRY = 'data-provider-specific-config';

    public function getConfig(): array
    {
        return [
            self::CONFIGURATION_ENTRY => [
                'foo' => 'bar',
            ],
            'factories' => [
                'MyService' => static function (array $config): object {
                    $configForMyDataProvider = $config[self::CONFIGURATION_ENTRY];

                    return new class ($configForMyDataProvider['foo']) {

                        public string $foo;

                        public function __construct(string $foo)
                        {
                            $this->foo = $foo;
                        }
                    };
                },
            ],
        ];
    }
};

$config = array_merge(
    [],
    $dataProvider->getConfig(),
    // Other data providers providing stuff
);

$content = "<?php\n" . VarExporter::export(
    $config,
    VarExporter::ADD_RETURN | VarExporter::CLOSURE_SNAPSHOT_USES
);

file_put_contents('cached-config.php', $content);

$configFromFilesystem = require 'cached-config.php';

($configFromFilesystem['factories']['MyService'])($configFromFilesystem);

So the exporter generates this code:

<?php
return [
    'data-provider-specific-config' => [
        'foo' => 'bar'
    ],
    'factories' => [
        'MyService' => static function (array $config) : object {
            $configForMyDataProvider = $config[self::CONFIGURATION_ENTRY];
            return new class($configForMyDataProvider['foo'])
            {
                public string $foo;
                public function __construct(string $foo)
                {
                    $this->foo = $foo;
                }
            };
        }
    ]
];

Overall, no real problem and everything works until the closure is being used to instantiate the service. PHP gives us the follow fatal error:

Fatal error: Uncaught Error: Cannot access self:: when no class scope is active in /project/cached-config.php:8

I wonder if it is possible to convert the self reference to some kind of FQCN so that we do not have to care about using self or not.

I am willing to contribute a fix. Just wanted to raise the issue first.

Feel free to give me some feedback whenever you find some time.
Thanks!

Okay, did my first researches and it seems that this library is not the problem.
Might need to create an issue in nikic/php-parser.

I got feedback from Nikita and it seems that it should be possible somehow from within this tool:
nikic/PHP-Parser#903 (comment)

I will see if I can prepare a patch once I find some time. But as always, there are other things with higher priorities (I ended up fixing the issue by just referencing the class by myself).

So any help would be highly appreciated here.