humbug / php-scoper

πŸ”¨ Prefixes all PHP namespaces in a file/directory to isolate the code bundled in PHARs.
MIT License
696 stars 76 forks source link

[Scoper] Improve the WP support experience #303

Closed wujekbogdan closed 10 months ago

wujekbogdan commented 5 years ago

Edit: @matzeeable has created WP React Starter which helps with this. However a cleaner extension point has been found and could help out as well outside of WP. Check https://github.com/humbug/php-scoper/issues/303#issuecomment-614097867 for more details


the whitelist-global-functions setting doesn't seem to work... or maybe I don't understand how it works. I'm trying to prefix a WordPress plugin. The whitelist-global-functions setting is set to true but global functions are prefixed anyway.

For example this:

add_action('admin_print_scripts', [$this, 'admin_enqueue_scripts']);

Becames this:

\_PhpScoper5c6c3100152e6\add_action('admin_print_scripts', [$this, 'admin_enqueue_scripts']);
theofidry commented 5 years ago

Yes it is expected, you find more details here. So the function is still scoped, but you should find a statement in scoper-autoload.php aliasing the scoped function to the original one

vielhuber commented 4 years ago

Is including vendor/scoper-autoload.php a required step when using php-scoper? I've seen it nowhere in the documentation and wonder how I should do it in the original file (with file_exists?)

vielhuber commented 4 years ago

Another question on this: It seems that in this case it's the other way around.

test1.php (that is outside of php-scoper):

<?php
function update_option()
{
    echo 'FOO';
}
require_once 'test2.php';

test2.php (that php-scoper changed):

<?php
namespace _PhpScoper282d0d739942;
require_once __DIR__ . '/vendor/scoper-autoload.php';
\_PhpScoper282d0d739942\update_option();

scoper-autoload.php

if (!function_exists('update_option')) {
    function update_option() {
        return \_PhpScoper282d0d739942\update_option(...func_get_args());
    }
}

The problem is, that if I run test1.php, I get "Call to undefined function _PhpScoper282d0d739942\update_option()", because you alias the update_options function (just the other way round).

What is the normal way to solve this? One straightforward way is to simply tell php-scoper to not prefix those wordpress native functions, but I haven't found a way to do that.

theofidry commented 4 years ago

Including the scoper-autoload file should be mentioned in the doc.

Regarding your second question it looks to me that you are not using Composer. The typical happy path is: require the Composer auto loader and then the PHP-Scoper aliases. Function declarations are typically registered as static files in Composer which are unconditionally all required (unlike regular files which go through the classmap and are lazily loaded).

But in any case it’s still possible to make it work without Composer, but the documentation may be a bit lacking for it, it would be a nice addition

On Wed 5 Feb 2020 at 16:52, David Vielhuber notifications@github.com wrote:

Another question on this: It seems that in this case it's the other way around.

test1.php (that is outside of php-scoper):

<?php function update_option() { echo 'FOO'; } require_once 'test2.php';

test2.php (that php-scoper changed):

<?php namespace _PhpScoper282d0d739942; require_once DIR . '/vendor/scoper-autoload.php'; _PhpScoper282d0d739942\update_option();

scoper-autoload.php

if (!function_exists('update_option')) { function update_option() { return _PhpScoper282d0d739942\update_option(...func_get_args()); } }

The problem is, that if I run test1.php, I get "Call to undefined function _PhpScoper282d0d739942\update_option()", because you alias the update_options function (just the other way round).

β€” You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/humbug/php-scoper/issues/303?email_source=notifications&email_token=ABHPVAJQJUOGL3OLT77JWYDRBLOFHA5CNFSM4GYNSW3KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEK35TVY#issuecomment-582474199, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABHPVAJ22MN6BBXEMZVCFFDRBLOFHANCNFSM4GYNSW3A .

vielhuber commented 4 years ago

Regarding your second question it looks to me that you are not using Composer. The typical happy path is: require the Composer auto loader and then the PHP-Scoper aliases. Function declarations are typically registered as static files in Composer which are unconditionally all required (unlike regular files which go through the classmap and are lazily loaded).

I'm using composer, I just stripped out the libraries in the demo example. The problem is, that my code mixes composer classes with native wordpress functions, which are not in any namespace and don't use composer.

I just managed to get it somewhat working by adding:

<?php
declare(strict_types=1);
return [
    'whitelist' => ['*'] // exclude native wordpress global functions
];

