Skip to content

Commit 5ab289d

Browse files
authored
Merge pull request #41 from venyii/adjustable-req-timeout
Add setter to adjust the http clients request timeout
2 parents 6b9d0a3 + cc2f9f6 commit 5ab289d

File tree

4 files changed

+170
-29
lines changed

4 files changed

+170
-29
lines changed

src/Analytics.php

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,10 @@ class Analytics
354354
*/
355355
protected $isDisabled = false;
356356

357+
/**
358+
* @var array
359+
*/
360+
protected $options = [];
357361

358362
/**
359363
* Initializes to a list of all the available parameters to be sent in a hit.
@@ -459,9 +463,10 @@ class Analytics
459463
*
460464
* @param bool $isSsl
461465
* @param bool $isDisabled
466+
* @param array $options
462467
* @throws \InvalidArgumentException
463468
*/
464-
public function __construct($isSsl = false, $isDisabled = false)
469+
public function __construct($isSsl = false, $isDisabled = false, array $options = [])
465470
{
466471
if (!is_bool($isSsl)) {
467472
throw new \InvalidArgumentException('First constructor argument "isSSL" must be boolean');
@@ -477,6 +482,7 @@ public function __construct($isSsl = false, $isDisabled = false)
477482
}
478483

479484
$this->isDisabled = $isDisabled;
485+
$this->options = $options;
480486
}
481487

482488
/**
@@ -585,12 +591,25 @@ protected function sendHit($methodName)
585591

586592
if ($this->isDisabled) {
587593
return new NullAnalyticsResponse();
588-
} else {
589-
return $this->getHttpClient()->post(
590-
$this->getUrl(),
591-
$this->isAsyncRequest
592-
);
593594
}
595+
596+
return $this->getHttpClient()->post($this->getUrl(), $this->getHttpClientOptions());
597+
}
598+
599+
/**
600+
* Build the options array for the http client based on the Analytics object options.
601+
*
602+
* @return array
603+
*/
604+
protected function getHttpClientOptions()
605+
{
606+
$options = ['async' => $this->isAsyncRequest];
607+
608+
if (isset($this->options['timeout'])) {
609+
$options['timeout'] = $this->options['timeout'];
610+
}
611+
612+
return $options;
594613
}
595614

