Without Laravel

The client is plain PHP with no dependencies. The default transport uses the cURL extension.

Build the client

use Darvis\BullhornApi\Client;
use Darvis\BullhornApi\Config;
use Darvis\BullhornApi\Token\FileTokenStore;

$bullhorn = new Client(
    new Config(
        clientId: 'your-client-id',
        clientSecret: 'your-client-secret',
        username: 'your.api.user',
        password: 'the-password',
    ),
    store: new FileTokenStore('/var/lib/myapp/bullhorn'),
);

$candidate = $bullhorn->entity('Candidate', 123, 'id,firstName');

Config::fromArray() reads the same keys as the Laravel config file (client_id, client_secret, username, password, timeout …). Client::fromEnv() reads the BULLHORN_ variables from Installation:

$bullhorn = Client::fromEnv(new FileTokenStore('/var/lib/myapp/bullhorn'));

Keep the session between requests

Without a store, the client keeps the session in memory, and every PHP request logs in again: four calls before the first real one. Bullhorn limits logins and may block an API user that logs in too often. A website needs a store that all requests share.

FileTokenStore writes one file per API user:

  • The directory must exist, be writable, and be an absolute path. A relative path would depend on the working directory, which differs between the web server and the command line.
  • In a web request it refuses a directory inside the document root, where the file could be downloaded. On the command line there is no document root to check, so choose the directory with care.
  • The files hold the session token and the refresh token unencrypted and are created with mode 0600. Keep the directory readable for the PHP user only.
  • A file is written in full and then renamed into place, so another request never reads half a file. A lock file makes sure one process at a time logs in or renews.
  • When the directory cannot be written, the client goes on and writes one line to the PHP error log. The next request logs in again.

Your own store

A store implements Darvis\BullhornApi\Token\TokenStore: get(), put(), forget() and synchronized(). Two rules matter:

  • get() returns what the shared backend holds now, not a copy on the instance. After a 401 the client reads again to find a session another process made.
  • synchronized() holds a lock across processes while it runs the callback, and runs it without the lock when the lock does not come free in time.

Your own transport

A transport implements Darvis\BullhornApi\Http\Transport: one send() that returns the status, the headers and the body. It must not follow redirects (the OAuth code arrives in one), must not retry, and throws only when there was no response.