humbug / php-scoper

🔨 Prefixes all PHP namespaces in a file/directory to isolate the code bundled in PHARs.
MIT License
711 stars 76 forks source link

Fatal error: Uncaught Error: Interface 'Stringable' not found in #1010

Open reinos opened 7 months ago

reinos commented 7 months ago

Bug report

Question Answer
PHP-Scoper version master branch due to this possible fix
PHP version 8.2
Platform with version MacOS

After running php-scoper it seems that some interfaces are no scoped.

See the source of file /vendor/symfony/polyfill-php80/PhpToken.php

namespace Symfony\Polyfill\Php80;

class PhpToken implements \Stringable
{
....
}

and file vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php

if (\PHP_VERSION_ID < 80000) {
    interface Stringable
    {
        /**
         * @return string
         */
        public function __toString();
    }
}

are scoped like

namespace ReinosMapsScoped\Symfony\Polyfill\Php80;

class PhpToken implements \Stringable <!-- NOTICE THIS, its not scoped
{
...
}
if (\PHP_VERSION_ID < 80000) {
    interface Stringable
    {
        /**
         * @return string
         */
        public function __toString();
    }
    \class_alias('ReinosMapsScoped\\Stringable', 'Stringable', \false);
}

The issue seems that class PhpToken implements \Stringable is not scoped to class PhpToken implements \ ReinosMapsScoped\Stringable

Now it throws an PHP error Fatal error: Uncaught Error: Interface 'Stringable' not found in..... and with adding manually the scoped namespace it fixed the error.

Is this a bug in PHPscoper or a configuration issue? 🤔

theofidry commented 7 months ago

It you're using the latest tagged version it is a known issue, if you're using the master branch however it should be fixed.

You notice you have a class alias statement added:

    \class_alias('ReinosMapsScoped\\Stringable', 'Stringable', \false);

You should also have a corresponding class_exists() statement in your scoper-autoload.php so \Stingable should be a registered symbol by the time PhpToken is loaded.

If you still have the problem on master please provide a reproducer so that I can look into it

reinos commented 6 months ago

Hi @theofidry, thanks for your reply!

So I did some digging and I found the issue.

The file scoper-autoload.php generate this for me.

humbug_phpscoper_expose_class('Reinos_maps_upd', 'ReinosMapsScoped\Reinos_maps_upd');
humbug_phpscoper_expose_class('Reinos_maps', 'ReinosMapsScoped\Reinos_maps');
humbug_phpscoper_expose_class('Reinos_maps_ext', 'ReinosMapsScoped\Reinos_maps_ext');
humbug_phpscoper_expose_class('Reinos_maps_ACT', 'ReinosMapsScoped\Reinos_maps_ACT');
humbug_phpscoper_expose_class('Reinos_maps_mcp', 'ReinosMapsScoped\Reinos_maps_mcp');
humbug_phpscoper_expose_class('ComposerAutoloaderInitc47e9d714b1dba70cb6cf7603e380961', 'ReinosMapsScoped\ComposerAutoloaderInitc47e9d714b1dba70cb6cf7603e380961');
humbug_phpscoper_expose_class('GooglePolylineBadInput', 'ReinosMapsScoped\GooglePolylineBadInput');
humbug_phpscoper_expose_class('GoogleTraitBadInputTest', 'ReinosMapsScoped\GoogleTraitBadInputTest');
humbug_phpscoper_expose_class('GooglePolylineHacking', 'ReinosMapsScoped\GooglePolylineHacking');
humbug_phpscoper_expose_class('GoogleTraitHackingTest', 'ReinosMapsScoped\GoogleTraitHackingTest');
humbug_phpscoper_expose_class('JsonException', 'ReinosMapsScoped\JsonException');
humbug_phpscoper_expose_class('PhpToken', 'ReinosMapsScoped\PhpToken');
humbug_phpscoper_expose_class('ValueError', 'ReinosMapsScoped\ValueError');
humbug_phpscoper_expose_class('Attribute', 'ReinosMapsScoped\Attribute');
humbug_phpscoper_expose_class('UnhandledMatchError', 'ReinosMapsScoped\UnhandledMatchError');
humbug_phpscoper_expose_class('Stringable', 'ReinosMapsScoped\Stringable');
humbug_phpscoper_expose_class('Normalizer', 'ReinosMapsScoped\Normalizer');