596615
/**

src/Network/HttpClient.php

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,24 +83,25 @@ private function getClient()
8383
*
8484
* @internal
8585
* @param string $url
86-
* @param boolean $nonBlocking
86+
* @param array $options
8787
* @return AnalyticsResponse
8888
*/
89-
public function post($url, $nonBlocking = false)
89+
public function post($url, array $options = [])
9090
{
9191
$request = new Request(
9292
'GET',
9393
$url,
9494
['User-Agent' => self::PHP_GA_MEASUREMENT_PROTOCOL_USER_AGENT]
9595
);
9696

97+
$opts = $this->parseOptions($options);
9798
$response = $this->getClient()->sendAsync($request, [
98-
'synchronous' => !$nonBlocking,
99-
'timeout' => self::REQUEST_TIMEOUT_SECONDS,
100-
'connect_timeout' => self::REQUEST_TIMEOUT_SECONDS,
99+
'synchronous' => !$opts['async'],
100+
'timeout' => $opts['timeout'],
101+
'connect_timeout' => $opts['timeout'],
101102
]);
102103

103-
if ($nonBlocking) {
104+
if ($opts['async']) {
104105
self::$promises[] = $response;
105106
} else {
106107
$response = $response->wait();
@@ -109,6 +110,35 @@ public function post($url, $nonBlocking = false)
109110
return $this->getAnalyticsResponse($request, $response);
110111
}
111112

113+
/**
114+
* Parse the given options and fill missing fields with default values.
115+
*
116+
* @param array $options
117+
* @return array
118+
*/
119+
private function parseOptions(array $options)
120+
{
121+
$defaultOptions = [
122+
'timeout' => static::REQUEST_TIMEOUT_SECONDS,
123+
'async' => false,
124+
];
125+
126+
$opts = [];
127+
foreach ($defaultOptions as $option => $value) {
128+
$opts[$option] = isset($options[$option]) ? $options[$option] : $defaultOptions[$option];
129+
}
130+
131+
if (!is_int($opts['timeout']) || $opts['timeout'] <= 0) {
132+
throw new \UnexpectedValueException('The timeout must be an integer with a value greater than 0');
133+
}
134+
135+
if (!is_bool($opts['async'])) {
136+
throw new \UnexpectedValueException('The async option must be boolean');
137+
}
138+
139+
return $opts;
140+
}
141+
112142
/**
113143
* Creates an analytics response object.
114144
*

tests/TheIconic/Tracking/GoogleAnalytics/AnalyticsTest.php

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace TheIconic\Tracking\GoogleAnalytics;
44

5+
use TheIconic\Tracking\GoogleAnalytics\Network\HttpClient;
56
use TheIconic\Tracking\GoogleAnalytics\Parameters\ContentGrouping\ContentGroup;
67
use TheIconic\Tracking\GoogleAnalytics\Parameters\EnhancedEcommerce\Affiliation;
78
use TheIconic\Tracking\GoogleAnalytics\Parameters\EnhancedEcommerce\CouponCode;
@@ -40,7 +41,6 @@ class AnalyticsTest extends \PHPUnit_Framework_TestCase
4041
*/
4142
private $analyticsSsl;
4243

43-
4444
public function setUp()
4545
{
4646
$this->analytics = new Analytics();
@@ -55,7 +55,6 @@ public function testInvalidClassInitialization()
5555
(new Analytics('1'));
5656
}
5757

58-
5958
/**
6059
* @expectedException \InvalidArgumentException
6160
*/
@@ -491,8 +490,6 @@ public function testMinimumParametersForSendHitMissingClientIdAndUserId()
491490
$this->analytics->sendPageview();
492491
}
493492

494-
/**
495-
*/
496493
public function testMinimumParametersForSendHitMissingClientIdButUserId()
497494
{
498495
$httpClient = $this->getMock('TheIconic\Tracking\GoogleAnalytics\Network\HttpClient', ['post']);
@@ -514,8 +511,6 @@ public function testMinimumParametersForSendHitMissingClientIdButUserId()
514511
$this->analytics->sendPageview();
515512
}
516513

517-
/**
518-
*/
519514
public function testMinimumParametersForSendHitWithClientIdButMissingUserId()
520515
{
521516
$httpClient = $this->getMock('TheIconic\Tracking\GoogleAnalytics\Network\HttpClient', ['post']);
@@ -553,4 +548,42 @@ public function testInvalidMethodCall()
553548
$this->analytics
554549
->iDontExists();
555550
}
551+
552+
/**
553+
* @dataProvider dataProviderAnalyticsOptions
554+
*
555+
* @param array $options
556+
* @param array $expectedOptions
557+
* @param bool $async
558+
*/
559+
public function testSendPassesOptionsToHttpClient(array $options, array $expectedOptions, $async)
560+
{
561+
$httpClient = $this->getMock('TheIconic\Tracking\GoogleAnalytics\Network\HttpClient', ['post']);
562+
563+
$analytics = new Analytics(false, false, $options);
564+
$analytics
565+
->setProtocolVersion('1')
566+
->setTrackingId('555')
567+
->setClientId('666')
568+
->setDocumentPath('\thepage')
569+
->setHitType('pageview')
570+
->setAsyncRequest($async);
571+
572+
$httpClient->expects($this->once())
573+
->method('post')
574+
->with($this->equalTo($analytics->getUrl()), $expectedOptions);
575+
576+
$analytics->setHttpClient($httpClient);
577+
$analytics->sendPageview();
578+
}
579+
580+
public static function dataProviderAnalyticsOptions()
581+
{
582+
return [
583+
[[], ['async' => false], false],
584+
[[], ['async' => true], true],
585+
[['timeout' => 5], ['timeout' => 5, 'async' => false], false],
586+
[['timeout' => 101], ['timeout' => 101, 'async' => true], true],
587+
];
588+
}
556589
}

tests/TheIconic/Tracking/GoogleAnalytics/Network/HttpClientTest.php

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@
22

33
namespace TheIconic\Tracking\GoogleAnalytics\Network;
44

5-
use TheIconic\Tracking\GoogleAnalytics\Parameters\General\CacheBuster;
6-
use TheIconic\Tracking\GoogleAnalytics\Tests\CompoundParameterTestCollection;
7-
use TheIconic\Tracking\GoogleAnalytics\Tests\CompoundTestParameter;
8-
use TheIconic\Tracking\GoogleAnalytics\Tests\SingleTestParameter;
9-
use TheIconic\Tracking\GoogleAnalytics\Tests\SingleTestParameterIndexed;
5+
use Psr\Http\Message\RequestInterface;
106