The native functions are not prefixed anymore. The problem here is that all other parts are also not prefixed anymore.

theofidry commented 4 years ago

I would need to sit in front of the computer to read it again to be able to tell: I’ll try to do so next week once I’m back

On Wed 5 Feb 2020 at 17:09, David Vielhuber notifications@github.com wrote:

Regarding your second question it looks to me that you are not using Composer. The typical happy path is: require the Composer auto loader and then the PHP-Scoper aliases. Function declarations are typically registered as static files in Composer which are unconditionally all required (unlike regular files which go through the classmap and are lazily loaded).

I'm using composer, I just stripped out the libraries in the demo example. The problem is, that my code mixes composer classes with native wordpress functions, which are not in any namespace and don't use composer.

I just managed to get it somewhat working by adding:

<?php declare(strict_types=1); return [ 'whitelist' => ['*'] // exclude native wordpress global functions ];

Is this the correct way to do it? The native functions are not prefixed anymore.

β€” You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/humbug/php-scoper/issues/303?email_source=notifications&email_token=ABHPVAOAGRKXVKI3IRXXD5DRBLQFFA5CNFSM4GYNSW3KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEK37VCY#issuecomment-582482571, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABHPVAPY6KNA2Q6AWD4QU4DRBLQFFANCNFSM4GYNSW3A .

vielhuber commented 4 years ago

Thanks in advance, I will fiddle around in the meantime.

vielhuber commented 4 years ago

I fixed this with:

rustygoldcoin commented 4 years ago

I agree with @theofidry on this one. PHP-Scoper is doing its job here. It is definitely an antipattern to bring in global dependencies into a library you are trying to scope. By using PHP-Scoper, you are agreeing that you want to bundle your code in such a way that it can operate in its own environment without interference from the global context. Everything function/class you depend on should be somewhere defined within the code itself and not in an external library.

So PHP-Scoper is working just like it was designed. With that being said, there are many of us that would like to use PHP-Scoper to isolate dependencies for our WP plugins. Which requires us to depend on global functions such as \add_action() for the plugins to work.

I have a solution and am trying to package it up so that others can use it as well for this problem. I have a patch that will be pulled in as a separate Composer dependency. When it is pulled into your project, it should just work without any extra configuration. I'm working on getting it out this week and hope to have something done by 2/21/2020.

rustygoldcoin commented 4 years ago

You guys are welcome to try out the solution I developed.

https://github.com/ua1-labs/php-scoper-globals

Let me know if you guys need anything fixed. This Repo is hot off the presses and I haven't tested the code too much. But think I got everything we need to at least bring functions into scope.

vielhuber commented 4 years ago

Wow, you address exactly the issue, thanks for your work.

May I ask: Is this possible without extra configuration? I use a lot of WordPress functions overall in my plugins and don't want to add every single WP function to the configuration file.

rustygoldcoin commented 4 years ago

So the way that this works means you have to predefine all of the functions you are using in your plugin. I started to create https://github.com/ua1-labs/php-scoper-wp-patch/ but realized that it is going to be very resource intensive. If you'd like to take over the repo, I'll gladly let you take it over, but its not something I'm going to continue to support. Rather, I'm going to keep building out php-scoper-globals.

theofidry commented 4 years ago

Thanks @joshualjohnson, I get a much better understanding of the issue now and it's an interesting solution.

A possible alternative I just though of is to enrich the Reflector. Indeed the reflector is what is saying is an internal symbol or not, and internal symbols are not prefixed.

So a solution would be to tell PHP-Scoper that all WP symbols are internal. I'm not entirely sure though how it should be achieved. So just a few points as a kind of brainstorming session:

rustygoldcoin commented 4 years ago

@theofidry I understand you might be interested in supporting this feature. However, It's my belief that your php-scoper is doing exactly what it is supposed to. By adding support for registering globals, it's my belief that we would be perpetuating and antipattern. I think leaving php-scoper as is would be just fine and if people need the ability to register globals for applications like WP they are welcome to use ua1-labs/php-scoper-globals. My studio will continue to support PhpScoperGlobals as the solution for registering globals for humbug/php-scoper.

I do not, however, want to support https://github.com/ua1-labs/php-scoper-wp-patch because I believe that it is a major killer of memory and CPUs. During testing, I found that I reached my memory exception many times over because we are registering 1000s of functions for each request. So I'm going to probably deleting that repo and removing it from Packagist. I was asking if @vielhuber wanted to support the repo and I could transfer it over to them before I go and delete it.

vielhuber commented 4 years ago

@joshualjohnson Can you please give me a short readme how to get started with https://github.com/ua1-labs/php-scoper-wp-patch ? I will test and try it out if it works and would be pleased to maintain it in the future.

vielhuber commented 4 years ago

@theofidry: Just for brainstorming: Would it be possible to provide an additional setting like: 'exclude_global_functions_from_dir' => './.../path/to/outside/dir/' php-scoper then scans all global functions inside that directory (where for example WordPress lays) and excludes those functions from being prefixed.

rustygoldcoin commented 4 years ago

Actually @vielhuber it's as simple as pulling in the code using composer require ua1-labs/php-scoper-wp-patch. Then it patches WP for you. https://github.com/ua1-labs/php-scoper-wp-patch/blob/master/UA1Labs/wp-functions.php includes all wp functions that need to get registered using PhpScoperGlobals. https://github.com/ua1-labs/php-scoper-wp-patch/blob/master/UA1Labs/wp-patch.php#L34 adds an autoloader for classes in the global scope.

theofidry commented 4 years ago

@vielhuber adding a setting listing the symbols to consider as internal yes, from a dir no

vielhuber commented 4 years ago

@joshualjohnson

Thanks for the instructions.

1st problem: I add php-scoper with:

require_once __DIR__ . '/vendor/scoper-autoload.php';

Now I get

Fatal error: Cannot redeclare add_submenu_page() (previously declared in ../vendor/scoper-autoload.php:1447) in /wp-admin/includes/plugin.php on line 1345

When I replace scoper-autoload.php with autoload.php, it works (but then other parts don't work!).

2st problem: I get

Fatal error: Uncaught Error: Maximum function nesting level of '10000' reached, aborting! in .../wp-content/plugins/gtbabel/vendor/composer/ClassLoader.php on line 373

When I comment out

    \spl_autoload_register(function ($name) {
        return \_PhpScoperb314e498b17b\UA1Labs\PhpScoperGlobals::registerGlobalClass($name);
    });

it works. Do you have a clue what causes this problem?

rustygoldcoin commented 4 years ago

Yeah, the autoloader that is being registered needs to be updated to account for other logic I did not account for. That is one of the major reasons I don't intend to support the wp-patch solution. I feel it would be too much overhead for a WP plugin and would kill performance.

So with that being said, the issue is that the autoloader registered is trying to load a class that it shouldn't be trying to load. So, you'd have to figure out what class it is trying to load and the reason it is getting caught in a loop.

vielhuber commented 4 years ago

Then I would prefer providing a simple array of functions to phpscoper. @theofidry: Is this already possible with the whitelist-option?

theofidry commented 4 years ago

@vielhuber not right now, but shouldn't be too hard to add one. What's to be done:

vielhuber commented 4 years ago

One question: Why doesn't the following work?

return [
    'whitelist' => [
        'add_submenu_page',
        'admin_print_scripts',
        /* ... */
    ],
];

Did I misunderstand the whitelist-option?

theofidry commented 4 years ago

The PHP-Scoper whitelisting mechanism assumes the whitelisted symbols are autoloaded within the scoped code. So in this case where the code you want to whitelist is from WP, it won't work

vielhuber commented 4 years ago

I managed to get it working with patchers:

$wp_functions = ['__','_admin_notice_multisite_activate_plugins_page','_e','...'];
return [
    'patchers' => [
        function (string $filePath, string $prefix, string $content) use ($wp_functions): string {
            // don't prefix native wp functions
            foreach($wp_functions as $wp_functions__value) {
                $content = str_replace('\\'.$prefix.'\\'.$wp_functions__value.'(', '\\'.$wp_functions__value.'(', $content);
            }
            return $content;
        }
    ]
];

This seems like a reasonable solution, or am I something missing?

rustygoldcoin commented 4 years ago

Yeah, that seems to be a fine fix. That is the exact reasons patchers were created.

theofidry commented 4 years ago

Is there a way to get all the WP functions easily?

rustygoldcoin commented 4 years ago

Not an easy way, but you could copy them from https://developer.wordpress.org/reference/functions/

vielhuber commented 4 years ago

No way I know. I used the list on https://codex.wordpress.org/Function_Reference, semiautomatically parsed it and added important classes like WP_Query to it.

vielhuber commented 4 years ago

Be careful: On https://codex.wordpress.org/Function_Reference several functions are missing (e.g. get_home_url()).

Alternatives:

rustygoldcoin commented 4 years ago

Thanks @vielhuber for the input. I've updated my library to include your findings you posted here. https://github.com/ua1-labs/php-scoper-globals/blob/1.1.0/patchers/globalIncludes.php

To use first include this file into your scoper.inc.php

require_once(__DIR__ . '/vendor/ua1-labs/php-scoper-globals/patchers/globalIncludes.php');

Now in your patchers config, include the following configuration

    'patchers' => [
        globalIncludes(['__', 'do_action'])
    ],

@vielhuber If you'd like, we can create a wpGlobalIncludes() that will simply return the patcher preconfigured with all wp functions/classes/constants.

Let me know.

vielhuber commented 4 years ago

Hello @joshuajohnson,

that sounds good.

I've found in the meantime the following functions, classes and constants, that were missing (perhaps you already included them):

'WP_Query', 'WP_Term_Query', 'WP_Widget', 'WP_PLUGIN_DIR', 'url_to_postid',

Then I adjusted the patcher so that also classes and function_exists('') works (perhaps you can update your code):

$content = str_replace('\\'.$prefix.'\\'.$include, '\\'.$include, $content); // "\PREFIX\foo()", or "foo extends nativeClass"
$content = str_replace($prefix.'\\\\'.$include, $include, $content); // "if( function_exists('PREFIX\\foo') )"
matzeeable commented 4 years ago

I also try to scope my WordPress plugins. I want to implement PHP-Scoper to WP React Starter but I come across this thread because I encounter the same issue. Just an idea: Would it be possible to whiltelist from a stub file like this one: https://github.com/php-stubs/wordpress-stubs ?

theofidry commented 4 years ago

Yes it should be

matzeeable commented 4 years ago

Proposel: Allow an option whitelist_stubs that allows to pass an array of file pathes, they get parsed with e. g. https://github.com/nikic/PHP-Parser and all kind of classes, function and so on automatically are passed to whitelist. This allows for example to whitelist WooCommerce stubs, too.

matzeeable commented 4 years ago

For all who read this issue and are interested in working with WordPress and PHP-Scoper, I have successfully implemented this in my open source boilerplate - right now in develop branch, but I will release soon: WP React Starter

@theofidry I thought about to start a PR to enhance the functionality of PHP-Scoper itself. Unfortunately I couldn't go that way because I needed more special logic due to the fact I am working in a monorepo. You can find my implementation in this commit: https://github.com/devowlio/wp-react-starter/commit/c9513b3819cb0e05fe55a1bd02569fd4016a54b1

Especially the following files could be of interest for you:

common/Gruntfile.plugin.ts # task "php:scope"
common/php-scope-stub.ts # extract all global classes, function, interfaces and traits from given files
common/php-scoper.php # PHP-Scoper configuration file with whitelisting and custom patcher

Technically said, it creates a whitelist for all available stubs and found plugins in our monorepo. It is a combination of JavaScript and PHP, but I think the logic is more important, it can be easily ported to PHP.

matzeeable commented 4 years ago

Hi again all!

The last few days I intensively worked on scoping WordPress plugins and found an ultimative way of doing this. I introduced PHP-Scoper to my boilerplate WP React Starter, see also as highlighted feature here:

Scoping your PHP coding and dependencies so they are isolated (avoid dependency version conflicts)

As I have mentioned previously (https://github.com/humbug/php-scoper/issues/303#issuecomment-611371040) we need to consider stubs when we want to scope a WordPress plugin. I have introduced a documentation about this in my boilerplate documentation: read here. Another way would to go the opposite and only namespace a set of vendors, as you can read here: #378, but this is not yet possible.

Generally said, you can now install stubs or create custom stubs and simply put it to package.json#stubs and WP React Starter is doing the rest. πŸŽ‰

@theofidry Perhaps it is worth to put a link to WP React Starter into your README.md as possible solution for WordPress development.

What do you think?

theofidry commented 4 years ago

Perhaps it is worth to put a link to WP React Starter into your README.md as possible solution for WordPress development.

Yes definitely. Feel free to do a PR to add it :)


Thanks a lot for the work :)

I'm glad there is good solutions being proposed and valid existing alternatives. I think the next step now is to make the integration a bit smoother/performant. For this, I think the following could be done:

matzeeable commented 4 years ago

Yes definitely. Feel free to do a PR to add it :)

@theofidry Yeah, I have added it to my todo list, thank you! πŸ™‚

[...] to PHP-Scoper as a dependency

Please do not add the stubs as dependency as PHP-Scoper should not contain stubs from third-parties. PHP-Scoper should be that configurable, so you can pass PHP files - it doesn't even have to be stubs (https://github.com/humbug/php-scoper/issues/303#issuecomment-611395657)

Add a map to https://github.com/php-stubs/wordpress-stubs the same way it is done for the PhpStorm stubs, see:

That is also not necessary, for the first. Please have a look at my implementation: https://github.com/devowlio/wp-react-starter/blob/master/common/php-scope-stub.ts

  1. A function which accepts an array of file pathes to stubs (as mentioned above, it does not need to be stubs, it can also be plain code)
  2. The function reads all the content of the passed files and passes the content to an AST conversation tool. In my case I am using a node implementation php-parser - in your case it should be glyazzle/php-parser.
  3. The AST gets recursively read and all available classes, functions, interfaces and traits are put into a string array

See #303 (comment) about adding an extension point for specifying what should be considered internal or not: this is not specifically about WP;

This is correct, you need to allow to specify a whitelist of identifiers (functions, classes, ...).

theofidry commented 4 years ago

That is also not necessary, for the first. Please have a look at my implementation:

