Installation
composer require contentful/contentful-bundle
Add the bundle to config/bundles.php:
Contentful\ContentfulBundle\ContentfulBundle::class => ['dev' => true],
Basic Configuration
Create config/packages/contentful.yaml:
contentful:
delivery:
space_id: 'your-space-id'
access_token: 'your-delivery-token'
environment_id: 'your-environment-id' # Optional (defaults to 'master')
First Use Case: Fetching Content
Inject the ContentfulClient service in a controller or service:
use Contentful\ContentfulBundle\Client\ContentfulClientInterface;
public function __construct(private ContentfulClientInterface $client) {}
public function showBlogPost(ContentTypeIdentifier $id) {
$entry = $this->client->getEntry($id);
return $this->render('blog/post.html.twig', ['entry' => $entry]);
}
Twig Integration
Enable Twig extensions in config/packages/twig.yaml:
twig:
globals:
contentful: '@contentful.twig.contentful_extension'
Use in templates:
{{ contentful.entry('blogPostId')|raw }}
Fetching Entries
// Single entry
$entry = $client->getEntry('blogPostId');
// Multiple entries (query)
$query = $client->createQuery()
->where('fields.title', 'LIKE', '%Laravel%')
->limit(10);
$entries = $client->getEntries($query);
Content Types & Fields
$contentType = $client->getContentType('blogPost');
$fields = $contentType->getFields(); // Array of field definitions
Localization
contentful:
delivery:
locales: ['en-US', 'es-ES'] # Optional
$entry = $client->getEntry($id, 'es-ES'); // Fetch localized entry
Caching (Symfony Cache Component)
contentful:
delivery:
cache:
enabled: true
pool: 'cache.app' # Symfony cache pool
ttl: 3600 # Cache TTL in seconds
Event Listeners (Real-Time Updates)
// config/services.yaml
services:
App\EventListener\ContentfulWebhookListener:
tags:
- { name: 'kernel.event_listener', event: 'contentful.webhook', method: 'onWebhook' }
HttpClient or Laravel’s Http facade via dependency injection.Contentful\ContentfulBundle\Doctrine\ContentfulEntityListener.api-platform/core for GraphQL/REST endpoints:
# config/packages/api_platform.yaml
api_platform:
formats:
jsonld:
contentful: ['application/vnd.api+json']
Token Security
access_token to version control. Use environment variables or Symfony’s parameter_bag:
# .env
CONTENTFUL_ACCESS_TOKEN=your-token-here
# config/packages/contentful.yaml
contentful:
delivery:
access_token: '%env(CONTENTFUL_ACCESS_TOKEN)%'
Rate Limiting
$client->setRetryOptions(['max_retries' => 3]);
Field Serialization
richText) and asset fields (asset) return nested objects. Flatten them in a DTO or use Twig filters:
{{ entry.fields.body|contentful_richtext_to_html }}
Environment Sync
master environment. Explicitly set environment_id to avoid surprises:
contentful:
delivery:
environment_id: 'staging' # Not 'master'
Enable Debug Mode
contentful:
delivery:
debug: true # Logs all API requests/responses
Check logs in var/log/dev.log.
Validate Responses
Use Contentful\ContentfulBundle\Exception\ContentfulException to catch API errors:
try {
$entry = $client->getEntry($id);
} catch (ContentfulException $e) {
// Handle 404, 403, etc.
$this->addFlash('error', $e->getMessage());
}
Custom Field Resolvers
Extend Contentful\ContentfulBundle\Resolver\FieldResolverInterface to handle custom field types:
class CustomFieldResolver implements FieldResolverInterface {
public function resolve($value, Field $field) {
return json_decode($value, true); // Example: JSON field
}
}
Register in services.yaml:
services:
App\Resolver\CustomFieldResolver:
tags:
- { name: 'contentful.field_resolver', alias: 'customField' }
Override Client Configuration Dynamically configure the client via a compiler pass:
use Contentful\ContentfulBundle\DependencyInjection\Compiler\ContentfulCompilerPass;
public function process(ContainerBuilder $container) {
$container->addCompilerPass(new ContentfulCompilerPass());
}
Webhook Validation Validate Contentful webhooks in a listener:
public function onWebhook(WebhookEvent $event) {
$payload = $event->getPayload();
if (!$this->validateWebhookSignature($payload)) {
throw new \RuntimeException('Invalid webhook signature');
}
}
Batch Loading
Use include in queries to fetch linked entries in one request:
$query->include(['author', 'tags']);
Lazy Loading For large datasets, implement pagination:
$query->limit(10)->skip(0); // Page 1
$query->limit(10)->skip(10); // Page 2
Symfony Cache Adapter Configure Redis/Memcached for distributed caching:
contentful:
delivery:
cache:
pool: 'cache.redis' # Use Redis cache pool
How can I help you explore Laravel packages today?