Skip to content
This repository was archived by the owner on Jan 21, 2020. It is now read-only.

Commit 944bf68

Browse files
committed
Send (most) payloads as form-urlencoded data
As it turns out, while the expected content-type is still application/json (though in practice it doesn't matter what you send), the actual expected _contents_ are supposed to be application/x-www-form-urlencoded regardless, except in a few cases (direct messages and media uploads). This patch modifies the `post()` and `performPost()` methods slightly: the former checks to see if the provided `$path` is in a known whitelist for JSON payloads, and passes the result on to `performPost()`. That method then varies how the payload is created and injected into the request accordingly. This _should_ fix #48.
1 parent 95e27ef commit 944bf68

File tree

2 files changed

+73
-86
lines changed

2 files changed

+73
-86
lines changed

src/Twitter.php

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ class Twitter
3232
*/
3333
const OAUTH_BASE_URI = 'https://api.twitter.com/oauth';
3434

35+
/**
36+
* Paths that use JSON payloads (vs form-encoded)
37+
*/
38+
const PATHS_JSON_PAYLOAD = [
39+
'direct_messages/events/new',
40+
'direct_messages/welcome_messages/new',
41+
'direct_messages/welcome_messages/rules/new',
42+
];
43+
3544
/**
3645
* 246 is the current limit for a status message, 140 characters are displayed
3746
* initially, with the remainder linked from the web UI or client. The limit is
@@ -402,7 +411,12 @@ public function post(string $path, $data = null) : Response
402411
{
403412
$client = $this->getHttpClient();
404413
$this->init($path, $client);
405-
$response = $this->performPost(Http\Request::METHOD_POST, $data, $client);
414+
$response = $this->performPost(
415+
Http\Request::METHOD_POST,
416+
$data,
417+
$client,
418+
in_array($path, self::PATHS_JSON_PAYLOAD, true)
419+
);
406420
return new Response($response);
407421
}
408422

@@ -1273,19 +1287,7 @@ public function statusesUpdate(string $status, $inReplyToStatusId = null, $extra
12731287
$params['in_reply_to_status_id'] = $inReplyToStatusId;
12741288
}
12751289

1276-
// For some reason, this endpoint DOES NOT accept JSON, but only form
1277-
// encoded parameters. As such, we do not call `post()` here, but instead
1278-
// interact directly with the HTTP client.
1279-
// @see https://github.com/zendframework/ZendService_Twitter/issues/48
1280-
$httpClient = $this->getHttpClient();
1281-
$this->init($path, $httpClient);
1282-
$httpClient->setMethod('POST');
1283-
$httpClient
1284-
->getRequest()
1285-
->getHeaders()
1286-
->addHeaderLine('Content-Type', 'application/x-www-form-urlencoded');
1287-
$httpClient->setParameterPost($params);
1288-
return new Response($httpClient->send());
1290+
return $this->post($path, $params);
12891291
}
12901292

12911293
/**
@@ -1512,21 +1514,17 @@ protected function validateScreenName(string $name) : string
15121514
* is JSON-encoded before being passed to the request body.
15131515
*
15141516
* @param null|string|array|\stdClass $data Raw data to send
1517+
* @param bool $asJson Whether or not the data should be submitted as JSON
1518+
* (vs form urlencoded, which is the default)
15151519
*/
1516-
protected function performPost(string $method, $data, Http\Client $client) : Http\Response
1520+
protected function performPost(string $method, $data, Http\Client $client, bool $asJson) : Http\Response
15171521
{
1518-
if (is_array($data) || is_object($data)) {
1519-
$data = json_encode($data, $this->jsonFlags);
1520-
}
1522+
$client->setMethod($method);
15211523

1522-
if (! empty($data)) {
1523-
$client->setRawBody($data);
1524-
$client->getRequest()
1525-
->getHeaders()
1526-
->addHeaderLine('Content-Type', 'application/json');
1527-
}
1524+
$asJson
1525+
? $this->prepareJsonPayloadForClient($client, $data)
1526+
: $this->prepareFormPayloadForClient($client, $data);
15281527

1529-
$client->setMethod($method);
15301528
return $client->send();
15311529
}
15321530

@@ -1642,4 +1640,36 @@ protected function createUserListParameter($ids, array $params, string $context)
16421640
$params['screen_name'] = implode(',', $ids);
16431641
return $params;
16441642
}
1643+
1644+
/**
1645+
* Prepare a JSON payload for the HTTP client.
1646+
*/
1647+
private function prepareJsonPayloadForClient(Http\Client $client, $data)
1648+
{
1649+
if (is_array($data) || is_object($data)) {
1650+
$data = json_encode($data, $this->jsonFlags);
1651+
}
1652+
1653+
if (empty($data) || ! is_string($data)) {
1654+
return;
1655+
}
1656+
1657+
$client->getRequest()
1658+
->getHeaders()
1659+
->addHeaderLine('Content-Type', 'application/json');
1660+
1661+
$client->setRawBody($data);
1662+
}
1663+
1664+
/**
1665+
* Prepare a form-url-encoded payload for the HTTP client.
1666+
*/
1667+
private function prepareFormPayloadForClient(Http\Client $client, $data)
1668+
{
1669+
if (! is_array($data)) {
1670+
return;
1671+
}
1672+
1673+
$client->setParameterPost($data);
1674+
}
16451675
}

test/TwitterTest.php