No indeed it's not a necessity. But it's by far way more performant and don't require extra tooling :) Hence a nice to have but not a necessity

dugajean commented 4 years ago

@theofidry Any news on this? Is there a way I can support development here?

mundschenk-at commented 4 years ago

I'd also like to help - I'm currently "manually" patching used WordPress functions in my plugins build process, but this is of course very error prone.

theofidry commented 4 years ago

Hi @mundschenk-at :) If you're willing to help I believe there is a few actionable points listed here

pxlrbt commented 3 years ago

@theofidry Based on @matzeeable solution I implemented a version of the stub extractor and patcher in PHP which probably is easier to integrate into PHP-Scoper: https://github.com/pxlrbt/php-scoper-prefix-remover

If you see a way how we could add this to PHP-Scoper let me know.

XedinUnknown commented 3 years ago

Of course, the problem is that if I require-dev this stuff, then I won't have any access to it during the build, because the build, naturally, installs non-dev dependencies: dev dependencies are not required in the final product. Which is why it is such a great thing that one could install PHPScoper with Phive: it doesn't need to be a code dependency. If the other supplementary tools cannot be installed that way too, then I'm not sure how they could be used without implementing super hacky workarounds with declaring a dependency on something that is not needed for production.

XedinUnknown commented 3 years ago

