Closed wujekbogdan closed 10 months 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
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?)
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.
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 .
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.
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 .
Thanks in advance, I will fiddle around in the meantime.
I fixed this with:
MyCustomPlugin
to my WordPress plugin file'whitelist' => ['MyCustomPlugin\*']
to scoper.inc.php to exclude all global functions called in this file.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.
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.
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.
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.
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:
@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.
@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.
@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.
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.
@vielhuber adding a setting listing the symbols to consider as internal yes, from a dir no
@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?
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.
Then I would prefer providing a simple array of functions to phpscoper. @theofidry: Is this already possible with the whitelist-option?
@vielhuber not right now, but shouldn't be too hard to add one. What's to be done:
php-scoper.inc.tpl
template, e.g. internalSymbols
?Configuration
Reflector
Reflector
to use the added optionOne question: Why doesn't the following work?
return [
'whitelist' => [
'add_submenu_page',
'admin_print_scripts',
/* ... */
],
];
Did I misunderstand the whitelist-option?
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
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?
Yeah, that seems to be a fine fix. That is the exact reasons patchers were created.
Is there a way to get all the WP functions easily?
Not an easy way, but you could copy them from https://developer.wordpress.org/reference/functions/
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.
Be careful: On https://codex.wordpress.org/Function_Reference several functions are missing (e.g. get_home_url()).
Alternatives:
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.
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') )"
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 ?
Yes it should be
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.
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.
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?
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:
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
php-parser
- in your case it should be glyazzle/php-parser
.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, ...).
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
@theofidry Any news on this? Is there a way I can support development here?
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.
Hi @mundschenk-at :) If you're willing to help I believe there is a few actionable points listed here
@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.
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.
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
?
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.
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:
IdentifierExtractor
into this project. Maybe needs a bit of polishing, but overall it would be great to have this functionality in PHP Scoper itself.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.
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.
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 π
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. Thewhitelist-global-functions
setting is set totrue
but global functions are prefixed anyway.For example this:
Becames this: