php-scoper
php-scoper copied to clipboard
How to use this to prefix a WordPress plugin?
Howdy!
I tried to use PHP-Scoper to prefix a WordPress plugin but couldn't. I saw the issue https://github.com/humbug/php-scoper/issues/303 and the PR https://github.com/humbug/php-scoper/pull/433, I've read through a fair amount of comments and tried to get hands down with it myself.
I will list the steps I took to scope my WordPress plugin vendor
folder, and the results that I've got:
Approach 1, per-dependency scope:
File: scoper-di52.php
php /var/www/tests/php-scoper/php-scoper.phar add-prefix --output-dir=/var/www/single/wp-content/plugins/wp-staging-dev/vendor_prefixed/lucatume/di52 --config=/var/www/tests/php-scoper/scoper-di52.php
Result: The prefixed di52 package in vendor_prefixed/lucatume/di52
, as is expected. But there are a few issues with this approach:
- The prefixed library is outside the Composer Autoloader, which means I would have to autoload it in a different way?
- The original, unprefixed library, is still being loaded on the Composer Autoloader
Approach 2, scope the entire vendor
folder:
File: scoper.php
php /var/www/tests/php-scoper/php-scoper.phar add-prefix --output-dir=/var/www/single/wp-content/plugins/wp-staging-dev/vendor_prefixed --config=/var/www/tests/php-scoper/scoper.php
Result: All vendor
dependencies prefixed in the vendor_prefixed
folder, including Composer itself. This should work, but I had to add this to the scoper.php
file, which raises an eyebrown for other exceptions that I might be missing and find out it can cause a fatal down the road:
'patchers' => [
function (string $filePath, string $prefix, string $contents): string {
// Change the contents here.
if ($filePath === '/var/www/single/wp-content/plugins/wp-staging-dev/vendor/composer/autoload_real.php') {
$contents = str_replace('\'Composer\\\\Autoload\\\\ClassLoader\'', '\'MyPlugin\\\\Vendor\\\\Composer\\\\Autoload\\\\ClassLoader\'', $contents);
$contents = str_replace('\'Composer\\Autoload\\ClassLoader\'', '\'MyPlugin\\Vendor\\Composer\\Autoload\\ClassLoader\'', $contents);
}
return $contents;
},
],
Patching is somewhat expected, but the fact that I had to do it in Composer Autoloader worries me, as the autoloader seems fragile as it's auto-generated.
With approach 1, I have added this to my composer.json so it can find the prefixed classes:
"autoload": {
"psr-4": {
"MyPlugin\\": "",
"MyPlugin\\Vendor\\": "vendor_prefixed"
}
}
I'm not sure if this would work well for attempt 2.
Am I taking the right approach to scope this WordPress plugin? Is there a third approach I should consider?
Thanks!
I see that Yoast and Google Site Kit uses PHP Scoper to prefix their WordPress plugins.
Both implementations seem to go through a fair amount of effort to implement their own autoloading solutions for the prefixed vendor classes.
Yoast
Prefixes the vendor classes, removes the unprefixed ones from Composer Autoloader using bash automation, then implement a spl_autoload_register
to load the prefixed classes.
Google Site Kit
Takes a similar approach, implementing their own spl_autoload_register
autoloader, and it seems it drops Composer autoloader entirely.
WooCommerce
Can PHP Scoper work with Composer autoloader? Would you recommend using PHP Scoper on a WordPress plugin?
I ended up figuring it out.
This approach is similar to Google Site Kit, generating a class map to avoid Composer doing file_exists
checks on the filesystem.
The scoping process should happen as part of a build process of a distributable version of the plugin. not for development. Understand why here. (This approach uses the Optimization Level 1: Class map generation)
This is not mean to be a copy-and-work solution. In this example I use Docker to have predictable paths, you might need to adjust those to your reality. I also use the Phar version of PHP-Scoper.
Makefile:
php-scoper-docker:
# Check run as root
if ! [ "$(shell id -u)" = 0 ] ; then echo "This command MUST run as ROOT"; exit 1 ;fi
# Check run inside Docker Container
test -f /.dockerenv || { echo "This command MUST run inside the DOCKER PHP Container"; exit 1; }
# Cleanup
rm -rf /var/www/single/wp-content/plugins/wp-staging-dev/vendor
rm -rf /var/www/single/wp-content/plugins/wp-staging-dev/vendor_prefixed
# Install Dependencies
composer install --no-dev --prefer-dist --no-scripts --working-dir=/var/www/single/wp-content/plugins/wp-staging-dev
# Prepare prefixed vendor
mkdir /var/www/single/wp-content/plugins/wp-staging-dev/vendor_prefixed
cp /var/www/single/wp-content/plugins/wp-staging-dev/composer.json /var/www/single/wp-content/plugins/wp-staging-dev/vendor_prefixed/composer.json
cp /var/www/single/wp-content/plugins/wp-staging-dev/composer.lock /var/www/single/wp-content/plugins/wp-staging-dev/vendor_prefixed/composer.lock
# Add Prefix
php /var/www/tests/php-scoper/php-scoper.phar add-prefix --output-dir=/var/www/single/wp-content/plugins/wp-staging-dev/vendor_prefixed/vendor --config=/var/www/tests/php-scoper/scoper.php --force
# Generate prefixed vendor classmap
composer dump-autoload --optimize --no-scripts --working-dir=/var/www/single/wp-content/plugins/wp-staging-dev/vendor_prefixed
# Build vendor again without dependencies
cp /var/www/single/wp-content/plugins/wp-staging-dev/composer.json /var/www/single/wp-content/plugins/wp-staging-dev/composer.json.bk
cp /var/www/single/wp-content/plugins/wp-staging-dev/composer.lock /var/www/single/wp-content/plugins/wp-staging-dev/composer.lock.bk
composer show --direct --no-dev --name-only --working-dir=/var/www/single/wp-content/plugins/wp-staging-dev | xargs composer remove --no-scripts --working-dir=/var/www/single/wp-content/plugins/wp-staging-dev
composer install --no-dev --prefer-dist -o --no-scripts --working-dir=/var/www/single/wp-content/plugins/wp-staging-dev
# Revert vendor
mv --force /var/www/single/wp-content/plugins/wp-staging-dev/composer.json.bk /var/www/single/wp-content/plugins/wp-staging-dev/composer.json
mv --force /var/www/single/wp-content/plugins/wp-staging-dev/composer.lock.bk /var/www/single/wp-content/plugins/wp-staging-dev/composer.lock
# Adjust vendor_prefixed paths
rm -f /var/www/single/wp-content/plugins/wp-staging-dev/vendor_prefixed/composer.json
rm -f /var/www/single/wp-content/plugins/wp-staging-dev/vendor_prefixed/composer.lock
mv /var/www/single/wp-content/plugins/wp-staging-dev/vendor_prefixed/vendor/* /var/www/single/wp-content/plugins/wp-staging-dev/vendor_prefixed/
rmdir /var/www/single/wp-content/plugins/wp-staging-dev/vendor_prefixed/vendor
# Permissions fix for files created by root
chown -R www-data:www-data /var/www/single/wp-content/plugins/wp-staging-dev
composer.json (Triggers a Makefile to run inside the Docker container):
"scripts": {
"post-autoload-dump": "docker exec --user root -i wpstaging_php bash -c 'cd /var/www/tests && make php-scoper-docker'"
}
Loading the autoloader in the plugin (Inspired by Google Site Kit):
$class_map = array_merge(
require_once __DIR__ . '/vendor/composer/autoload_classmap.php',
require_once __DIR__ . '/vendor_prefixed/composer/autoload_classmap.php'
);
spl_autoload_register(
function ($class) use ($class_map) {
if (isset($class_map[$class])) {
require_once $class_map[$class];
return true;
}
return null;
},
true,
true
);
'/vendor/composer/autoload_classmap.php'
contains a classmap for the src
code
'/vendor_prefixed/composer/autoload_classmap.php'
contains a classmap for the vendor_prefixed
code
Check the contents of those two files to see if everything looks right to you.
Re-opening this as privately requested for visibility and further discussion around the subject.
Maintainers of this package, please feel free to close this if you see fit.
@Luc45 thanks for opening the issue; there is also #303. There is some ideas I hope to get to it in the coming weeks; It's a good idea to keep this issue as well to come back to it after I think
Since you mentioned Site Kit by Google, you might also want to check out how we've done the prefixing in another Google plugin here: https://github.com/google/web-stories-wp/.
I just published how I scoped my WordPress plugin: https://graphql-api.com/blog/graphql-api-for-wp-is-now-scoped-thanks-to-php-scoper/
There is plenty of answers here so I'll be closing this issue. I am not satisfied with the WP support but I'll keep #303 for this purpose.