-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
Rely on PSR-18 and Guzzle/psr7 only instead of full Guzzle. #1568
Conversation
/** | ||
* Set the HTTP client implementation to be used for network request. | ||
*/ | ||
public static function setHttpClient(ClientInterface $httpClient): void |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed this because some HTTP clients implementing PSR-18 might be scoped (the Symfony one can be, for instance), so it's safer to inject it at runtime instead of keeping a client in the memory
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
might be scoped
What to you mean be that ? And what difference does it make for PhpSpreadsheet if an HTTP client is scoped ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When using Symfony HTTP Client, it can be scoped, meaning it can have a base uri, default headers, etc., which might not be suitable for PhpSpreadsheet usage. Some HTTP clients can also contain state: history, cookies, etc., so keeping its state in the memory can have side-effects, such as leaking state, etc.
if (class_exists(Client::class)) { | ||
// Support for Guzzle | ||
return new Client(); | ||
} elseif (class_exists(HttpClient::class)) { | ||
// Support for Symfony HttpClient | ||
return HttpClient::create(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's provide support for at least 2 clients, just in case 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This won't do. You removed dependencies on implementation in composer.json, but you re-add add them here in code. All references to specific implementations must disappear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, these are optional dependencies, since class_exists()
is used, so if the class doesn't exist, it will fall back to an exception, telling the user to either inject an http client, or install one of the two supported implementations.
I forgot to do it in the PR, but I was about to add a suggest
section in composer.json
to invite users to install either symfony/http-client
or guzzlehttp/guzzle
to benefit from the WEBSERVICE
feature.
This is kinda like automatic feature-flag depending on what composer packages are installed in the project
@@ -57,7 +57,8 @@ | |||
"markbaker/complex": "^1.4", | |||
"markbaker/matrix": "^1.2", | |||
"psr/simple-cache": "^1.0", | |||
"guzzlehttp/guzzle": "^7.0" | |||
"psr/http-client": "^1.0", | |||
"guzzlehttp/psr7": "^1.6" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
guzzlehttp/psr7
is safe, compared to guzzlehttp/guzzle
, because it only contains the tools to create the Request
object, which would be cumbersome if we had to create it directly in PhpSpreadsheet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we do the PSR way, we hat to do it all the way. That means dropping all references to Guzzle and instead rely on https://www.php-fig.org/psr/psr-17/ to create Request object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even though I agree on your point, there are some kind of maintainability issues with it: this means that PhpSpreadsheet would have to have its own factory, plus its own Request class, plus its own Response class for testing. This is reinventing the wheel at the cost of maintaining it for one single feature in PhpSpreadsheet.
Here, relying on guzzlehttp/psr7
is safer than the entire Guzzle stack, because guzzlehttp/psr7
depends only on ralouphie/getallheaders
version 2 and 3, making it compatible with all packages that might already have it in their dependency tree. For instance, Drupal needs it since 8.8, Slim needs it too, they depend on its v3. There are very few packages that need its v2, and yet guzzle's PSR-7 implementation is still compatible with it anyway, so it's safe even with really old apps.
Seems really fair in terms of balance between maintainability and dependency tree 😉
If you like to go all along with PSRs, then we shouldn't at all require any PSR-18 implementation and instead recreate an entire Http Client in PhpSpreadsheet, but I think you agree that relying on external implementations by using interfaces is safer for PhpSpreadsheet maintainers burden 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a few things that need changing/explaining before this can be merged.
@@ -57,7 +57,8 @@ | |||
"markbaker/complex": "^1.4", | |||
"markbaker/matrix": "^1.2", | |||
"psr/simple-cache": "^1.0", | |||
"guzzlehttp/guzzle": "^7.0" | |||
"psr/http-client": "^1.0", | |||
"guzzlehttp/psr7": "^1.6" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we do the PSR way, we hat to do it all the way. That means dropping all references to Guzzle and instead rely on https://www.php-fig.org/psr/psr-17/ to create Request object.
@@ -18,7 +19,7 @@ class Web | |||
* | |||
* @return string the output resulting from a call to the webservice | |||
*/ | |||
public static function WEBSERVICE(string $url) | |||
public static function WEBSERVICE(string $url, ?ClientInterface $client = null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method signature must not change, because in most cases the user is not directly calling this method, instead the calculation engine is. And the calculation engine cannot provide extra parameters such as this new one.
/** | ||
* Set the HTTP client implementation to be used for network request. | ||
*/ | ||
public static function setHttpClient(ClientInterface $httpClient): void |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
might be scoped
What to you mean be that ? And what difference does it make for PhpSpreadsheet if an HTTP client is scoped ?
if (class_exists(Client::class)) { | ||
// Support for Guzzle | ||
return new Client(); | ||
} elseif (class_exists(HttpClient::class)) { | ||
// Support for Symfony HttpClient | ||
return HttpClient::create(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This won't do. You removed dependencies on implementation in composer.json, but you re-add add them here in code. All references to specific implementations must disappear.
/** | ||
* Set the HTTP client implementation to be used for network request. | ||
*/ | ||
public static function setHttpClient(ClientInterface $httpClient): void |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be kept as:
public static function setHttpClient(ClientInterface $httpClient, RequestFactoryInterface $requestFactory): void
Because both parameters must be set, in a coherent way, in order for WEBSERVICE to work.
use Psr\Http\Message\RequestInterface; | ||
use Psr\Http\Message\ResponseInterface; | ||
|
||
class HttpClientMock implements ClientInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This entire class should be replaced by PHPunit mock mechanic in order to also test that the method is called exactly once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see any value in checking the number of times the client is tested.
I could change the mock to remove the internal response when sendRequest()
is used, this will ensure it's used only once.
Also, using phpunit mocks has a performance impact. Here, having a custom mock is costless
@Pierstoval are you planning to improve your PR based on @PowerKiKi 's comments? |
Thanks for taking the work back 👍 |
This is:
Checklist:
Why this change is needed?
Fixes #1562, check the issue for more details