117
class HttpClientTest extends \PHPUnit_Framework_TestCase
128
{
@@ -23,7 +19,45 @@ class HttpClientTest extends \PHPUnit_Framework_TestCase
2319
public function setUp()
2420
{
2521
$this->httpClient = new HttpClient();
22+
}
23+
24+
/**
25+
* @dataProvider dataProviderInvalidOptions
26+
*
27+
* @param array $options
28+
* @param $exceptionMessage
29+
*/
30+
public function testPostValidatesOptions(array $options, $exceptionMessage)
31+
{
32+
$guzzleClient = $this->getMockBuilder('GuzzleHttp\Client')
33+
->setMethods(['sendAsync'])
34+
->disableOriginalConstructor()
35+
->getMock();
36+
37+
$guzzleClient->expects($this->never())->method('sendAsync');
38+
$this->httpClient->setClient($guzzleClient);
2639

40+
$this->setExpectedException(\UnexpectedValueException::class, $exceptionMessage);
41+
42+
$this->httpClient->post('http://test-collector.com/collect?v=1', $options);
43+
}
44+
45+
public static function dataProviderInvalidOptions()
46+
{
47+
$timeoutExc = 'The timeout must be an integer with a value greater than 0';
48+
$asyncExc = 'The async option must be boolean';
49+
50+
return [
51+
[['timeout' => 'no'], $timeoutExc],
52+
[['timeout' => -1], $timeoutExc],
53+
[['timeout' => true], $timeoutExc],
54+
[['async' => 'false'], $asyncExc],
55+
[['async' => 1], $asyncExc],
56+
];
57+
}
58+
59+
public function testPost()
60+
{
2761
$mockResponse = $this->getMockBuilder('GuzzleHttp\Psr7\Response')
2862
->setMethods(['getStatusCode'])
2963
->disableOriginalConstructor()
@@ -44,7 +78,32 @@ public function setUp()
4478

4579
$guzzleClient->expects($this->atLeast(1))
4680
->method('sendAsync')
47-
->with($this->anything())
81+
->withConsecutive(
82+
[
83+
$this->isInstanceOf(RequestInterface::class),
84+
[
85+
'synchronous' => true,
86+
'timeout' => 100,
87+
'connect_timeout' => 100,
88+
],
89+
],
90+
[
91+
$this->isInstanceOf(RequestInterface::class),
92+
[
93+
'synchronous' => false,
94+
'timeout' => 30,
95+
'connect_timeout' => 30,
96+
],
97+
],
98+
[
99+
$this->isInstanceOf(RequestInterface::class),
100+
[
101+
'synchronous' => true,
102+
'timeout' => 3,
103+
'connect_timeout' => 3,
104+
],
105+
]
106+
)
48107
->will($this->returnValue($mockPromise));
49108

50109
$this->httpClient->setClient($guzzleClient);
@@ -58,17 +117,17 @@ public function setUp()
58117
->will($this->returnArgument(1));
59118

60119
$this->mockHttpClient->setClient($guzzleClient);
61-
}
62120

63-
public function testPost()
64-
{
65121
$response = $this->mockHttpClient->post('http://test-collector.com/collect?v=1');
66122
$this->assertInstanceOf('Psr\Http\Message\ResponseInterface', $response);
67123

68-
$responseAsync = $this->mockHttpClient->post('http://test-collector.com/collect?v=1', true);
124+
$responseAsync = $this->mockHttpClient->post(
125+
'http://test-collector.com/collect?v=1',
126+
['async' => true, 'timeout' => 30]
127+
);
69128
$this->assertInstanceOf('GuzzleHttp\Promise\PromiseInterface', $responseAsync);
70129

71-
$response = $this->httpClient->post('http://test-collector.com/collect?v=1');
130+
$response = $this->httpClient->post('http://test-collector.com/collect?v=1', ['timeout' => 3]);
72131

73132
$this->assertInstanceOf('TheIconic\Tracking\GoogleAnalytics\AnalyticsResponse', $response);
74133

0 commit comments

Comments
 (0)