Lines changed: 18 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -65,18 +65,16 @@ protected function stubOAuthClient(
6565
$client->setHeaders(['Accept-Charset' => 'ISO-8859-1,utf-8'])->will([$client, 'reveal']);
6666
$client->clearCookies()->will([$client, 'reveal']);
6767
$client->getCookies()->willReturn([]);
68+
6869
if (null !== $params && $method === 'GET') {
6970
$client->setParameterGet($params)->shouldBeCalled();
7071
}
71-
if (null !== $params && $method === 'POST') {
72-
$requestBody = json_encode($params, $this->jsonFlags);
73-
$client->setRawBody($requestBody)->shouldBeCalled();
74-
75-
$headers = $this->prophesize(Http\Headers::class);
76-
$headers->addHeaderLine('Content-Type', 'application/json')->shouldBeCalled();
77-
$request = $this->prophesize(Http\Request::class);
78-
$request->getHeaders()->will([$headers, 'reveal']);
79-
$client->getRequest()->will([$request, 'reveal']);
72+
73+
if ($method === 'POST' && null !== $params) {
74+
$pathMinusExtension = str_replace('.json', '', $path);
75+
in_array($pathMinusExtension, Twitter\Twitter::PATHS_JSON_PAYLOAD, true)
76+
? $this->prepareJsonPayloadForClient($client, $params)
77+
: $this->prepareFormEncodedPayloadForClient($client, $params);
8078
}
8179

8280
$response = $this->prophesize(Http\Response::class);
@@ -103,62 +101,21 @@ protected function stubOAuthClient(
103101
return $client->reveal();
104102
}
105103

106-
/**
107-
* Quick reusable OAuth client stub setup for form-encoded POST requests.
108-
*
109-
* It acts exactly like stubOAuthClient(), but instead of creating a
110-
* json-encoded payload, it creates a form-encoded one.
111-
*
112-
* @param string $path Path appended to Twitter API endpoint
113-
* @param string $method Do we expect HTTP GET or POST?
114-
* @param string $responseFile File containing a valid XML response to the request
115-
* @param array $params Expected GET/POST parameters for the request
116-
* @param array $responseHeaders Headers expected on the returned response
117-
* @return OAuthClient
118-
*/
119-
protected function stubOAuthClientForFormPost(
120-
$path,
121-
$responseFile = null,
122-
array $params = [],
123-
array $responseHeaders = []
124-
) {
125-
$client = $this->prophesize(OAuthClient::class);
126-
$client->setMethod('POST')->will([$client, 'reveal']);
127-
$client->setParameterPost($params)->will([$client, 'reveal']);
128-
$client->resetParameters()->will([$client, 'reveal']);
129-
$client->setUri('https://api.twitter.com/1.1/' . $path)->shouldBeCalled();
130-
$client->setHeaders(['Accept-Charset' => 'ISO-8859-1,utf-8'])->will([$client, 'reveal']);
131-
$client->clearCookies()->will([$client, 'reveal']);
132-
$client->getCookies()->willReturn([]);
133-
104+
protected function prepareJsonPayloadForClient($client, array $params = null)
105+
{
134106
$headers = $this->prophesize(Http\Headers::class);
135-
$headers->addHeaderLine('Content-Type', 'application/x-www-form-urlencoded')->shouldBeCalled();
107+
$headers->addHeaderLine('Content-Type', 'application/json')->shouldBeCalled();
136108
$request = $this->prophesize(Http\Request::class);
137109
$request->getHeaders()->will([$headers, 'reveal']);
138110
$client->getRequest()->will([$request, 'reveal']);
139111

140-
$response = $this->prophesize(Http\Response::class);
141-
142-
$response->getBody()->will(function () use ($responseFile) {
143-
if (null === $responseFile) {
144-
return '{}';
145-
}
146-
return file_get_contents(__DIR__ . '/_files/' . $responseFile);
147-
});
148-
149-
$headers = $this->prophesize(Http\Headers::class);
150-
foreach ($responseHeaders as $headerName => $value) {
151-
$headers->has($headerName)->willReturn(true);
152-
$header = $this->prophesize(Http\Header\HeaderInterface::class);
153-
$header->getFieldValue()->willReturn($value);
154-
$headers->get($headerName)->will([$header, 'reveal']);
155-
}
156-
157-
$response->getHeaders()->will([$headers, 'reveal']);
158-
159-
$client->send()->will([$response, 'reveal']);
112+
$requestBody = json_encode($params, $this->jsonFlags);
113+
$client->setRawBody($requestBody)->shouldBeCalled();
114+
}
160115

161-
return $client->reveal();
116+
protected function prepareFormEncodedPayloadForClient($client, array $params = null)
117+
{
118+
$client->setParameterPost($params)->will([$client, 'reveal']);
162119
}
163120

164121
public function stubHttpClientInitialization()
@@ -507,13 +464,13 @@ public function testUserTimelineReturnsResults()
507464

508465
/**
509466
* TODO: Add verification for ALL optional parameters
510-
* @see https://github.com/zendframework/ZendService_Twitter/issues/48
511467
*/
512468
public function testPostStatusUpdateReturnsResponse()
513469
{
514470
$twitter = new Twitter\Twitter;
515-
$twitter->setHttpClient($this->stubOAuthClientForFormPost(
471+
$twitter->setHttpClient($this->stubOAuthClient(
516472
'statuses/update.json',
473+
Http\Request::METHOD_POST,
517474
'statuses.update.json',
518475
['status' => 'Test Message 1']
519476
));

0 commit comments

Comments
 (0)