Indeed, Stringable is there and should be registered. However, the class PhpToken is called first and that class implements the Stringable interface. So at that time when PhpToken is registering, \Stringable is not yet created as alias.

By moving Stringable just before the PhpToken fixed the issue.

humbug_phpscoper_expose_class('Stringable', 'ReinosMapsScoped\Stringable');
humbug_phpscoper_expose_class('PhpToken', 'ReinosMapsScoped\PhpToken');

Any idea how we can fix this?

theofidry commented 6 months ago

I see, that's an annoying one... We could fix it for this specific case for now. Fixing this more generally looks a bit tricky, doable but tricky.

incraigulous commented 6 months ago

@theofidry , do you know of a workaround we could use until this can be fixed? I can confirm that it works if I reorder my exposed classes to this order:

humbug_phpscoper_expose_class( 'Stringable', 'CodeZone\Bible\Stringable' ); humbug_phpscoper_expose_class( 'PhpToken', 'CodeZone\Bible\PhpToken' );

incraigulous commented 6 months ago

@theofidry , what about a setting to allow for setting a priority of exposed classes?

'exposed-class-priority' => [
     'Stringable' => 0,
     'PhpToken' => 1
]

Just an idea. I'm sure there is a more elegant solution.

theofidry commented 6 months ago

I'm working on a PoC for it. In terms of effort having another setting is not exactly trivial either, so might as well try to fix it the proper way. I don't think it is that hard in the end, but I was on holiday so didn't get that much time to look into it, I hope to fix this within the week or next week though.

incraigulous commented 6 months ago

:pray: Thank you @theofidry!

theofidry commented 6 months ago

Ok I'll need some help here... Could you guys provide a super minimal reproducer?

I identified the source code changes that I need to do, it's not a crazy amount but for the tests it's another story. So I've been trying to implement an e2e test instead to validate my idea first before fixing all the other tests... And I can't make it work.

I did my changes in #1035. There is some source code changes but they actually have no impact yet (I'm tracking the dependencies of the class when aliasing a class, but I do nothing with them for now). The end to end test is in the fixtures/set041-exposed-symbols-hierarchy directory and executed with make e2e_041.

I've tried various things, but I cannot reproduce the issue you describe. I've even added a polyfill-80 structure like to try to reproduce it without having to run an old PHP version but this does not do the trick: for me the StringeableLike interface (which would be the Stringeable of your case) is present in the loader classmap (because it is registered in classmap in the composer.json).

I also notice that PhpToken is namespaced, so I don't get why yours is exposed this way.

reinos commented 1 month ago

Hi @theofidry Its been a while. Sorry for taking sooo long. However, I have no idea how to send you a minimal reproducer. Ideas? I can send you my project that is causing the issue though. Perhaps that is helpful?

let me know.

theofidry commented 1 month ago

No worries, if you can't publish the project maybe you can send a zip with the steps to reproduce it?

reinos commented 1 month ago

No worries, if you can't publish the project maybe you can send a zip with the steps to reproduce it?

How can I send you the zip file? Can we get in contact with each other?

theofidry commented 1 month ago

You can send them either here at theo.fidry @ gmail.com (and write here when you sent it, as it will likely end up in spam)

reinos commented 1 month ago

I send you the project, I hope you can find what the cause of the issue is in this case 🤞🏻

Keep me posted!

theofidry commented 4 weeks ago

Thanks, received

reinos commented 4 weeks ago

Cool, let me know if you have any questions.

reinos commented 16 hours ago

Hi @theofidry

Any luck on reproducing/fixing this issue?

theofidry commented 12 hours ago

Sorry couldnt take a look, got a bit busy with my moving. I saved the project you sent me by email, I hope to be able to take a look at it this week