sending array via query string in guzzle - php

Guzzle client creates by default from this code
$client->get('https://example.com/{?a}', array('a' => array('c','d')));
this url
https://example.com/?a=c,d
What is the best practice to send array in query string in RESTful application? The question is, how can I determine on the server side whether c,d is a string or an array? Isn't it better to send arrays using square brackets, e.g. a[]=c&a[]=d? How can I set Guzzle to use square brackets? Or it is better to use JSON encoded variables? On the server side I'm using Tonic.

Working Solution:
$vars = array('state[]' => array('Assigned','New'), 'per_page' => $perPage, 'page' => $pageNumber);
$query = http_build_query($vars, null, '&');
$string = preg_replace('/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '=', $query); // state[]=Assigned&state[]=New
$client = new Client([follow instruction to initialize your client ....]);
$response = $client->request('GET', $uri, ['query' => $string]);
Now you have same name parameters in your request.
Dung.
Source: http_build_query with same name parameters

It seems the answer is here.
I wanted to do something like ?status[]=first&status[]=second
You can do this in Guzzle like shown in the link above:
$client = new Client('http://test.com/api');
$request = $client->get('/resource');
$query = $request->getQuery();
$query->set('status', array('first', 'second'));

Guzzle has a helper function you can use called build_query(). This uses PHP's http_build_query().
Here's an example of how to use it:
$params = [
'a[]' => [
'c',
'd'
],
'page' => 1
];
$query = \GuzzleHttp\Psr7\build_query($params);
$response = $client->request('GET', 'https://example.com/', [
'query' => $query
]);

I'm not 100% sure this quite answers the question. But I found the question while looking for how to construct complex queries using Guzzle and none of the answers here were the solution I ended up using. I'm adding it here in case it's ever useful for any other devs.
Using Guzzle 6, you can do this type of request:
$endPoint = "https://example.com";
$queryParams = [
'a' => [
[
"b" => "c"
]
]
];
$options = [
'debug' => true, // so you can see what the request looks like
'query' => $queryParams
];
$client->request('GET', $endPoint, $options);
As a real world example, query params like this:
$queryParams = [
'filters' => [
[
"field" => "status",
"value" => "open",
"operator" => "equal"
],
[
"field" => "total",
"operator" => "greater_than",
"value" => 50
],
],
'limit' => 500,
'start' => 7
];
produce a url like this:
https://example.com?filters=[{"field":"status","operator":"equal","value":"open"},{"field":"total","operator":"less_than","value":50}]&limit=500&start=7
The point being that the query key of the $options array, seems very powerful. I'd recommend having a play with that before going down the route of writing complex regular expressions.

$query = array('x' => array(
'a',
'b',
'c'
));
$query_string = http_build_query($query, null, '&'); //build query string
$query_string = preg_replace('/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '[]=', $query_string); //x[]=a&x[]=b
$response = $guzzle->client->get($path, array('query' => $query_string)); //Make guzzle request
return json_decode($response->getBody()->getContents()); //Return JSON decoded array
This is how you can process x with array of values in guzzle, tested with version 6 or later

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);

Getting API Response using Guzzle in Laravel

I am trying to get a response from the Metals API but keep getting 404 errors even though I can get the API using the URL.
public function valueFromApi(){
$accesskey = "123456";
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://metals-api.com/api/latest', [
'form_params' => [
'access_key' => $accesskey,
'base' => 'GBP',
'symbols' => 'XAU',]
]);
dd($response);
}
If I try and access the URL directly through a browser this works:
https://metals-api.com/api/latest?access_key=123456&base=GBP&symbols=XAU
I must have misunderstood the way the parameters are working. Any advice is appreciated.
Form params is not the same as query parameters. Therefor you need to set the parameters as query. If you are accessing this in the browser, i would not expect it to be a POST but a GET.
$response = $client->request('GET', 'https://metals-api.com/api/latest', [
RequestOptions::QUERY => [
'access_key' => $accesskey,
'base' => 'GBP',
'symbols' => 'XAU',
]
]);
I am using the RequestOptions, this is syntaxic sugar for the hardcoded string options, the same as 'query'.
As specified in their docs, you need to define the constant
define("form_params", GuzzleHttp\RequestOptions::FORM_PARAMS );
Then you can use your code
$response = $client->request('POST', 'https://metals-api.com/api/latest', [
'form_params' => [
'access_key' => $accesskey,
'base' => 'GBP',
'symbols' => 'XAU',]
]);

