Installation
Add the bundle to your composer.json:
composer require axstrad/seo-bundle
Enable it in config/bundles.php:
return [
// ...
Axstrad\SeoBundle\AxstradSeoBundle::class => ['all' => true],
];
Configuration
Extend Symfony-CMF's SEO bundle config in config/packages/axstrad_seo.yaml:
axstrad_seo:
doctrine: true # Enable Doctrine ORM support
routes:
home: / # Map routes to SEO entities
First Use Case
Annotate an entity with @Seo and define metadata:
use Axstrad\SeoBundle\Annotation\Seo;
/**
* @Seo(
* title="Homepage",
* description="Welcome to our site",
* keywords="home, welcome"
* )
*/
class Homepage
{
// ...
}
Fetch SEO data in a controller:
use Axstrad\SeoBundle\Service\SeoService;
public function index(SeoService $seoService)
{
$seoData = $seoService->getSeoDataForRoute('/');
return new Response($seoData->getTitle());
}
Entity-Based SEO
@Seo annotations on Doctrine entities (e.g., BlogPost, Product)./**
* @Seo(title="%title%", description="%excerpt%")
*/
class BlogPost
{
public function getSeoTitle(): string
{
return $this->title . ' | ' . config('app.name');
}
}
Route-Level SEO
axstrad_seo:
routes:
blog_post: /blog/{slug}
product: /product/{id}
$seoData = $seoService->getSeoDataForRoute($request->getPathInfo());
$this->renderSeo($seoData); // Custom method to inject into Twig
Twig Integration Pass SEO data to templates:
return $this->render('blog/post.html.twig', [
'seo' => $seoService->getSeoDataForEntity($post),
]);
Use in Twig:
<title>{{ seo.title }}</title>
<meta name="description" content="{{ seo.description }}">
Dynamic Metadata Override SEO data per request:
$seoData = $seoService->getSeoDataForEntity($entity);
$seoData->setTitle('Custom Title - ' . $seoData->getTitle());
symfony-cmf/routing for route-aware SEO.SeoService to fetch SEO data for admin-generated entities.SeoService to generate SEO headers for JSON responses:
$response->headers->set('X-Seo-Title', $seoData->getTitle());
Doctrine Cache Invalidation
php bin/console doctrine:cache:clear-metadata
$seoService->invalidateSeoCacheForEntity($entity);
Route Resolution Conflicts
axstrad_seo.routes maps to unique route patterns. Overlaps may cause unexpected behavior.$seoService->getRouteForPath('/ambiguous-path'); // Returns null if no match
Annotation Parsing
@Seo in prePersist/preUpdate callbacks.getSeoTitle()) instead of annotation values.Legacy Symfony Versions
Enable Debug Mode
Add to config/packages/dev/axstrad_seo.yaml:
axstrad_seo:
debug: true
Logs SEO resolution steps to var/log/dev.log.
Check Resolved Data Dump SEO data in a controller:
dump($seoService->getSeoDataForRoute('/')->toArray());
Custom Metadata Providers
Implement Axstrad\SeoBundle\Provider\SeoMetadataProviderInterface to add logic:
class CustomSeoProvider implements SeoMetadataProviderInterface
{
public function getMetadata(EntityManagerInterface $em, string $route, array $params)
{
if ($route === '/promo') {
return new SeoData('Promo', 'Limited-time offer!');
}
return null;
}
}
Register in config:
axstrad_seo:
providers:
- App\Provider\CustomSeoProvider
Override SeoData Class
Extend Axstrad\SeoBundle\Model\SeoData to add custom fields:
class ExtendedSeoData extends SeoData
{
private $canonicalUrl;
public function setCanonicalUrl(string $url): self
{
$this->canonicalUrl = $url;
return $this;
}
}
Configure in services.yaml:
services:
Axstrad\SeoBundle\Model\SeoData: '@App\ExtendedSeoData'
Event Listeners
Listen for seo.metadata.resolved events to modify SEO data:
$eventDispatcher->addListener('seo.metadata.resolved', function (SeoMetadataEvent $event) {
$event->getSeoData()->setTitle('[Modified] ' . $event->getSeoData()->getTitle());
});
How can I help you explore Laravel packages today?