dbp/relay-base-course-bundle
Install the Bundle
composer require dbp/relay-base-course-bundle
Add to config/bundles.php:
return [
// ...
Dbp\Relay\BaseCourseBundle\DbpRelayBaseCourseBundle::class => ['all' => true],
];
Clear Caches
php bin/console cache:clear
Implement CourseProviderInterface
Create a service (e.g., src/Service/CourseProvider.php) implementing CourseProviderInterface:
use Dbp\Relay\BaseCourseBundle\API\CourseProviderInterface;
use Dbp\Relay\BaseCourseBundle\Entity\Course;
class CourseProvider implements CourseProviderInterface {
public function getCourseById(string $identifier, array $options = []): ?Course {
// Fetch course logic (e.g., from DB, API, or static data)
$course = new Course();
$course->setIdentifier($identifier);
return $course;
}
}
Register the Service
Tag it as a course_provider in services.yaml:
services:
App\Service\CourseProvider:
tags: ['relay.course_provider']
Test the Endpoint
The bundle provides a /api/courses/{id} endpoint. Test with:
curl http://localhost:8000/api/courses/test-course-id
Course Data Fetching
getCourseById() to fetch courses from your data source (DB, API, etc.).$options for filtering (e.g., ['with_modules' => true]).Entity Customization
Course entity (e.g., App\Entity\ExtendedCourse) and override methods like setIdentifier().API Integration
/api/courses/{id}). Customize responses by overriding the CourseController or using event listeners.Event-Driven Extensions
course.pre_fetch and course.post_fetch events to modify course data before/after retrieval:
// config/services.yaml
App\EventListener\CourseListener:
tags:
- { name: kernel.event_listener, event: course.pre_fetch, method: onPreFetch }
Bulk Operations
getCoursesByIds() (optional) for batch fetching:
public function getCoursesByIds(array $identifiers, array $options = []): array {
return array_map([$this, 'getCourseById'], $identifiers);
}
Database Integration Use Doctrine ORM to fetch courses:
public function getCourseById(string $identifier): ?Course {
return $this->entityManager->getRepository(Course::class)
->findOneBy(['identifier' => $identifier]);
}
API Caching
Cache responses in getCourseById():
use Symfony\Contracts\Cache\CacheInterface;
public function __construct(private CacheInterface $cache) {}
public function getCourseById(string $identifier): ?Course {
return $this->cache->get($identifier, fn() => $this->fetchFromDb($identifier));
}
Validation
Validate identifiers in getCourseById():
if (!preg_match('/^[a-z0-9\-]+$/', $identifier)) {
throw new \InvalidArgumentException('Invalid course identifier');
}
Testing Mock the provider in PHPUnit:
$provider = $this->createMock(CourseProviderInterface::class);
$provider->method('getCourseById')->willReturn(new Course());
$this->container->set('relay.course_provider', $provider);
Missing Service Tag
relay.course_provider will cause the bundle to fail silently. Verify with:
php bin/console debug:container relay.course_provider
Entity Mismatch
Course entity doesn’t match the bundle’s expected structure (e.g., missing setIdentifier()), the API will throw MethodNotAllowedHttpException. Extend the entity or use a trait:
use Dbp\Relay\BaseCourseBundle\Entity\Traits\CourseTrait;
class ExtendedCourse {
use CourseTrait;
}
Circular Dependencies
Course entity directly into your provider. Use dependency injection for the provider itself.API Route Conflicts
/api/courses/{id} route may conflict with existing routes. Override the route in your routing.yaml:
relay_course:
path: /my-custom-courses/{id}
methods: [GET]
controller: App\Controller\CourseController::getCourseAction
Performance with Large Datasets
LazyCollection or pagination.Check Provider Injection Dump the active provider to verify it’s loaded:
php bin/console debug:container --parameter relay.course_provider
Enable API Debugging
Add debug headers to the CourseController:
public function getCourseAction(string $id, Request $request): Response {
$course = $this->courseProvider->getCourseById($id);
$response = new Response(json_encode($course));
$response->headers->set('X-Debug-Course-Id', $id);
return $response;
}
Log Provider Calls Decorate the provider to log invocations:
class LoggingCourseProvider implements CourseProviderInterface {
public function __construct(private CourseProviderInterface $decorated) {}
public function getCourseById(string $identifier): ?Course {
\Log::info("Fetching course: {$identifier}");
return $this->decorated->getCourseById($identifier);
}
}
Custom Fields
Add custom fields to the Course entity and serialize them in jsonSerialize():
class ExtendedCourse extends Course {
private ?string $customField;
public function jsonSerialize(): array {
return array_merge(parent::jsonSerialize(), [
'custom_field' => $this->customField,
]);
}
}
Authentication
Secure the /api/courses endpoint with Voters or Firewalls:
# config/packages/security.yaml
access_control:
- { path: ^/api/courses, roles: ROLE_COURSE_VIEWER }
Webhooks Trigger events after course retrieval (e.g., analytics):
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class CourseProvider {
public function __construct(private EventDispatcherInterface $dispatcher) {}
public function getCourseById(string $identifier): ?Course {
$course = $this->fetchCourse($identifier);
$this->dispatcher->dispatch(new CourseFetchedEvent($course));
return $course;
}
}
Internationalization
Localize course fields by overriding the Course entity’s getTitle():
public function getTitle(): string {
return $this->translator->trans('course.title', ['%name%' => $this->name]);
}
Testing the Bundle Use the bundle’s test utilities to validate implementations:
$this->assertInstanceOf(
Course::class,
$this->get('relay.course_provider')->getCourseById('test-id')
);
How can I help you explore Laravel packages today?