-
Notifications
You must be signed in to change notification settings - Fork 498
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
BC issue with mixed versions between WP Request and WP-CLI Request #659
Comments
I will now look into adding a check in WP-CLI that manually triggers the BC layer for all needed classes when it can detected this is needed by WP. I think this should solve the issue for WP-CLI. I wanted to create the above issue in here nevertheless, as other consumers might be affected, and there might be a fix we can apply within The obvious fix would be to remove the |
@schlessera Couple of questions/observations:
|
@jrfnl Yes, currently WP-CLI is using Requests without namespace scoping. I've tried integrating PHP-Scoper, but it breaks more than it fixes in this case, unfortunately. WP-CLI still is forced to adhere to the extension mechanism of WordPress, and that also means that WP-CLI needs to be able to execute WordPress plugins and themes. The only solution I can think of right now is to load the WP Requests for all commands that need to actually load WP itself, and use its own bundled version if WP is not to be loaded. Given that Core has postponed the adoption of Requests v2, I'll push the WP-CLI v2.5.1 release for PHP 8.1 compat without the Requests v2 upgrade. This buys me time to look further into how this can be properly resolved, and hopefully we'll be able to properly test already early in the cycle. |
If nothing else, you can possibly use the version constant to figure out in strategic places which version of Requests has been loaded ? |
Conclusions from analyzing this issue today in a video call with @schlessera and @alpipego: The problem occurs in the following situation:
The reason is as follow:
The underlying problem is ultimately that during the same "page load" a mix of two different versions of Requests is being used. The suggested solution for projects which expect to encounter different (major) versions of Requests during the same "page load" and don't isolate the separate projects, is to preload the class aliases for the old Requests classes in a bootstrap file. If the names of the "old" Requests classes aliased to the "new" classes are in memory, the "old" Autoloader won't get triggered, the class aliases can do their job and effectively only one version of Requests (the new version) will be used during the "page load". |
Just thinking - to make it easier to implement the above mentioned bootstrapping in projects which have to deal with mixed Requests versions, we could add a In a project bootstrap, a project could then do something along the lines of the below (untested, but should work): require_once 'path/to/Requests/src/Autoload.php';
// Make sure the autoloader is registered first as otherwise you may run into trouble on PHP 8.1.
// See: https://news-web.php.net/php.internals/115549
WpOrg\Requests\Autoload::register();
// Silence deprecations.
// Depending on when the bootstrapping is done, you may need to wrap this in a `if (!defined(...)) {}`.
define('REQUESTS_SILENCE_PSR0_DEPRECATIONS', true)
$preload = WpOrg\Requests\Autoload::getDeprecatedClasses();
$preload['requests'] = '\WpOrg\Requests\Requests'; // Add the base Requests class, just to be safe.
// Preload the class aliases for the Requests 1.x classes to ensure only Requests 2.x classes get loaded.
foreach ($preload as $old => $new) {
// Make sure we don't get "Class already exists errors" from autoloading chains
// Think: an `implements` causing an interface to be loaded before we explicitly request it.
if (class_exists($old) === false && interface_exists($old) === false) {
Autoload::load($old);
}
} @schlessera @alpipego What do you think ? Would that work for you ? A couple of caveats:
Note that the preloading with the lowercase "old" names is not an issue as class/interface names in PHP are case-insensitive. |
Oh and once you've tested this and it's okay, we may want to add it as an example in the |
Some additional notes:
Also @costdev: you may find the above an interesting read in the context of the WP Upgrader component. |
This method will return the array for the class mapping between the legacy classes and their namespaced 2.x counterparts. The need for this change came up while using the 2.0.x version of the library together with WordPress core, that includes a 1.x version of Requests. In order to prevent WordPress core from autoloading the 1.x classes, they must be aliased before accessed. Through exposing the mapping array, the classes can be aliased before being loaded by WordPress core. See WordPress#659
This method will return the array for the class mapping between the legacy classes and their namespaced 2.x counterparts. The need for this change came up while using the 2.0.x version of the library together with WordPress core, that includes a 1.x version of Requests. In order to prevent WordPress core from autoloading the 1.x classes, they must be aliased before accessed. Through exposing the mapping array, the classes can be aliased before being loaded by WordPress core. See WordPress#659
Thank you @jrfnl. The code worked for me with minor changes: require_once 'path/to/Requests/src/Autoload.php';
// Make sure the autoloader is registered first as otherwise you may run into trouble on PHP 8.1.
// See: https://news-web.php.net/php.internals/115549
WpOrg\Requests\Autoload::register();
// Silence deprecations.
// Depending on when the bootstrapping is done, you may need to wrap this in a `if (!defined(...)) {}`.
- define('REQUESTS_SILENCE_PSR0_DEPRECATIONS', true)
+ define('REQUESTS_SILENCE_PSR0_DEPRECATIONS', true);
- $preload = WpOrg\Requests\Autoload::getDeprecatedClasses();
+ $preload = WpOrg\Requests\Autoload::get_deprecated_classes();
$preload['requests'] = '\WpOrg\Requests\Requests'; // Add the base Requests class, just to be safe.
// Preload the class aliases for the Requests 1.x classes to ensure only Requests 2.x classes get loaded.
foreach ($preload as $old => $new) {
// Make sure we don't get "Class already exists errors" from autoloading chains
// Think: an `implements` causing an interface to be loaded before we explicitly request it.
if (class_exists($old) === false && interface_exists($old) === false) {
- Autoload::load($old);
+ WpOrg\Requests\Autoload::load($old);
}
} I have added the new method to the autoloader and added the example to the |
@alpipego Ha, that's what I get for quickly writing out a code sample in a comment instead of a code editor 😂 I'll have a look at the PR. Thanks for testing and writing this up. |
This method will return the array for the class mapping between the legacy classes and their namespaced 2.x counterparts. The need for this change came up while using the 2.0.x version of the library together with WordPress core, that includes a 1.x version of Requests. In order to prevent WordPress core from autoloading the 1.x classes, they must be aliased before accessed. Through exposing the mapping array, the classes can be aliased before being loaded by WordPress core. See WordPress#659
This method will return the array for the class mapping between the legacy classes and their namespaced 2.x counterparts. The need for this change came up while using the 2.x version of the library together with WordPress core, that includes a 1.x version of Requests. In order to prevent WordPress core from autoloading the 1.x classes, they must be aliased before accessed. Through exposing the mapping array, the classes can be aliased before being loaded by WordPress core. See WordPress#659
Pinging @hellofromtonya as a FYI as well. |
This method will return the array for the class mapping between the legacy classes and their namespaced 2.x counterparts. The need for this change came up while using the 2.x version of the library together with a framework or CMS, that includes a 1.x version of Requests. In order to prevent the framework from autoloading the 1.x classes, they must be aliased before accessed. Through exposing the mapping array, the classes can be aliased before being their implementations get loaded. See WordPress#659
Leaving this open until @schlessera has been able to test the above with WP-CLI. Alain: please let us know how you get on & close the issue if the changes made in #681 solve this sufficiently. |
This method will return the array for the class mapping between the legacy classes and their namespaced 2.x counterparts. The need for this change came up while using the 2.x version of the library together with a framework or CMS, that includes a 1.x version of Requests. In order to prevent the framework from autoloading the 1.x classes, they must be aliased before accessed. Through exposing the mapping array, the classes can be aliased before being their implementations get loaded. See WordPress#659
@schlessera Just checking in: have you had a chance to test this with WP-CLI in the mean time ? |
@schlessera Just checking - has this been tested with WP-CLI by now ? |
This has been resolved through PR wp-cli/wp-cli#5795 as well as its follow-up hotfix wp-cli/wp-cli#5796. |
Summary
When WordPress uses strictly-typed arguments in constructors dealing with
Requests
, they break withRequests
version mismatches, as the new objects do not inherit the old objects.Given the following code sample
As an example, let's consider installing WP Core with WP-CLI. This is only a single example for a bigger problem.
I'd expect the following behaviour
I'd expect this to work successfully, and WP-CLI doing what is needed to put WP Core into an installed state and then outputting:
Instead this happened
With Requests v2 bundled with WP-CLI, this fatals:
Additional context
WP-CLI bundles its own version of
Requests
, as it also needs to make remote requests when the WP site is missing or broken.In the example above, what happens is that the BC layer is being triggered by WordPress Core as expected and allows the old classes and methods to be used in older WP core versions.
However, WP Core uses strict typing based on class in some constructors and methods. As the BC layer is built with class aliases, and not with extensions, the type hint is wrong and fatals. Although, the
WpOrg\Requests\Response
is aliased toRequests\Response
, using a trsict type-hint does not seem to trigger the BC layer and so the class alias was not loaded even though it would be needed.Tested against
develop
branch?develop
branch of Requests.The text was updated successfully, but these errors were encountered: