backend2-plus/connect-rest-api-bundle
## Getting Started
### Minimal Setup
1. **Installation**:
```bash
composer require backend2-plus/connect-rest-api-bundle
The bundle auto-registers in Symfony 7+/8.0.
Configure .env:
CONNECT_REST_API_USERNAME=your_api_username
CONNECT_REST_API_PASSWORD=your_api_password
Basic Usage:
Inject ConnectRestApiService into any controller/service:
use Backend2Plus\ConnectRestApiBundle\Service\ConnectRestApiService;
public function __construct(private ConnectRestApiService $apiClient) {}
// Example GET request
$response = $this->apiClient->get('https://api.example.com/data');
$data = json_decode($response->getContent(), true);
// In a controller/service
$users = $this->apiClient->get('https://api.example.com/users')->toArray();
return $this->json($users);
Pattern: Use the service in dedicated API service classes to abstract API calls.
// src/Service/ExternalApiService.php
class ExternalApiService {
public function __construct(private ConnectRestApiService $apiClient) {}
public function fetchUser(int $id): array {
return $this->apiClient->get("https://api.example.com/users/{$id}")->toArray();
}
}
Pattern: Create DTOs for API responses to enforce structure.
// src/DTO/UserDto.php
class UserDto {
public function __construct(
public string $id,
public string $name,
public string $email
) {}
}
// Usage
$response = $this->apiClient->get('https://api.example.com/users/1');
$userDto = new UserDto(
$response['id'],
$response['name'],
$response['email']
);
Pattern: Override bundle config per environment (e.g., config/packages/dev/connect_rest_api.yaml):
connect_rest_api:
username: '%env(resolve:DEV_API_USERNAME)%'
password: '%env(resolve:DEV_API_PASSWORD)%'
base_uri: 'https://dev-api.example.com'
Pattern: Centralize error handling in a middleware or interceptor.
// src/EventListener/ApiErrorListener.php
class ApiErrorListener {
public function onKernelException(GetResponseForExceptionEvent $event) {
$exception = $event->getThrowable();
if ($exception instanceof \Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface) {
$event->setResponse(new Response('API Error', 400));
}
}
}
Pattern: Mock ConnectRestApiService in tests using Symfony’s HTTP Client mocks.
// tests/Service/ExternalApiServiceTest.php
public function testFetchUser() {
$mockResponse = new Response(json_encode(['id' => 1, 'name' => 'Test']));
$httpClient = $this->createMock(HttpClientInterface::class);
$httpClient->method('request')->willReturn($mockResponse);
$service = new ExternalApiService($httpClient);
$result = $service->fetchUser(1);
$this->assertEquals('Test', $result['name']);
}
Gotcha: Hardcoding credentials in config/packages/connect_rest_api.yaml bypasses .env overrides.
Fix: Always use %env() placeholders:
connect_rest_api:
username: '%env(CONNECT_REST_API_USERNAME)%'
Tip: For multi-environment setups, use env() in PHP:
$this->apiClient->connector('GET', $url, [], [
'auth_basic' => [$this->getEnv('API_USERNAME'), $this->getEnv('API_PASSWORD')]
]);
Gotcha: The bundle uses Symfony’s HttpClient, which may throw ClientExceptionInterface for 4xx/5xx responses.
Fix: Catch specifically:
try {
$response = $this->apiClient->get($url);
} catch (ClientExceptionInterface $e) {
if ($e->getStatusCode() === 404) {
// Handle 404
}
}
Tip: Disable SSL verification for testing (not production!):
$response = $this->apiClient->connector('GET', $url, [], [
'verify_peer' => false,
]);
Gotcha: Reusing the same HttpClient instance (default behavior) can cause connection pooling issues.
Fix: Configure the client per request if needed:
$response = $this->apiClient->connector('GET', $url, [], [
'http_client' => HttpClient::create(['timeout' => 30]),
]);
Tip: Cache responses for read-heavy APIs:
$cacheKey = 'api_users_' . md5($url);
if (!$cached = $this->cache->get($cacheKey)) {
$cached = $this->apiClient->get($url)->toArray();
$this->cache->set($cacheKey, $cached, 3600);
}
// src/Service/ResilientApiService.php
class ResilientApiService extends ConnectRestApiService {
public function resilientGet(string $url, int $retries = 3): Response {
$attempt = 0;
while ($attempt < $retries) {
try {
return parent::get($url);
} catch (TransportExceptionInterface $e) {
$attempt++;
if ($attempt === $retries) throw $e;
sleep(2 ** $attempt); // Exponential backoff
}
}
}
}
Tip: Enable HTTP Client debug mode in config/packages/dev/connect_rest_api.yaml:
connect_rest_api:
debug: true
Logs requests/responses to var/log/dev.log.
Gotcha: Headers like Content-Type: application/json are auto-set for non-GET requests. Override with:
$this->apiClient->connector('POST', $url, $data, [
'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'],
]);
ParameterBag for runtime overrides:
$this->apiClient->setCredentials(
$this->container->getParameter('api.username'),
$this->container->getParameter('api.password')
);
| Error | Cause | Solution |
|---|---|---|
InvalidArgumentException |
Invalid HTTP method (e.g., PATCH) |
Use uppercase methods ('PATCH', not 'patch'). |
TransportExceptionInterface |
Network timeout | Increase timeout in options. |
ClientExceptionInterface |
4xx/5xx HTTP status | Check $response->getStatusCode(). |
JsonException |
Malformed JSON response | Validate API response structure. |
---
How can I help you explore Laravel packages today?