Send multidimensional array as Guzzle form_params - php

I'm using Laravel 9 with a PHP Linnworks API repository (https://github.com/booni3/linnworks) which uses Guzzle for the API requests like so: linnworks->api->Orders()->post($url, $parameters );
I'm trying to re-create the following CURL POST request (which works fine) with the above repository's Guzzle implementation:
curl_setopt_array($curl, array(
CURLOPT_URL => "https://eu-ext.linnworks.net//api/Orders/SetExtendedProperties",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "orderId=bf812fc0-d655-ecda-26da-bd64bcb3898a&extendedProperties=[
{
\"RowId\": \"ea89429e-5c71-4069-8340-7fad55ffe056\",
\"Name\": \"Net-Total\",
\"Value\": \"123\",
\"Type\": \"PROFITCALC\"
}
]"
As you can see, the postfields are just sent as a string, which was nice and easy as Linnworks API can be a bit all over the place with the request formats, but with this Guzzle implementation I'm having to send $parameters as an array. I've tried json encoding the extendedProperties sub-array like so:
$parameters = [
"orderId" => "bf812fc0-d655-ecda-26da-bd64bcb3898a",
"extendedProperties" => json_encode([
"RowId" => "ea89429e-5c71-4069-8340-7fad55ffe056",
"Name" => "Net-Total",
"Value" => "123",
"Type" => "PROFITCALC"
])
];
$url = "Orders/SetExtendedProperties/";
$extendedPropertiesNew = $this->linnworks->api->Orders()->post($url, $parameters );
Unfortunately though, I'm getting the following error returned by the API:
Client error: `POST https://eu-ext.linnworks.net/api/Orders/SetExtendedProperties/` resulted in a `400 Bad Request` response: {"Code":null,"Message":"Invalid parameter extendedProperties"}
Which doesn't make much sense to me as the request documentation here (https://apps.linnworks.net/Api/Method/Orders-SetExtendedProperties) states extendedProperties is a legit parameter and my Curl request also works fine.
I've also tried not using json_encode and just sending $parameters as a multi dimensional array but that just returns an empty array, deletes any existing order extended properties but no extended properties are written as intended, and as my old Curl request does successfully.
If it's of any help, please see booni3/Linnworks post and parse class methods:
public function post($url = null, array $parameters = []): array
{
return $this->parse(function() use($url, $parameters){
return $this->client->post($this->server.$url, [
'form_params' => $parameters,
'headers' => [
'Content-Type' => 'application/x-www-form-urlencoded',
'Accept' => 'application/json',
'Authorization' => $this->bearer ?? ''
]
]);
});
}
private function parse(callable $callback)
{
$response = call_user_func($callback);
$json = json_decode((string) $response->getBody(), true);
if(json_last_error() !== JSON_ERROR_NONE){
throw new LinnworksResponseCouldNotBeParsed((string) $response->getBody());
}
return $json;
}
Any ideas on how to get the form_params to match exactly with my curl request so I can get this to work? I've used this repository successfully with countless other Linnworks API requests already but I'm simply stuck on this one so any help would be greatly appreciated.

Related

Guzzle: Sending POST with Nested JSON to an API

I am trying to hit a POST API Endpoint with Guzzle in PHP (Wordpress CLI) to calculate shipping cost. The route expects a RAW JSON data in the following format:
{
"startCountryCode": "CH"
"endCountryCode": "US",
"products": {
"quantity": 1,
"vid": x //Variable ID
}
}
Link to the API I am consuming: https://developers.cjdropshipping.com/api2.0/v1/logistic/freightCalculate
$body = [
"endCountryCode" => "US",
"startCountryCode" => "CN",
"products" => [
'vid' => $vid,
'quantity' => 1
],
];
$request = $this->client->request(
'POST', 'https://developers.cjdropshipping.com/api2.0/v1/logistic/freightCalculate',
[
'headers' => [
'CJ-Access-Token' => $this->auth_via_cj(), // unnecessary, no auth required. Ignore this header
],
'body' => json_encode( $body )
],
);
I've also tried using 'json' => $body instead of the 'body' parameter.
I am getting 400 Bad Request error.
Any ideas?
Try to give body like this.
"json" => json_encode($body)
I spent so many hours on this to just realise that products is actually expecting array of objects. I've been sending just a one-dimensional array and that was causing the 'Bad Request' error.
In order to fix this, just encapsulate 'vid' and 'quantity' into an array and voila!
You don't need to convert data in json format, Guzzle take care of that.
Also you can use post() method of Guzzle library to achieve same result of request. Here is exaple...
$client = new Client();
$params['headers'] = ['Content-Type' => 'application/json'];
$params['json'] = array("endCountryCode" => "US", "startCountryCode" => "CN", "products" => array("vid" => $vid, "quantity" => 1));
$response = $client->post('https://developers.cjdropshipping.com/api2.0/v1/logistic/freightCalculate', $params);

param is missing or the value is empty laravel

I am creating a user with the api provided. I am using Laravel and trying to store data to smartrmail and docs to create new subscriber is here https://docs.smartrmail.com/en/articles/636615-list-subscribers
Each time i send request i get following error:
Server error: `POST https://go.smartrmail.com/api/v1/lists/1sptso/list_subscribers` resulted in a `500 Internal Server Error` response: {"error":"param is missing or the value is empty: subscribers"}
{"error":"param is missing or the value is empty: subscribers"}
I am using Laravel and my code is here
Route::get('smartrmail',function(){
$headers = [
'Accept' => 'application/json',
'Authorization' => 'token f91715d5-3aac-4db3-a133-4b3a9493a9a4',
'Content-Type' => 'application/json',
];
$client = new GuzzleHttp\Client([
'headers' => $headers
]);
$data = [
"subscribers"=>[
[
"email"=> "vanhalen#example.com",
"first_name"=> "van",
"last_name"=> "halen",
"subscribed"=> true,
]
]
];
$res = $client->request('POST', 'https://go.smartrmail.com/api/v1/lists/1sptso/list_subscribers', [
'form_params' => [
$data
]
]);
return($res);
// echo $res->getStatusCode();
});
Anybody help me to figure out what is wrong here. I am following this docs
https://docs.smartrmail.com/en/articles/636615-list-subscribers
to create a new subscriber
Instead of
'form_params' => [
$data
]
use
'json' => $data
Explanation
You want to send json data (I assume that because you set header 'Content-Type' => 'application/json', which means that you are sending json), but form_params is used for application/x-www-form-urlencoded.
json sets header to application/json and sends data as json.
As you set proper header, this should work too:
'body' => $data
Proper name of param you can find in Guzzle docs, I used uploading data part.

Guzzle 6 PUT request not sending form params

I have the following code that I use whenever I want to make POST requests using Guzzle:
$request = $client->request('POST', $url, [
'form_params' => $params,
'headers' => [
'Referer' => '(intentionally removed)',
'Accept' => 'application/json',
]
]);
The code works without any issues and the information within $params is always sent, however when I change the request type from POST to PUT so that the request becomes:
$request = $client->request('PUT', $url, [
'form_params' => $params,
'headers' => [
'Referer' => '(intentionally removed)',
'Accept' => 'application/json',
]
]);
The request suddenly stops sending the data contained within $params.
I have tested the endpoint the request is send to with Insomnia with both POST and PUT requests and both types are processed as expected, so I am certain the issue is not there.
What can be causing the data from Guzzle to be send using the POST method but not when using the PUT?
This behaviour described in guzzle documentation form-params
form_params - Used to send an application/x-www-form-urlencoded POST request.
Probably, you enough pass the parameters in json format:
$request = $client->request('PUT', $url, [
'json' => $params,
'headers' => [
'Referer' => '(intentionally removed)',
'Accept' => 'application/json',
]
]);

Kubernetes changes content type

I have a service in PHP.
One endpoint calls to other endpoint in these service.
For connection I use guzzle.
Content sent from 1st endpoint to 2nd should be application/json.
On docker it works correctly but when I deploy to Kubernetes,
in logs I see that request content is application/x-www-form-urlencoded.
Even if content type is hardcoded:
private function getPostRequestOptions($postData) : array
{
return [
'headers' => [
'Content-Type' => 'application/json',
'Request-ID' => $this->requestId
],
'body' => json_encode($postData),
'connect_timeout' => static::CONNECT_TIMEOUT,
'timeout' => static::TIMEOUT,
'http_errors' => true,
];
}
public function sendPost(string $path, $postData): \stdClass
{
return $this->executeRequest(
'POST',
$this->getFullUrl($path),
$this->getPostRequestOptions(
$postData
)
);
}
Does someone have any clue why it happens like this?
The problem was Dynatrace monitoring for php 7.
New version of plugin stopped cutting headers.

How do I get the body of SENT data with Guzzle PHP?

I am using Guzzle (v6.1.1) in PHP to make a POST request to a server. It works fine. I am adding some logging functions to log what was sent and received and I can't figure out how to get the data that Guzzle sent to the server. I can get the response just fine, but how do I get the sent data? (Which would be the JSON string.)
Here is the relevant portion of my code:
$client = new GuzzleHttp\Client(['base_uri' => $serviceUrlPayments ]);
try {
$response = $client->request('POST', 'Charge', [
'auth' => [$securenetId, $secureKey],
'json' => [ "amount" => $amount,
"paymentVaultToken" => array(
"customerId" => $customerId,
"paymentMethodId" => $token,
"publicKey" => $publicKey
),
"extendedInformation" => array(
"typeOfGoods" => $typeOfGoods,
"userDefinedFields" => $udfs,
"notes" => $Notes
),
'developerApplication'=> $developerApplication
]
]);
} catch (ServerErrorResponseException $e) {
echo (string) $e->getResponse()->getBody();
}
echo $response->getBody(); // THIS CORRECTLY SHOWS THE SERVER RESPONSE
echo $client->getBody(); // This doesn't work
echo $client->request->getBody(); // nor does this
Any help would be appreciated. I did try to look in Guzzle sourcecode for a function similar to getBody() that would work with the request, but I'm not a PHP expert so I didn't come up with anything helpful. I also searched Google a lot but found only people talking about getting the response back from the server, which I have no trouble with.
You can do this work by creating a Middleware.
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;
$stack = HandlerStack::create();
// my middleware
$stack->push(Middleware::mapRequest(function (RequestInterface $request) {
$contentsRequest = (string) $request->getBody();
//var_dump($contentsRequest);
return $request;
}));
$client = new Client([
'base_uri' => 'http://www.example.com/api/',
'handler' => $stack
]);
$response = $client->request('POST', 'itemupdate', [
'auth' => [$username, $password],
'json' => [
"key" => "value",
"key2" => "value",
]
]);
This, however, is triggered before to receive the response. You may want to do something like this:
$stack->push(function (callable $handler) {
return function (RequestInterface $request, array $options) use ($handler) {
return $handler($request, $options)->then(
function ($response) use ($request) {
// work here
$contentsRequest = (string) $request->getBody();
//var_dump($contentsRequest);
return $response;
}
);
};
});
Using Guzzle 6.2.
I've been struggling with this for the last couple days too, while trying to build a method for auditing HTTP interactions with different APIs. The solution in my case was to simply rewind the request body.
The the request's body is actually implemented as a stream. So when the request is sent, Guzzle reads from the stream. Reading the complete stream moves the stream's internal pointer to the end. So when you call getContents() after the request has been made, the internal pointer is already at the end of the stream and returns nothing.
The solution? Rewind the pointer to the beginning and read the stream again.
<?php
// ...
$body = $request->getBody();
echo $body->getContents(); // -->nothing
// Rewind the stream
$body->rewind();
echo $body->getContents(); // -->The request body :)
My solution for Laravel from 5.7:
MessageFormatter works with variable substitutions, see this: https://github.com/guzzle/guzzle/blob/master/src/MessageFormatter.php
$stack = HandlerStack::create();
$stack->push(
Middleware::log(
Log::channel('single'),
new MessageFormatter('Req Body: {request}')
)
);
$client = new Client();
$response = $client->request(
'POST',
'https://url.com/go',
[
'headers' => [
"Content-Type" => "application/json",
'Authorization' => 'Bearer 123'
],
'json' => $menu,
'handler' => $stack
]
);
You can reproduce the data string created by the request by doing
$data = array(
"key" => "value",
"key2" => "value",
);
$response = $client->request('POST', 'itemupdate', [
'auth' => [$username, $password],
'json' => $data,
]);
// ...
echo json_encode($data);
This will output your data as JSON string.
Documentation at http://php.net/manual/fr/function.json-encode.php
EDIT
Guzzle has a Request and a Response class (and many other).
Request has effectively a getQuery() method that returns an object containing your data as private, same as all other members.
Also you cannot access it.
This is why I think manually encode it is the easier solution.
If you want know what is done by Guzzle, it also have a Collection class that transform data and send it in request.

Categories