php-scoper: [Scoper] Scoping autoloaded files result in an autoload conflict

Edit to avoid to summarise the state of this issue.

When scoping a package which has autoloaded files, the scoped files will keep the same hash. As a result if your current application/library is using the scoped package and has files with the same hash, only one file will be loaded which results in undesirable behaviours.

It appears the hash strategy is not something we can change composer/composer#7942 and changing the package name of the scoped packages has undesirable side effects so it is not a solution either.

@jarrodbell https://github.com/humbug/php-scoper/issues/298#issuecomment-678089152 and @ondrejmirtes https://github.com/humbug/php-scoper/issues/298#issuecomment-683425709 made it work by changing it after the composer dump process as part of the scoping build process.

The discussion is carrying from that point to figure out if there is a way PHP-Scoper could fix it by itself without further actions from the user.


Bug report

Question Answer
Box version PHP Scoper version 0.11.4 2018-11-13 08:49:16 UTC
PHP version PHP 7.1.22
Platform with version Ubuntu 16.04.5 LTS
Github Repo https://github.com/mollie/mollie-api-php

We are using php-scoper in our client to generate a release for integrators to be used in for example Wordpress. When it is used there, there are often more packages with Guzzle via composer.

When this happens they will get the exception: Uncaught Error: Call to undefined function GuzzleHttp\choose_handler()

This is because the corresponding guzzlehttp/guzzle/src/functions_include.php is not loaded for the second implementation due to the composer file require hash being the same.

Below there is an example of our package being ran alongside another guzzle package. This is a var_dump inside the composer_real.php where the functions_include is being required if the file hash is not already loaded.

array(3) {
  ["c964ee0ededf28c96ebd9db5099ef910"]=>
  string(99) "{path_to_site}/vendor/composer/../guzzlehttp/promises/src/functions_include.php"
  ["a0edc8309cc5e1d60e3047b5df6b7052"]=>
  string(95) "{path_to_site}/vendor/composer/../guzzlehttp/psr7/src/functions_include.php"
  ["37a3dc5111fe8f707ab4c132ef1dbc62"]=>
  string(97) "{path_to_site}/vendor/composer/../guzzlehttp/guzzle/src/functions_include.php"
}
array(3) {
  ["c964ee0ededf28c96ebd9db5099ef910"]=>
  string(106) "{path_to_site}/guzzle/vendor/composer/../guzzlehttp/promises/src/functions_include.php"
  ["a0edc8309cc5e1d60e3047b5df6b7052"]=>
  string(102) "{path_to_site}/guzzle/vendor/composer/../guzzlehttp/psr7/src/functions_include.php"
  ["37a3dc5111fe8f707ab4c132ef1dbc62"]=>
  string(104) "{path_to_site}k/guzzle/vendor/composer/../guzzlehttp/guzzle/src/functions_include.php"
}

So this causes only the first implementation it’s choose_handler function to be available.

I’m not really sure if this bug should be reported here, but i’m curious to your expert opinion about this. Might it be worth it to also scope the files that outta be required by composer? To avoid some of them not being loaded? As manipulating the hash in above example would fix the problem.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 4
  • Comments: 62 (38 by maintainers)

Commits related to this issue

Most upvoted comments

Understood - however I am not sure how to catch this in scoper - when one is supposed to dump the autoloader after using scoper.

What solved it for me was this simple script"

<?php
/**
 * This helper is needed to "trick" composer autoloader to load the prefixed files
 * Otherwise if owncloud/core contains the same libraries ( i.e. guzzle ) it won't
 * load the files, as the file hash is the same and thus composer would think this was already loaded
 *
 * More information also found here: https://github.com/humbug/php-scoper/issues/298
 */
$scoper_path = './build/scoper/vendor/composer';
$static_loader_path = $scoper_path.'/autoload_static.php';
echo "Fixing $static_loader_path \n";
$static_loader = file_get_contents($static_loader_path);
$static_loader = \preg_replace('/\'([A-Za-z0-9]*?)\' => __DIR__ \. (.*?),/', '\'a$1\' => __DIR__ . $2,', $static_loader);
file_put_contents($static_loader_path, $static_loader);
$files_loader_path = $scoper_path.'/autoload_files.php';
echo "Fixing $files_loader_path \n";
$files_loader = file_get_contents($files_loader_path);
$files_loader = \preg_replace('/\'(.*?)\' => (.*?),/', '\'a$1\' => $2,', $files_loader);
file_put_contents($files_loader_path, $files_loader);

I ran into the same problem recently, and took the advice here to edit the autoload_static.php after the scoping process. I simply bolted it onto my gulp build process with a function like this (which uses the gulp-replace plugin):

// Need to prefix hashes in autoload_static.php to ensure they don't conflict with any other autoloaded packages
// This is because other WordPress plugins might use same packages, scoped differently (or not scoped at all) and get loaded first (or not loaded at all)
// Which could cause our plugin to fail, or their plugins to fail. So we need to ensure each entry has a unique hash by simply adding a prefix.
const composerScopeAutoload = () => {
	return src(["path/to/vendor/composer/autoload_static.php"])
		.pipe(replace(/\'([A-Za-z0-9]*?)\' => __DIR__ \. (.*?),/g, '\'myprefix$1\' => __DIR__ . $2,'))
		.pipe(dest(`dist/vendor/composer`));
};

So as per the discussion in https://github.com/composer/composer/issues/7942, I changed my mind and I think it makes sense for PHP-Scoper to change the names of the scoped packages.

So I guess a solution would be to also change the packages names in composer.json and installed.json to solve the issue, maybe by just appending the prefix to the name.

The concerned code is this one I think: https://github.com/humbug/php-scoper/tree/ddcf428/src/Scoper/Composer

So if we could somehow make it possible to hook into the post autoload dump section, we can run whatever fix we think is necessary + you could provide a utitlity that contains the code above as a standard hook.

This IMO is a very fragile hook. So I’m not too keen to provide one there as in my experience, although users may be warn this is fragile and entirely there responsibility, ultimately there still end up coming to me (not always in a friendly way) about there special case.

However your case, or rather this issue, is much more general and IMO deserves a proper fix. Be it from Composer or within PhpScoper, either way is fine by me.

So if I understand correctly, you alter the autoload_static.php file to change the paths?

I’m here with the same problem, I have a WordPress plugin bundling the scoped AWS SDK, but it loads a functions file before another plugin with the SDK.

@theofidry have you had any further thoughts on this?

@patrickjahns is https://github.com/humbug/php-scoper/issues/298#issuecomment-525700081 still your working solution? Where does that code live? Found it https://github.com/owncloud/files_primary_s3/pull/237/files#diff-89e7fc97925a92c2521656947a720895

Update: Thanks @patrickjahns - works a treat!

👍 I’ll try to take a look ASAP but it’s likely gonna take a couple of weeks, this month is quite busy for me 😕

I’ll create a repo for you 👍