Skip to content
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

Closed
wants to merge 1 commit into from
Closed

Rely on PSR-18 and Guzzle/psr7 only instead of full Guzzle. #1568

wants to merge 1 commit into from

Conversation

Pierstoval
Copy link

This is:

- [x] a bugfix
- [ ] a new feature

Checklist:

Why this change is needed?

Fixes #1562, check the issue for more details

@Pierstoval Pierstoval mentioned this pull request Jul 3, 2020
/**
* Set the HTTP client implementation to be used for network request.
*/
public static function setHttpClient(ClientInterface $httpClient): void
Copy link
Author

@Pierstoval Pierstoval Jul 3, 2020

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

Copy link
Member

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 ?

Copy link
Author

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.

Comment on lines +169 to +174
if (class_exists(Client::class)) {
// Support for Guzzle
return new Client();
} elseif (class_exists(HttpClient::class)) {
// Support for Symfony HttpClient
return HttpClient::create();
Copy link
Author

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 😉

Copy link
Member

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.

Copy link
Author

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"
Copy link
Author

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.

Copy link
Member

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.

Copy link
Author

@Pierstoval Pierstoval Jul 6, 2020

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 😉

Copy link
Member

@PowerKiKi PowerKiKi left a 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"
Copy link
Member

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)
Copy link
Member

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
Copy link
Member

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 ?

Comment on lines +169 to +174
if (class_exists(Client::class)) {
// Support for Guzzle
return new Client();
} elseif (class_exists(HttpClient::class)) {
// Support for Symfony HttpClient
return HttpClient::create();
Copy link
Member

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
Copy link
Member

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
Copy link
Member

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.

Copy link
Author

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

@patrickbrouwers
Copy link
Contributor

@Pierstoval are you planning to improve your PR based on @PowerKiKi 's comments?

@PowerKiKi PowerKiKi closed this in 7cb4884 Jul 19, 2020
@Pierstoval
Copy link
Author

Thanks for taking the work back 👍

@Pierstoval Pierstoval deleted the guzzle branch July 19, 2020 11:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Guzzle 6 support
3 participants