Installation:
composer require answear/fan-courier-bundle
The bundle auto-registers in config/bundles.php.
Configuration:
Add credentials to config/packages/answear_fancourier.yaml:
answear_fan_courier:
username: "your_api_username"
password: "your_api_password"
apiUrl: "https://api.fancourier.ro/api/v2/" # Verify with API docs
logger: "app.logger.fancourier" # Optional (default: null)
First Use Case: Fetch pickup points in a service:
use Answear\FanCourierBundle\Service\PickupPointService;
$pickupPoints = $this->pickupPointService->getAll();
// Returns array of `PickupPointDTO` objects
Answear\FanCourierBundle\Service\PickupPointService (core functionality)Answear\FanCourierBundle\DTO\PickupPointDTO and AddressDTO (data structure)Fetching Pickup Points:
// Basic usage
$points = $pickupPointService->getAll();
// Filter by city (if API supports it)
$points = $pickupPointService->getFiltered(['city' => 'Bucharest']);
Integration with Laravel:
config/services.php:
'services' => [
'fancourier.pickup' => Answear\FanCourierBundle\Service\PickupPointService::class,
],
public function __construct(
private PickupPointService $pickupPointService
) {}
Data Transformation:
Convert PickupPointDTO to Eloquent models:
$pickupPoint = PickupPoint::create([
'id' => $dto->getId(),
'name' => $dto->getName(),
'address' => json_encode($dto->getAddress()->toArray()),
]);
Caching Responses: Cache API responses for 1 hour (e.g., using Laravel Cache):
$cacheKey = 'fancourier.pickup_points';
$points = Cache::remember($cacheKey, now()->addHours(1), function () {
return $this->pickupPointService->getAll();
});
Error Handling: Wrap API calls in try-catch:
try {
$points = $pickupPointService->getAll();
} catch (\Answear\FanCourierBundle\Exception\ApiException $e) {
Log::error('FanCourier API Error: ' . $e->getMessage());
throw new \RuntimeException('Failed to fetch pickup points.', 0, $e);
}
Event-Driven Updates: Trigger events when pickup points are fetched/updated:
event(new PickupPointsFetched($points));
Queue Background Jobs: Offload API calls to queues:
dispatch(new FetchPickupPointsJob($pickupPointService));
Custom API Endpoints: Extend the service to support undocumented endpoints:
class CustomPickupPointService extends PickupPointService {
public function getByPostalCode(string $postalCode): array {
return $this->callApi('custom/endpoint', ['postalCode' => $postalCode]);
}
}
API URL Mismatch:
apiUrl in config must match the exact base URL from the API docs (e.g., https://api.fancourier.ro/api/v2/).Authentication Failures:
username/password throws a silent ApiException.logger config) and check Symfony logs for 401 Unauthorized errors.curl -u "username:password" "https://api.fancourier.ro/api/v2/pickup-points"
DTO Structure Changes:
PickupPointDTO structure changed in v2.0.0 (e.g., address moved to a nested AddressDTO).// Old (v1.x)
$city = $dto->getCity();
// New (v2.x)
$city = $dto->getAddress()->getCity();
Rate Limiting:
$this->pickupPointService->setClientConfig(['timeout' => 30]);
Logger Configuration:
Psr\Log\LoggerInterface.logger: "monolog.logger.fancourier"
Or create a dedicated channel in config/logging.php.Enable Debug Mode:
Set APP_DEBUG=true in .env to see raw API responses in logs.
Inspect HTTP Requests: Use Guzzle middleware to log requests:
$stack = HandlerStack::create();
$stack->push(Middleware::tap(function (RequestInterface $request) {
Log::debug('FanCourier Request:', ['url' => (string) $request->getUri()]);
}));
$this->pickupPointService->setClient($client->withHandler($stack));
Validate API Responses:
FanCourier may return non-standard JSON. Use json_last_error() to debug:
$response = $this->pickupPointService->callApi('endpoint');
$data = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \RuntimeException('Invalid JSON: ' . json_last_error_msg());
}
Custom HTTP Client: Replace the default Guzzle client:
$client = new \GuzzleHttp\Client(['timeout' => 10]);
$this->pickupPointService->setClient($client);
Add New Endpoints:
Extend PickupPointService to support undocumented APIs:
class ExtendedPickupPointService extends PickupPointService {
public function getByCoordinates(float $lat, float $lng): array {
return $this->callApi('custom/coordinates', [
'latitude' => $lat,
'longitude' => $lng,
]);
}
}
Override DTOs: Create custom DTOs to map API responses to your domain:
class CustomPickupPointDTO {
public function __construct(private array $data) {}
public function getFormattedAddress(): string {
return implode(', ', [
$this->data['address']['city'],
$this->data['address']['street'],
]);
}
}
Add Rate Limiting: Implement a decorator to enforce rate limits:
class RateLimitedPickupPointService {
public function __construct(private PickupPointService $service) {}
public function getAll(): array {
if ($this->isRateLimited()) {
sleep(60); // Wait 1 minute
}
return $this->service->getAll();
}
}
apiUrl Trailing Slash:
Ensure the URL ends with / (e.g., api/v2/ not api/v2). FanCourier’s API may reject malformed URLs.
Logger Priority:
The logger must be configured before the bundle initializes. Use kernel.request event to set it dynamically if needed:
public function onKernelRequest(RequestEvent $event) {
$this->container->get('answear_fan_courier.service.pickup_point')
->
How can I help you explore Laravel packages today?