SeanKimball's avatar

Guzzle vs. Http Facade trying to consume CiviCRM APIv4

Having a beast of a time getting the Http facade to pass params to CiviCRM API correctly. I can get it to communicate properly if I use Guzzle directly (which is probably the sensible thing to do, but I am committed now) What am I doing wrong? Is there a way to tell Http::post to NOT post using Json (so I can manually encode it?) ?

More details in the comments int he code.

My question is: "Why is the Http facade not working as expected?"

public function makeRequest($entity, $action, $params = [])
    {
        echo '<pre>';

        $params = [
            'select' => ['id'],
            'limit' => 2,
        ];

        /** example taken from CIVICRM API expolrer, but not working
         * it returns all results, not just the first 2
         * note, it is using a get request, not a post request
         * User-Agent: GuzzleHttp/7
         * Content-Type: application/x-www-form-urlencoded
         * Content-Length: 60
         */
        $client = new \GuzzleHttp\Client([
            'base_uri' => $this->endpoint,
            'headers' => ['X-Civi-Auth' => 'Bearer ' . $this->apiKey],
            'debug' => true,
        ]);
        $response = $client->get('LocationType/get', [
            'form_params' => ['params' => json_encode($params)],
        ]);
        $locationTypes = json_decode((string) $response->getBody(), TRUE);

        dump($locationTypes); // dumps 5 results (all of them)



        /**
         * This examples uses the post method and returns the first 2 results correctly
         * It's user agent, content type and length are the same as above
         * User-Agent: GuzzleHttp/7
         * Content-Type: application/x-www-form-urlencoded
         * Content-Length: 60
         */
        $client = new \GuzzleHttp\Client([
            'base_uri' => $this->endpoint,
            'headers' => ['X-Civi-Auth' => 'Bearer ' . $this->apiKey],
            'debug' => true,
        ]);
        $response = $client->post('LocationType/get', [
            'form_params' => ['params' => json_encode($params)],
        ]);
        $locationTypes = json_decode((string) $response->getBody(), TRUE);

        dump($locationTypes); // shows the correct number of results

        /**
         * This is a complete mystery it always returns 5 results
         * regardless of the way parms are passed in
         * it always has a shorter content length of 27
         * content type does not matter if set to json or form
         */
        $response = Http::dump()->withHeaders([
            'X-Civi-Auth' => 'Bearer ' . $this->apiKey,
            'Content-Type' => 'application/x-www-form-urlencoded',
            'debug' => true,
        ])->post($this->endpoint . 'LocationType/get', [
            // does not work, returns 5 results
            // 'form_params' => ['params' => json_encode($params)], 

            // I think this should be correct, but it returns 5 results
            'params' => json_encode($params), 

            // I am just guessing at this point....... 5 results
            // 'params' => $params,  // nope
            // $params, // nope
            // 'json' => $params, // nope
            // 'limit' => 1, // nope
            // 'select' => ['id'], // nope
        ]);

        dd($response->object());
    }
0 likes
6 replies
LaryAI's avatar
Level 58

It seems like the issue you're encountering is related to how you're sending the parameters with the Laravel Http facade. When using the Http facade, you don't need to manually encode the parameters as JSON or use the form_params key like you do with Guzzle. Instead, you should pass the parameters directly to the post method.

Here's how you can modify your Http facade request to work similarly to your Guzzle post request:

public function makeRequest($entity, $action, $params = [])
{
    $params = [
        'select' => ['id'],
        'limit' => 2,
    ];

    // ...

    /**
     * When using the Http facade, pass the parameters directly.
     * The Http facade will automatically convert the array to a query string
     * for a form request, or to JSON if you use the ->asJson() method.
     */
    $response = Http::withHeaders([
        'X-Civi-Auth' => 'Bearer ' . $this->apiKey,
        'Accept' => 'application/json', // Ensure we're accepting JSON
    ])->post($this->endpoint . 'LocationType/get', [
        'json' => $params, // Use 'json' key to indicate JSON payload
    ]);

    // Decode the JSON response
    $locationTypes = $response->json();

    dd($locationTypes);
}

In this example, I've used the json key to indicate that the parameters should be sent as a JSON payload. The Accept header is set to application/json to indicate that we expect a JSON response.

If the API expects the parameters to be sent as form parameters, you can simply pass the array to the post method without the json key:

$response = Http::withHeaders([
    'X-Civi-Auth' => 'Bearer ' . $this->apiKey,
])->post($this->endpoint . 'LocationType/get', $params);

In this case, the Http facade will send the request with a Content-Type of application/x-www-form-urlencoded, and the parameters will be encoded as a URL-encoded query string.

Make sure to adjust the endpoint URL and headers according to the API's expectations. If the API requires a GET request with query parameters, you would use the get method instead of post and pass the parameters as a query string.

Remember to check the API documentation for CiviCRM to ensure you're sending the request in the format that the API expects.

SeanKimball's avatar

@LaryAI

In this case, the Http facade will send the request with a Content-Type of application/x-www-form-urlencoded,

No, it always sends the content type of json unless you specify something different

michaelaerni's avatar

@seankimball I'm running into the same issue. I always get all the results returned, unless I use a POST request. And I can't make it work with Laravel's Http Facade, either. Did you find a way to use the Http Facade?

SeanKimball's avatar

@michaelaerni No I never solved it - CiviCRM APIv4 explorer is wrong - these are supposed to be post requests, the docs say GET. I gave up on the facade and just started using Guzzle natively in a service. Frankly, the Http facade does not really offer that much more useful functionality.

michaelaerni's avatar

I found the solution. This is how you can make a successful API call using the Http Facade. Works with both GET and POST request. The key is adding the asForm method. Here's an example that gets 10 events.

$response = Http::withHeaders([
  'X-Civi-Auth' => 'Bearer ' . $this->apiKey,
])
  ->asForm()
  ->get($this->endpoint . '/Event/get', [
    "params" => json_encode([
      "select" => ["title"],
      "limit" => 10
    ])
  ])
  ->json()
1 like
SeanKimball's avatar

@michaelaerni interesting ... I could swear up and down that I had tried the asForm method. I would think you don't need the json() method, and I would assume that get or post both work because using the asForm would send a post no matter what ... but that bit is a guess. I do not know.

but thanks! I will certainly give that another test tomorrow!

Please or to participate in this conversation.