Skip to content

Prefer the sync call if possible #33

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

Merged
merged 2 commits into from
Dec 26, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"php-http/httplug": "1.0.0-beta",
"php-http/message-factory": "^1.0",
"php-http/promise": "^0.1.1",
"php-http/client-common": "^0.1",
"php-http/client-common": "^0.2@dev",
"symfony/options-resolver": "^2.6|^3.0"
},
"require-dev": {
Expand All @@ -32,6 +32,11 @@
"Http\\Client\\Plugin\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"spec\\Http\\Client\\Plugin\\": "spec/"
}
},
"suggest": {
"php-http/message": "Allow to use Authentication and Encoding plugins",
"php-http/cookie": "Allow to use CookiePlugin",
Expand Down
14 changes: 14 additions & 0 deletions spec/LoopPlugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace spec\Http\Client\Plugin;

use Http\Client\Plugin\Plugin;
use Psr\Http\Message\RequestInterface;

class LoopPlugin implements Plugin
{
public function handleRequest(RequestInterface $request, callable $next, callable $first)
{
return $first($request);
}
}
28 changes: 18 additions & 10 deletions spec/PluginClientSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,6 @@
use Psr\Http\Message\ResponseInterface;
use PhpSpec\ObjectBehavior;

class DefectuousPlugin implements Plugin
{
public function handleRequest(RequestInterface $request, callable $next, callable $first)
{
return $first($request);
}
}

class PluginClientSpec extends ObjectBehavior
{
function let(HttpClient $client)
Expand All @@ -44,7 +36,7 @@ function it_sends_request_with_underlying_client(HttpClient $client, RequestInte
{
$client->sendRequest($request)->willReturn($response);

$this->sendRequest($request)->shouldReturnAnInstanceOf('Psr\Http\Message\ResponseInterface');
$this->sendRequest($request)->shouldReturn($response);
}

function it_sends_async_request_with_underlying_client(HttpAsyncClient $asyncClient, RequestInterface $request, Promise $promise)
Expand All @@ -55,9 +47,25 @@ function it_sends_async_request_with_underlying_client(HttpAsyncClient $asyncCli
$this->sendAsyncRequest($request)->shouldReturn($promise);
}

function it_sends_async_request_if_no_send_request(HttpAsyncClient $asyncClient, RequestInterface $request, ResponseInterface $response, Promise $promise)
{
$this->beConstructedWith($asyncClient->getWrappedObject());
$asyncClient->sendAsyncRequest($request)->willReturn($promise);
$promise->wait()->willReturn($response);

$this->sendRequest($request)->shouldReturn($response);
}

function it_prefers_send_request(StubClient $client, RequestInterface $request, ResponseInterface $response)
{
$client->sendRequest($request)->willReturn($response);

$this->sendRequest($request)->shouldReturn($response);
}

function it_throws_loop_exception(HttpClient $client, RequestInterface $request)
{
$this->beConstructedWith($client, [new DefectuousPlugin()]);
$this->beConstructedWith($client, [new LoopPlugin()]);

$this->shouldThrow('Http\Client\Plugin\Exception\LoopException')->duringSendRequest($request);
}
Expand Down
18 changes: 18 additions & 0 deletions spec/StubClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace spec\Http\Client\Plugin;

use Http\Client\HttpAsyncClient;
use Http\Client\HttpClient;
use Psr\Http\Message\RequestInterface;

class StubClient implements HttpAsyncClient, HttpClient
{
public function sendAsyncRequest(RequestInterface $request)
{
}

public function sendRequest(RequestInterface $request)
{
}
}
24 changes: 0 additions & 24 deletions src/EmulateAsyncClient.php

This file was deleted.

43 changes: 31 additions & 12 deletions src/PluginClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

namespace Http\Client\Plugin;

use Http\Client\Common\EmulatedHttpAsyncClient;
use Http\Client\Exception;
use Http\Client\HttpAsyncClient;
use Http\Client\HttpClient;
use Http\Client\Plugin\Exception\LoopException;
use Http\Promise\FulfilledPromise;
use Http\Promise\RejectedPromise;
use Psr\Http\Message\RequestInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

Expand Down Expand Up @@ -48,7 +52,7 @@ public function __construct($client, array $plugins = [], array $options = [])
if ($client instanceof HttpAsyncClient) {
$this->client = $client;
} elseif ($client instanceof HttpClient) {
$this->client = new EmulateAsyncClient($client);
$this->client = new EmulatedHttpAsyncClient($client);
} else {
throw new \RuntimeException('Client must be an instance of Http\\Client\\HttpClient or Http\\Client\\HttpAsyncClient');
}
Expand All @@ -62,17 +66,34 @@ public function __construct($client, array $plugins = [], array $options = [])
*/
public function sendRequest(RequestInterface $request)
{
$promise = $this->sendAsyncRequest($request);
// If we don't have an http client, use the async call
if (!($this->client instanceof HttpClient)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we just emulate a sync client if an async client is passed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we can, but by doing this we save many calls, from sync to async and async to sync.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't matter IMO. If we emulate a sync client as well, this condition and call becomes unnecessary.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The emulation add a non necessary layer of code in this case.

As we will have a emulated sync call, which transform promise into a response / exception which is then transformed into promise, and then retransformed into response / exception.

Here we only do the conversion from promise to response / exception once.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I meant async emulation here. We don't need sync emulation IMO. There you also have only one conversion (like now).

return $this->sendAsyncRequest($request)->wait();
}

// Else we want to use the synchronous call of the underlying client, and not the async one in the case
// we have both an async and sync call
$client = $this->client;
$pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) use ($client) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically, this doesn't make any difference IMO. When a non-async client is passed, it is emulated to be an async client, which means you are free to always rely on sendAsyncRequest. If it is an async client, the upper condition would just return with an async client call. If it is not an async client, then the emulated async request send method will just do the same as this callback does.

What's the difference then?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The difference is if you have an async client and a sync client, it will use the correct underlying method for sendRequest or sendAsyncRequest, before it was always using the sendAsyncRequest one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but I still can't see the difference. Here is what happens now:

If the client is async (not nonasync) then the async version is called.

If it is sync, we emulate an async one. In the plugin clien's sync call we wrap the sync client call with the same code as we did in case of the emulated client. Same code, whats the difference apart from the method name?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And if it is both async and sync ?

try {
return new FulfilledPromise($client->sendRequest($request));
} catch (Exception $exception) {
return new RejectedPromise($exception);
}
});

return $promise->wait();
return $pluginChain($request)->wait();
}

/**
* {@inheritdoc}
*/
public function sendAsyncRequest(RequestInterface $request)
{
$pluginChain = $this->createPluginChain($this->plugins);
$client = $this->client;
$pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) use ($client) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In PHP 5.4, you can access the $this variable inside the closure.

return $client->sendAsyncRequest($request);
});

return $pluginChain($request);
}
Expand All @@ -95,20 +116,18 @@ protected function configure(array $options = [])
}

/**
* @param Plugin[] $pluginList
* Create the plugin chain.
*
* @param Plugin[] $pluginList A list of plugins
* @param callable $clientCallable Callable making the HTTP call
*
* @return callable
*/
private function createPluginChain($pluginList)
private function createPluginChain($pluginList, callable $clientCallable)
{
$client = $this->client;
$options = $this->options;
$firstCallable = $lastCallable = $clientCallable;

$lastCallable = function (RequestInterface $request) use ($client) {
return $client->sendAsyncRequest($request);
};

$firstCallable = $lastCallable;
while ($plugin = array_pop($pluginList)) {
$lastCallable = function (RequestInterface $request) use ($plugin, $lastCallable, &$firstCallable) {
return $plugin->handleRequest($request, $lastCallable, $firstCallable);
Expand Down