Symfony HttpClient GET request with multiple query string parameters with same name

I am trying to make an API request in the following format:
/api/v1/courses?enrollment_state=active&include[]=total_students&include[]=term
How can I do so using the HttpClient Component query string parameters?
$response = $client->request('GET', '/api/v1/courses', [
'query' => [
'enrollment_state' => 'active',
'include[]' => 'term',
'include[]' => 'total_students',
],
]);
As the above approach does not work due to duplicate array key?
I have also tried:
'include[]' => ['term', 'total_students']
To create the equivalent to:
https://www.example.com/?token=foo&abc[]=one&abc[]=two
Just do:
$client->request(
'GET',
'https://www.example.com/',
[
'query' => [
'token' => 'foo',
'abc' => ['one', 'two']
]
]
);
as #user1392897 says, #yivi snippet returns indexes in the url query string.
https://www.example.com/?foo[0]=bar&foo[1]=baz
That's because it use the http_build_query built in function and it's the function behaviour. You can read this thread php url query nested array with no index about it.
A workaround it to build the query string yourself from the array and append it to your url, the the 2nd parameter of the HttpClient->request() method.
function createQueryString(array $queryArray = []): ?string
{
$queryString = http_build_query($queryArray, '', '&', \PHP_QUERY_RFC3986);
$queryString = preg_replace('/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '%5B%5D=', $queryString); //foo[]=x&foo[]=y
return '' !== $queryString ? $queryString : null;
}
$queryArray = [
'abc' => ['one', 'two']
];
$queryString = createQueryString($queryArray);
$url = 'https://www.example.com/';
if (is_string($queryString)) {
$url = sprintf('%s?%s', $url, $queryString);
}
$response = $client->request('GET', $url);

ElasticSearch PHP SDK search returning null on match_all query

I recently tried to use ES. So I set it up in a cloud 9 environnement. I inserted data using a curl request file and I can see them with
http://mydomain/ingredients/aliments/_search?size=350&pretty=true
I then tried to set up elastic SDK (v.2.0) with Silex but I can't get the same output...
Here is my code :
$client = $app['elasticsearch'];
$params = array(
'size' => 350,
'index' => 'ingredients',
'type'=>'aliment',
'body' => array(
'query'=>array(
'match_all' => new \stdClass()
)
)
);
$ingredients = $client->search($params);
The output is NULL but when I do the following :
$params = array(
'index' => 'ingredients',
'type' => 'aliment'
);
$count = $client->count($params);
The output is as expected : {"count":240,"_shards":{"total":5,"successful":5,"failed":0}}
I've already spent a few hours trying to figure what's going on, I tried to replace the 'query' args with a json string, I tried empty array instead of the new stdClass but nothing seems to work.
Edit : I read the documentation again and tried the official example :
$client = $app['elasticsearch'];
$params = [
"search_type" => "scan", // use search_type=scan
"scroll" => "30s", // how long between scroll requests. should be small!
"size" => 50, // how many results *per shard* you want back
"index" => "ingredients",
"body" => [
"query" => [
"match_all" => []
]
]
];
$output = $client->search($params);
$scroll_id = $output['_scroll_id']; /*<<<This works****/
while (\true) {
// Execute a Scroll request
$response = $client->scroll([
"scroll_id" => $scroll_id, //...using our previously obtained _scroll_id
"scroll" => "30s" // and the same timeout window
]
);
var_dump($response); /*<<<THIS IS NULL****/
...
}
And unfortunately got same null result...
What am I doing wrong ?
Thanks for reading.
In my case it works this way:
$json = '{
"query": {
"match_all": {}
}
}';
$params = [
'type' => 'my_type',
'body'=> $json
];
I found out that the inserted data was malformed.
Accessing some malformed data via browser URL seems to be OK but not with a curl command line or the SDK.
Instead of {name:"Yaourt",type:"",description:""} , I wrote {"name":"Yaourt","description":""} in my requests file and now everything work as expected !
#ivanesi 's answer works. You may also try this one:
$params["index"] = $indexName;
$params["body"]["query"]["match_all"] = new \stdClass();

