Use Laravel's HTTP Client with Facebook's Business SDK
Tags: php, laravel, facebook
Published: 2022-05-30 17:48:57

---
title: Use Laravel's HTTP Client with Facebook's Business SDK
published: true
date: 2022-05-29 12:00:00 UTC
tags: #php #laravel #facebook
canonical_url: https://stefanzweifel.dev/posts/2022/05/29/use-laravels-http-client-with-facebooks-business-sdk
---

Recently, I had to integrate the Facebook Marketing API into a project at work. The app not only needed to be able to login with Facebook – solved by using [Laravel Socialite](https://laravel.com/docs/master/socialite) – but also make requests to various Facebook API endpoints to fetch data about pages of a business.

Facebook provides a PHP SDK for their "Marketing API" called [`facebook/php-business-sdk`](https://github.com/facebook/facebook-php-business-sdk) <sup id="fnref:1"><a href="#fn:1" role="doc-noteref">1</a></sup>. (Naming something consistently is a thing Facebook seems to struggle with. The SDK is also available as `facebook/php-ads-sdk`)

Anyhow, once the Business SDK is installed in your project, you pass your app ID, app secret and a valid access token to `Api::init()` and you're good to go.

```php
use FacebookAds\Api;
use FacebookAds\Object\AdAccount;
use FacebookAds\Object\Fields\AdAccountFields;

Api::init($app_id, $app_secret, $access_token);

$fields = [
  AdAccountFields::ID,
  AdAccountFields::NAME,
];

$account = (new AdAccount($account_id))->getSelf($fields);
```

But how would you start writing tests for your code that interacts with the Facebook API? The SDK doesn't use Laravel's [HTTP client](https://laravel.com/docs/master/http-client). You can't just call `Http::fake()` and start writing your tests. The requests would still hit Facebook's live servers.

After digging through the source code of the package, I noticed that Facebook follows the [Adapter](https://refactoring.guru/design-patterns/adapter)-pattern and allows us to use our own class to make the HTTP requests to their API. Meaning we can create our own Adapter that makes the requests with Laravel's HTTP client and in doing so, allows us to use `Http::fake()` in our tests.

I've create a new `LaravelHttpAdapter`-class under `App\Domain\Facebook` that extends `FacebookAds\Http\Adapter\AbstractAdapter` and implements `FacebookAds\Http\Adapter\AdapterInterface`.

The code looks like this:

```php
namespace App\Domain\Facebook;

use FacebookAds\Http\Adapter\AbstractAdapter;
use FacebookAds\Http\Adapter\AdapterInterface;
use FacebookAds\Http\Headers;
use FacebookAds\Http\RequestInterface;
use FacebookAds\Http\Response;
use FacebookAds\Http\ResponseInterface;
use Illuminate\Support\Facades\Http;

class LaravelHttpAdapter extends AbstractAdapter implements AdapterInterface
{
    protected \ArrayObject $opts;

    public function getOpts()
    {
        return $this->opts;
    }

    public function setOpts(\ArrayObject $opts)
    {
        $this->opts = $opts;
    }

    public function sendRequest(RequestInterface $request): ResponseInterface
    {
        $body = $request->getBodyParams()->export();

        $httpClient = Http::withHeaders($request->getHeaders()->getArrayCopy());

        if ($request->getMethod() === 'POST') {
            $response = $httpClient->post($request->getUrl(), $body);
        } else {
            $response = $httpClient->send($request->getMethod(), $request->getUrl());
        }

        $facebookResponse = new Response();
        $headers = new Headers($response->headers());
        $facebookResponse->setBody($response->body());
        $facebookResponse->setHeaders($headers);

        return $facebookResponse;
    }
}

```

The `sendRequest`-method is where the magic happens. From the `RequestInterface` object we extract the URL, headers, HTTP method and body and pass it on to Laravel's HTTP client.<sup id="fnref:2"><a href="#fn:2" role="doc-noteref">2</a></sup>  
The response from the HTTP client is then turned into a `Response`-object that implements Facebook's `ResponseInterface`.

Alright. Next we have to tell the Facebook SDK to use our Adapter class. For this I've created an `InteractsWithFacebookApi`-trait. There are multiple places in our app where requests to Facebook are made, so it made sense to extract this bit of code.

Here's how this trait looks like.

```php
namespace App\Domain\Facebook\Concerns;

use App\Domain\Facebook\LaravelHttpAdapter;
use FacebookAds\Api;
use FacebookAds\Http\Client;
use Illuminate\Support\Facades\Http;

trait InteractsWithFacebookApi
{
    public function setupFacebookApi(string $accessToken)
    {
        // Default Init method to create new Facebook API Instance
        $api = Api::init(
            config('services.facebook.client_id'),
            config('services.facebook.client_secret'),
            $accessToken
        );

        // Build new API instance where Laravel's HTTP client
        // is used to make HTTP requests to Facebooks API.
        // Allows us to use Http::fake() in tests.
        $httpClient = new Client();
        $httpClient->setAdapter(new LaravelHttpAdapter($httpClient));
        $newApiInstance = new Api($httpClient, $api->getSession());
        
        $api::setInstance($newApiInstance);
    }
}

```

The `setupFacebookApi`-method accepts an access token and inits the API as documented in the SDKs README.  
As the SDK doesn't use any sort of IOC-container, we have to manually build a new instance of Facebook's internal HTTP client class. We then call the `setAdapter()`-method on the `Client`-object and pass an instance of our `LaravelHttpAdapter`-class to it.

Next we create a new API instance using the updated HTTP client and the existing credentials. To finish things off, we call `setInstance()` on the existing API instance and tell the SDK to use our new API instance.

## Writing Tests[#](#content-writing-tests "Permalink")

Writing tests for code that interacts with the Facebook API is now a piece of cake.

The test below asserts that an `ActivateAdCampaignAction`-class makes a request to the Facebook API endpoint, sends `['status' => 'ACTIVE']` and returns `['success' => true]`. All without ever hitting a real server or writing mocks for the Facebook SDK.

```php
/** @test */
public function it_makes_http_request_to_facebook_to_activate_given_ad_campaign()
{
    Http::fake([
        'https://graph.facebook.com/*' => Http::response(['success' => true]),
    ]);
    
    $adCampaign = AdCampaign::factory()->create([
        'active' => 0,
    ]);

    app(ActivateAdCampaignAction::class)->execute($adCampaign);

    Http::assertSent(function (Request $request) {
        return $request['status'] === 'ACTIVE';
    });
}

```

Of course you could go even further.  
The `Http::fake()`-call currently contains a wildcard check (`https://graph.facebook.com/*`) and not a check for a specific API endpoint (`https://graph.facebook.com/v13.0/{adCampaignId}`).

Or the response passed to `Http::response()` is hard coded in the test case. If you need the response multiple times you could extract that and create an `AdCampaignResponses`-class. It could have a `successfullUpdate()`-method on it that returns the data.

* * *

I despise working with Facebook and their API. Their documentation is ridiculously bad, their developer portal regularly shows out of date information or just doesn't work at all. And of course the company itself is shady AF.

Despite all that, Facebook (still) dominates the ad market in my region and businesses use it to attract customers.  
At least I could figure out a clean way to use their SDK and writing tests familiar to Laravel developers.

* * *

1. Fun Fact: Their PHP SDK to interact with their graph API has been deprecated is not compatible with PHP 8. That's coming from a company worth billions of USD, thousands of engineers and which was built on PHP. [↩](#fnref:1)

2. The project I'm working on only makes GET and POST requests to Facebook's API. So this implementation might break if you have to make PATCH, PUT or DELETE requests. Feel free to send me your patches. [↩](#fnref:2)