-
Notifications
You must be signed in to change notification settings - Fork 50
add profile client #136
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
add profile client #136
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
<?php | ||
|
||
namespace Http\HttplugBundle\Collector; | ||
|
||
use Http\Client\Common\FlexibleHttpClient; | ||
use Http\Client\HttpAsyncClient; | ||
use Http\Client\HttpClient; | ||
use Psr\Http\Message\RequestInterface; | ||
|
||
/** | ||
* The ProfileClient decorates any client that implement both HttpClient and HttpAsyncClient interfaces to gather target | ||
* url and response status code. | ||
* | ||
* @author Fabien Bourigault <bourigaultfabien@gmail.com> | ||
* | ||
* @internal | ||
*/ | ||
class ProfileClient implements HttpClient, HttpAsyncClient | ||
{ | ||
/** | ||
* @var HttpClient|HttpAsyncClient | ||
*/ | ||
private $client; | ||
|
||
/** | ||
* @var Collector | ||
*/ | ||
private $collector; | ||
|
||
/** | ||
* @param HttpClient|HttpAsyncClient $client The client to profile. Client must implement both HttpClient and | ||
* HttpAsyncClient interfaces. | ||
* @param Collector $collector | ||
*/ | ||
public function __construct($client, Collector $collector) | ||
{ | ||
if (!($client instanceof HttpClient && $client instanceof HttpAsyncClient)) { | ||
throw new \RuntimeException(sprintf( | ||
'%s first argument must implement %s and %s. Consider using %s.', | ||
__METHOD__, | ||
HttpClient::class, | ||
HttpAsyncClient::class, | ||
FlexibleHttpClient::class | ||
)); | ||
} | ||
$this->client = $client; | ||
$this->collector = $collector; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function sendAsyncRequest(RequestInterface $request) | ||
{ | ||
$this->collectRequestInformations($request); | ||
|
||
return $this->client->sendAsyncRequest($request); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function sendRequest(RequestInterface $request) | ||
{ | ||
$this->collectRequestInformations($request); | ||
|
||
return $this->client->sendRequest($request); | ||
} | ||
|
||
/** | ||
* @param RequestInterface $request | ||
*/ | ||
private function collectRequestInformations(RequestInterface $request) | ||
{ | ||
if (!$stack = $this->collector->getCurrentStack()) { | ||
return; | ||
} | ||
|
||
$stack = $this->collector->getCurrentStack(); | ||
$stack->setRequestTarget($request->getRequestTarget()); | ||
$stack->setRequestMethod($request->getMethod()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<?php | ||
|
||
namespace Http\HttplugBundle\Collector; | ||
|
||
use Http\Client\Common\FlexibleHttpClient; | ||
use Http\Client\HttpAsyncClient; | ||
use Http\Client\HttpClient; | ||
use Http\HttplugBundle\ClientFactory\ClientFactory; | ||
|
||
/** | ||
* The ProfileClientFactory decorates any ClientFactory and returns the created client decorated by a ProfileClient. | ||
* | ||
* @author Fabien Bourigault <bourigaultfabien@gmail.com> | ||
* | ||
* @internal | ||
*/ | ||
class ProfileClientFactory implements ClientFactory | ||
{ | ||
/** | ||
* @var ClientFactory|callable | ||
*/ | ||
private $factory; | ||
|
||
/** | ||
* @var Collector | ||
*/ | ||
private $collector; | ||
|
||
/** | ||
* @param ClientFactory|callable $factory | ||
* @param Collector $collector | ||
*/ | ||
public function __construct($factory, Collector $collector) | ||
{ | ||
if (!$factory instanceof ClientFactory && !is_callable($factory)) { | ||
throw new \RuntimeException(sprintf('First argument to ProfileClientFactory::__construct must be a "%s" or a callable.', ClientFactory::class)); | ||
} | ||
$this->factory = $factory; | ||
$this->collector = $collector; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function createClient(array $config = []) | ||
{ | ||
$client = is_callable($this->factory) ? $this->factory($config) : $this->factory->createClient($config); | ||
|
||
if (!($client instanceof HttpClient && $client instanceof HttpAsyncClient)) { | ||
$client = new FlexibleHttpClient($client); | ||
} | ||
|
||
return new ProfileClient($client, $this->collector); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
<?php | ||
|
||
namespace Http\HttplugBundle\Tests\Unit\Collector; | ||
|
||
use Http\Client\HttpAsyncClient; | ||
use Http\Client\HttpClient; | ||
use Http\HttplugBundle\Collector\Collector; | ||
use Http\HttplugBundle\Collector\ProfileClient; | ||
use Http\HttplugBundle\Collector\Stack; | ||
use Http\Promise\Promise; | ||
use Psr\Http\Message\RequestInterface; | ||
use Psr\Http\Message\ResponseInterface; | ||
|
||
class ProfileClientTest extends \PHPUnit_Framework_TestCase | ||
{ | ||
/** | ||
* @var Collector | ||
*/ | ||
private $collector; | ||
|
||
/** | ||
* @var Stack | ||
*/ | ||
private $currentStack; | ||
|
||
/** | ||
* @var HttpClient | ||
*/ | ||
private $client; | ||
|
||
/** | ||
* @var RequestInterface | ||
*/ | ||
private $request; | ||
|
||
/** | ||
* @var ProfileClient | ||
*/ | ||
private $subject; | ||
|
||
/** | ||
* @var ResponseInterface | ||
*/ | ||
private $response; | ||
|
||
/** | ||
* @var Promise | ||
*/ | ||
private $promise; | ||
|
||
public function setUp() | ||
{ | ||
$this->collector = $this->getMockBuilder(Collector::class)->disableOriginalConstructor()->getMock(); | ||
$this->currentStack = new Stack('default', 'FormattedRequest'); | ||
$this->client = $this->getMockBuilder(ClientInterface::class)->getMock(); | ||
$this->request = $this->getMockBuilder(RequestInterface::class)->getMock(); | ||
$this->subject = new ProfileClient($this->client, $this->collector); | ||
$this->response = $this->getMockBuilder(ResponseInterface::class)->getMock(); | ||
$this->promise = $this->getMockBuilder(Promise::class)->getMock(); | ||
|
||
$this->client->method('sendRequest')->willReturn($this->response); | ||
$this->client->method('sendAsyncRequest')->willReturn($this->promise); | ||
|
||
$this->request->method('getMethod')->willReturn('GET'); | ||
$this->request->method('getRequestTarget')->willReturn('/target'); | ||
|
||
$this->collector->method('getCurrentStack')->willReturn($this->currentStack); | ||
} | ||
|
||
public function testCallDecoratedClient() | ||
{ | ||
$this->client | ||
->expects($this->once()) | ||
->method('sendRequest') | ||
->with($this->identicalTo($this->request)) | ||
; | ||
|
||
$this->client | ||
->expects($this->once()) | ||
->method('sendAsyncRequest') | ||
->with($this->identicalTo($this->request)) | ||
; | ||
|
||
$this->assertEquals($this->response, $this->subject->sendRequest($this->request)); | ||
|
||
$this->assertEquals($this->promise, $this->subject->sendAsyncRequest($this->request)); | ||
} | ||
|
||
public function testCollectRequestInformations() | ||
{ | ||
$this->subject->sendRequest($this->request); | ||
|
||
$this->assertEquals('GET', $this->currentStack->getRequestMethod()); | ||
$this->assertEquals('/target', $this->currentStack->getRequestTarget()); | ||
} | ||
|
||
public function testCollectAsyncRequestInformations() | ||
{ | ||
$this->subject->sendAsyncRequest($this->request); | ||
|
||
$this->assertEquals('GET', $this->currentStack->getRequestMethod()); | ||
$this->assertEquals('/target', $this->currentStack->getRequestTarget()); | ||
} | ||
} | ||
|
||
interface ClientInterface extends HttpClient, HttpAsyncClient | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. or just mock flexible client? not sure whats better. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does that prevent from doing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. argh, thats quite stupid. ok then this is fine. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's the drawback of the Should this interface moved in an other package (in the tests section)? EDIT: having this interface here makes it not autoloadable which is good! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah i think its right to have it here. its not something that anybody should rely on. |
||
{ | ||
} |
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.
How about introducing a new interface extending both interfaces and type hinting against that?
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.
That may be convenient but it does not comply with the Interface Segregation principle.
I know we talked about this before and decided not to, but I cannot find that thread atm.
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.
Found it on Slack: https://php-http.slack.com/archives/C0D2U9SR3/p1487721978001035
(register here: http://slack.httplug.io/)
Uh oh!
There was an error while loading. Please reload this page.
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 ISP states not to make big interfaces, so that people don't need to implement all of the methods inside. In your case, they still can choose to implement either inherited interface, so ISP wouldn't be violated here IMO. And if you do need both methods, it looks like the best bay to express that need.
Uh oh!
There was an error while loading. Please reload this page.
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.
To be honest, I don't understand what inversion of control has to do with this, but I trust you have your (maybe fairly complicated) reasons. Plus, I wasn't aware there were other capabilities, and if an interface is introduced for every combination, this could get quite messy… the real thing to fix here might be php itself , to add the possibility to type hint against several interfaces :P
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.
More about this here