developersnl/bullhorn-client-bundle
Installation Run:
composer require developersnl/bullhorn-client-bundle
For non-Flex projects, manually add the bundle to config/bundles.php.
Configuration
Create config/packages/bullhorn_client.yaml with required credentials:
bullhorn_client:
authentication:
clientId: 'your_client_id'
clientSecret: 'your_client_secret'
authUrl: 'https://auth-emea.bullhornstaffing.com/oauth/authorize'
tokenUrl: 'https://auth-emea.bullhornstaffing.com/oauth/token'
loginUrl: 'https://rest-emea.bullhornstaffing.com/rest-services/login'
rest:
username: 'your_username'
password: 'your_password'
First Use Case Inject the client into a service/controller and call an endpoint:
use Developersnl\BullhornClientBundle\Client\BullhornClient;
class CandidateController extends Controller
{
public function __construct(private BullhornClient $client) {}
public function index()
{
$candidates = $this->client->get('/candidates');
return response()->json($candidates);
}
}
Authentication Flow The bundle handles OAuth2 token acquisition automatically. Avoid manual token management:
// No need to manually fetch tokens; the client handles it.
$this->client->get('/candidates'); // Token is auto-refreshed if expired.
CRUD Operations Use standard HTTP methods for REST operations:
// Create
$newCandidate = $this->client->post('/candidates', $data);
// Read
$candidate = $this->client->get('/candidates/123');
// Update
$this->client->put('/candidates/123', $updatedData);
// Delete
$this->client->delete('/candidates/123');
Query Parameters Pass arrays for filtering/pagination:
$this->client->get('/candidates', [
'fields' => ['firstName', 'lastName', 'email'],
'pageSize' => 50,
'pageNumber' => 1
]);
Response Handling
Responses are decoded JSON by default. Use getResponse() for raw responses:
$response = $this->client->get('/candidates');
$data = $response->getData(); // Decoded JSON
$status = $response->getStatusCode();
Service Layer Pattern Create a dedicated service class to encapsulate Bullhorn logic:
class BullhornCandidateService
{
public function __construct(private BullhornClient $client) {}
public function findByEmail(string $email): ?array
{
$candidates = $this->client->get('/candidates', [
'email' => $email,
'pageSize' => 1
]);
return $candidates['results'][0] ?? null;
}
}
Event Listeners Extend the client for custom logic (e.g., logging, retries):
$this->client->on('request', function ($request) {
logger()->debug('Bullhorn Request:', ['url' => $request->getUri()]);
});
Dependency Injection Prefer constructor injection over manual instantiation:
// ❌ Avoid
$client = new BullhornClient();
// ✅ Prefer
public function __construct(private BullhornClient $client) {}
Token Expiry
clientSecret and clientId are correct.storage/logs/laravel.log for TokenExpiredException errors.Endpoint URLs
rest-emea.bullhornstaffing.com) may not work for US regions. Override in config:
bullhorn_client:
authentication:
authUrl: 'https://auth.bullhornstaffing.com/oauth/authorize' # US region
Rate Limiting
$candidates = Cache::remember('bullhorn_candidates', now()->addMinutes(5), function () {
return $this->client->get('/candidates');
});
Field Selection
fields in queries returns all fields, which can bloat responses. Always specify:
$this->client->get('/candidates', ['fields' => ['id', 'firstName']]);
Enable Verbose Logging
Add to config/logging.php:
'channels' => [
'bullhorn' => [
'driver' => 'single',
'path' => storage_path('logs/bullhorn.log'),
'level' => 'debug',
],
],
Then configure the client to use this channel:
bullhorn_client:
logging_channel: 'bullhorn'
Inspect Raw Requests Use a middleware to log requests/responses:
$this->client->on('request', function ($request) {
logger()->debug('Request:', [
'method' => $request->getMethod(),
'url' => (string) $request->getUri(),
'body' => $request->getBody()->getContents(),
]);
});
Custom Headers Add headers globally via config:
bullhorn_client:
headers:
'X-Custom-Header': 'value'
Or per-request:
$this->client->get('/candidates', [], [
'headers' => ['X-Custom-Header' => 'value']
]);
Response Transformers
Extend the Response class to modify decoded data:
$this->client->on('response', function ($response) {
$response->setData($this->transformResponse($response->getData()));
});
Mocking for Tests
Use the MockHttpClient for unit tests:
$mockClient = new MockHttpClient();
$mockClient->shouldReceive('get')->once()->andReturn(['data' => 'mocked']);
$this->app->instance(BullhornClient::class, $mockClient);
Async Operations For long-running tasks, use Laravel Queues:
dispatch(new SyncBullhornCandidates($this->client));
class SyncBullhornCandidates implements ShouldQueue
{
public function handle(BullhornClient $client) {
$client->get('/candidates'); // Runs asynchronously
}
}
How can I help you explore Laravel packages today?