Installation
Add the bundle to your composer.json:
composer require baikal/rest-bundle
Enable the bundle in config/bundles.php:
return [
// ...
Baikal\RestBundle\BaikalRestBundle::class => ['all' => true],
];
Configuration Publish the default config:
php bin/console baikal:rest:install
Update config/packages/baikal_rest.yaml with your OAuth credentials (e.g., from Baïkal CalDAV server).
First Use Case Create a basic REST controller to interact with Baïkal:
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
use Baikal\RestBundle\Client\BaikalClient;
class BaikalController
{
private $baikalClient;
public function __construct(BaikalClient $baikalClient)
{
$this->baikalClient = $baikalClient;
}
#[Route('/events', methods: ['GET'])]
public function listEvents(): JsonResponse
{
$events = $this->baikalClient->getEvents();
return new JsonResponse($events);
}
}
Test with:
php bin/console server:run
Access http://localhost:8000/events.
Authentication
Use the BaikalClient with injected OAuth credentials:
$client = $this->baikalClient->authenticate(
$accessToken,
$refreshToken // Optional
);
CRUD Operations Leverage the bundle’s methods for common CalDAV operations:
// Create
$event = $this->baikalClient->createEvent([
'summary' => 'Team Meeting',
'dtstart' => '20231015T100000Z',
'dtend' => '20231015T110000Z',
]);
// Read
$event = $this->baikalClient->getEvent($eventId);
// Update
$this->baikalClient->updateEvent($eventId, ['summary' => 'Updated Meeting']);
// Delete
$this->baikalClient->deleteEvent($eventId);
Event Subscriptions Use Symfony’s Messenger component to queue Baïkal API calls:
use Baikal\RestBundle\Message\SyncEventsMessage;
$this->messageBus->dispatch(
new SyncEventsMessage($userId, $sinceDate)
);
Webhook Integration Handle Baïkal webhooks via a controller:
#[Route('/baikal/webhook', methods: ['POST'])]
public function handleWebhook(Request $request): Response
{
$payload = json_decode($request->getContent(), true);
$this->baikalClient->processWebhook($payload);
return new Response('OK');
}
CORS Configuration
Extend nelmio_cors config to allow Baïkal API domains:
# config/packages/nelmio_cors.yaml
paths:
'^/api/baikal/':
allow_origin: ['*']
allow_methods: ['GET', 'POST', 'PUT', 'DELETE']
allow_headers: ['Authorization', 'Content-Type']
Dependency Injection Bind custom Baïkal clients per environment:
# config/services.yaml
services:
Baikal\RestBundle\Client\BaikalClient:
arguments:
$baseUri: '%env(BAIKAL_API_URL)%'
$clientId: '%env(BAIKAL_CLIENT_ID)%'
$clientSecret: '%env(BAIKAL_CLIENT_SECRET)%'
Error Handling Create a global exception listener for Baïkal API errors:
use Baikal\RestBundle\Exception\BaikalApiException;
class BaikalExceptionListener
{
public function onKernelException(GetResponseForExceptionEvent $event)
{
if ($event->getThrowable() instanceof BaikalApiException) {
$event->setResponse(new JsonResponse([
'error' => 'Baïkal API',
'message' => $event->getThrowable()->getMessage(),
], 400));
}
}
}
OAuth Token Management
401 Unauthorized errors.if ($this->baikalClient->isTokenExpired()) {
$this->baikalClient->refreshToken($refreshToken);
}
Rate Limiting
HttpClient with retry middleware:
$client = HttpClient::create([
'base_uri' => $baseUri,
'headers' => ['Authorization' => 'Bearer ' . $accessToken],
])->withOptions([
'retry_on_status' => [429, 503],
]);
Time Zone Mismatches
$dtstart = (new \DateTime('2023-10-15 10:00:00', new \DateTimeZone('Europe/Paris')))
->setTimezone(new \DateTimeZone('UTC'))
->format('Ymd\THis\Z');
Circular References in Serialization
# config/packages/jms_serializer.yaml
handlers:
Baikal\RestBundle\Entity\Event:
groups: [event, event_collection]
Enable API Logging Add a subscriber to log Baïkal API requests/responses:
use Psr\Log\LoggerInterface;
class BaikalLoggerSubscriber implements EventSubscriberInterface
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function onKernelRequest(GetResponseEvent $event)
{
if ($event->getRequest()->getPathInfo() === '/api/baikal/events') {
$this->logger->debug('Baïkal API Request', [
'uri' => $event->getRequest()->getUri(),
'body' => $event->getRequest()->getContent(),
]);
}
}
}
Test with Mocked Client
Use PHPUnit to mock BaikalClient:
$mockClient = $this->createMock(BaikalClient::class);
$mockClient->method('getEvents')->willReturn([/* mock data */]);
$controller = new BaikalController($mockClient);
$response = $controller->listEvents();
$this->assertEquals(200, $response->getStatusCode());
Custom Endpoints Extend the bundle by creating a custom client:
class CustomBaikalClient extends BaikalClient
{
public function getCustomEvents(array $filters): array
{
return $this->get('/custom-endpoint', [
'query' => $filters,
]);
}
}
Event Transformers Override serialization/deserialization:
use JMS\Serializer\Context;
class CustomEventTransformer implements TransformerInterface
{
public function transform($data, Context $context)
{
// Custom logic here
return $data;
}
}
Webhook Verification Add HMAC verification for webhooks:
use Symfony\Component\HttpFoundation\Request;
public function verifyWebhook(Request $request): bool
{
$expectedSignature = hash_hmac(
'sha256',
$request->getContent(),
$this->baikalClient->getWebhookSecret()
);
return hash_equals($expectedSignature, $request->headers->get('X-Baikal-Signature'));
}
How can I help you explore Laravel packages today?