Correct me if I'm wrong, though: does this issue stem from the fact that WordPress is not available to PHPScoper during prefixing? Because I was puzzled as to why the global WP functions are getting prefixed when whitelist-global-functions etc is on. Would it help if the functions were explicitly used from the global namespace in the code, e.g. \add_action('init', function() {})? Or perhaps if they are imported with use function add_action?

pxlrbt commented 3 years ago

Of course, the problem is that if I require-dev this stuff, then I won't have any access to it during the build, because the build, naturally, installs non-dev dependencies: dev dependencies are not required in the final product. Which is why it is such a great thing that one could install PHPScoper with Phive: it doesn't need to be a code dependency.

In this case you can download that part manually. Or only composer require that package during your build. There are options. I'd also prefer an official way or integrating this into PHP-Scoper.

Correct me if I'm wrong, though: does this issue stem from the fact that WordPress is not available to PHPScoper during prefixing

Not sure what's causing this as I have no knowledge about PHP-Scoper internals.

Would it help if the functions were explicitly used from the global namespace in the code, e.g. \add_action('init', function() {})? Or perhaps if they are imported with use function add_action?

Tried it both and it didn't work. Either the function statement or the use function statement will be prefixed.

XedinUnknown commented 3 years ago

I settled for downloading the file with Gulp during build. However, with the php-scoper-prefix-remover things aren't so simple, because it's a pacakge.