How do I configure default query parameters with Guzzle 6?

Migrating from 5 to 6, and I've run into a snag and can't find the relevant docs.
Guzzle docs here, http://guzzle.readthedocs.io/en/latest/quickstart.html#creating-a-client, site that we can add "any number of default request options".
I want to send "foo=bar" with every request. E.g.:
$client = new Client([
'base_uri' => 'http://google.com',
]);
$client->get('this/that.json', [
'query' => [ 'a' => 'b' ],
]);
This will generate GET on http://google.com/this/that.json?a=b
How do I modify the client construction so that it yields:
http://google.com/this/that.json?foo=bar&a=b
Thanks for your help!
Alright, so far, this works here:
$extraParams = [
'a' => $config['a'],
'b' => $config['b'],
];
$handler = HandlerStack::create();
$handler->push(Middleware::mapRequest(function (RequestInterface $request) use ($extraParams) {
$uri = $request->getUri();
$uri .= ( $uri ? '&' : '' );
$uri .= http_build_query( $extraParams );
return new Request(
$request->getMethod(),
$uri,
$request->getHeaders(),
$request->getBody(),
$request->getProtocolVersion()
);
}));
$this->client = new Client([
'base_uri' => $url,
'handler' => $handler,
'exceptions' => false,
]);
If anyone knows how to make it less sinister-looking, I would say thank you!
I found a nice solution here.
Basically, anything defined in the first array of arguments, become part of the config for the client.
this means you can do this when initialising:
$client = new Client([
'base_uri' => 'http://google.com',
// can be called anything but defaults works well
'defaults' => [
'query' => [
'foo' => 'bar',
]
]
]);
Then, when using the client:
$options = [
'query' => [
'nonDefault' => 'baz',
]
];
// merge non default options with default ones
$options = array_merge_recursive($options, $client->getConfig('defaults'));
$guzzleResponse = $client->get('this/that.json', $options);
It's woth noting that the array_merge_recursive function appends to nested arrays rather than overwrites. If you plan on changing a default value, you'll need a different utility function. It works nicely when the default values are immutable though.
A "less sinister-looking" example based on the answer by #Saeven and the comment from #VladimirPak.
$query_defaults = [
'a' => $config['a'],
'b' => $config['b'],
];
$handler = \GuzzleHttp\HandlerStack::create();
$handler->push(\GuzzleHttp\Middleware::mapRequest(function (\Psr\Http\Message\RequestInterface $request) use ($query_defaults) {
$query = \GuzzleHttp\Psr7\Query::parse($request->getUri()->getQuery());
$query = array_merge($query_defaults, $query);
return $request->withUri($request->getUri()->withQuery(\GuzzleHttp\Psr7\Query::build($query)));
}));
$this->client = new \GuzzleHttp\Client([
'base_uri' => $url,
'handler' => $handler,
'exceptions' => false,
]);
I'm not sure how less sinister-looking it is though. lol
the solution proposed in github looks pretty ugly. This does not look much better, but at least is more readable and also works. I'd like feedback if anyone knows why should not be used:
$query = $uri . '/person/id?personid=' . $personid . '&name=' . $name;
return $result = $this->client->get(
$query
)
->getBody()->getContents();

Categories