What I would do if I wanted to integrate this into PHP Scoper:

  1. Merge IdentifierExtractor into this project. Maybe needs a bit of polishing, but overall it would be great to have this functionality in PHP Scoper itself.
  2. Have users get the stubs however they want. For example, they could download the stub file during build, like I did.
  3. Include an example of a WP patcher that uses the above extractor in conjunction with the stub file. Just put it into the readme. Something along the lines of:

    <?php declare(strict_types=1);
    
    // scoper.inc.php
    
    use IdentifierExtractor;
    
    $identifiers = (new IdentifierExtractor())
        ->addStub('stub-file.php')
        ->extract();
    
    return [
        'patchers' => [
            function (string $filePath, string $prefix, string $content) use ($identifiers): string {
                $prefixDoubleSlashed = str_replace('\\', '\\\\', $prefix);
                $quotes = ['\'', '"', '`'];
    
                foreach ($identifiers as $identifier) {
                    $identifierDoubleSlashed = str_replace('\\', '\\\\', $identifier);
                    $content = str_replace($prefix . '\\' . $identifier, $identifier, $content); // "PREFIX\foo()", or "foo extends nativeClass"
    
                    // Replace in strings, e. g.  "if( function_exists('PREFIX\\foo') )"
                    foreach ($quotes as $quote) {
                        $content = str_replace(
                            $quote . $prefixDoubleSlashed . '\\\\' . $identifierDoubleSlashed . $quote,
                            $quote . $identifierDoubleSlashed . $quote,
                            $content
                        );
                    }
                }
            },
        ],
    ];

My reasoning is that the need to extract identifiers from arbitrary PHP code is generic, while the code itself and the WP patcher are specific to the user's project, and to WP, respectively.

pxlrbt commented 3 years ago

However, with the php-scoper-prefix-remover things aren't so simple, because it's a pacakge.

Sure. I see it as a temporary fix until we solved that issue within PHP-Scoper.

Merge IdentifierExtractor into this project. Maybe needs a bit of polishing, but overall it would be great to have this functionality in PHP Scoper itself.

Happy for any feedback for improvements. I see whether I can create a PR for this.

Have users get the stubs however they want. For example, they could download the stub file during build, like I did.

Isn't that the current state? I downloaded them via composer but you could also just add them to your project and reference that file.

My reasoning is that the need to extract identifiers from arbitrary PHP code is generic, while the code itself and the WP patcher are specific to the user's project, and to WP, respectively.

Do you have any examples that also need that identifier extraction but have nothing to do with removing that prefix? I think it makes it easier for users to just include that specific patcher from PHP-Scoper but I also understand that this could lead to more maintenance work.

There's also a SymfonyPatcher in the core of PHP-Scoper. Maybe it's useful to add some more common patchers.

XedinUnknown commented 3 years ago

However, with the php-scoper-prefix-remover things aren't so simple, because it's a pacakge.

Sure. I see it as a temporary fix until we solved that issue within PHP-Scoper.

Yep

Merge IdentifierExtractor into this project. Maybe needs a bit of polishing, but overall it would be great to have this functionality in PHP Scoper itself.

Happy for any feedback for improvements. I see whether I can create a PR for this.

I don't have concrete suggestions right now, but I thought that maybe PHP Scoper uses some DI container or something else that could be used to, for example, inject the parser factory into the extractor.

Have users get the stubs however they want. For example, they could download the stub file during build, like I did.

Isn't that the current state? I downloaded them via composer but you could also just add them to your project and reference that file.

Yes, it is. I basically wrote how I see things eventually. So, I think that the way things are with regard to obtaining the stubs file is good already.

My reasoning is that the need to extract identifiers from arbitrary PHP code is generic, while the code itself and the WP patcher are specific to the user's project, and to WP, respectively.

Do you have any examples that also need that identifier extraction but have nothing to do with removing that prefix? I think it makes it easier for users to just include that specific patcher from PHP-Scoper but I also understand that this could lead to more maintenance work.

There's also a SymfonyPatcher in the core of PHP-Scoper. Maybe it's useful to add some more common patchers.

I don't have examples. But what matters to me is separation of concerns. There can be countless cases in the future which for now we have not thought about. The only thing we know right now is that extracting identifiers is a generic task used during scoping, and that patching WP code is a usecase-specific task.

Of course, removing the prefix for any set of arbitrary identifiers is a generic task that is useful during scoping, so if the patcher is not WP-specific, it too can be included in PHP Scoper, no problem πŸ™‚