lensmedia/symfony-seo

一些可复用的SEO内容的“私有”包。

安装: 143

依赖: 0

建议者: 0

安全: 0

星级: 0

关注者: 1

分支: 0

开放问题: 0

类型:symfony-bundle

dev-main 2024-06-03 15:31 UTC

This package is auto-updated.

Last update: 2024-09-03 15:59:31 UTC


README

为Symfony项目提供的简单可复用SEO工具。

元数据

属性

use Lens\Bundle\SeoBundle\Attribute\Meta;

class Index extends AbstractController
{
    #[Route([
        'nl' => null,
        'en' => '/en',
    ], name: 'homepage')]
    #[Meta('nl', 'Hoi wereld!', keywords: ['lens', 'zmo', 'bundel'])]
    #[Meta('en', 'Hello world!', keywords: ['lens', 'seo', 'bundle'])]
    public function __invoke(): Response
    {
        return $this->render('homepage.html.twig');
    }
}

在twig中使用

Twig/MetaExtension向twig上下文添加了一个全局变量lens_seo_meta(可以更改,见配置),然后可以使用它

<title>{{ title ?? lens_seo_meta.title ?? 'meta.title'|trans }}</title>

{% set title = lens_seo_meta.title ?? title ?? 'meta.title'|trans %}
{% set description = lens_seo_meta.description ?? description ?? 'meta.description'|trans %}

{% if lens_seo_meta is defined and lens_seo_meta is not empty %}
    <meta name="title" content="{{ title }}">
    <meta name="description" content="{{ description }}">
    {% if (keywords ?? lens_seo_meta.keywords)|length %}
        <meta name="keywords" content="{{ (keywords ?? lens_seo_meta.keywords)|join(', ') }}">
    {% endif %}
{% endif %}

元数据解析器

元数据解析器允许完全控制元标签,主要用于动态路由。

#[Route(name: 'faq')]
#[Meta(resolver: FaqResolver::class)]
public function __invoke(): Response
{
   ...
namespace App\Seo\Meta;

use Lens\Bundle\SeoBundle\Attribute\Meta;
use Lens\Bundle\SeoBundle\MetaResolverInterface;
use Symfony\Component\HttpFoundation\Request;

class FaqResolver implements MetaResolverInterface
{
    public function resolveMeta(Request $request, Meta $meta): void
    {
        // This works well if you have an entity value resolver, otherwise you
        // can use the value and use dependency injection to get the entity.
        $faq = $request->attributes->get('faq');

        $meta->title = $faq->metaTitle ?? $faq->question;
        $meta->description = $faq->metaDescription;
    }

面包屑

添加面包屑的属性

use Lens\Bundle\SeoBundle\Attribute\Breadcrumb;

class Index extends AbstractController
{
    #[Route(name: 'homepage_route_name')]
    #[Breadcrumb([
        'nl' => 'homepagina',
        'en' => 'homepage',
    ])]
    public function __invoke(): Response
    {
        return $this->render('homepage.html.twig');
    }
}
class Faq extends AbstractController
{
    #[Route(name: 'faq_route_name')]
    #[Breadcrumb([
        'nl' => 'veel gestelde vragen',
        'en' => 'frequently asked questions',
    ], parent: 'homepage_route_name')]
    public function __invoke(): Response
    {
        return $this->render('faq.html.twig');
    }
}

在twig中使用

Twig/BreadcrumbExtension向twig上下文添加了一个全局变量lens_seo_breadcrumbs(可以更改,见配置),然后可以使用它

{% if lens_seo_breadcrumbs is defined and lens_seo_breadcrumbs is not empty %}
    <ol class="breadcrumbs">
        {% for breadcrumb in lens_seo_breadcrumbs %}
            {% if loop.last %}
                <li class="breadcrumb-item active">{{ breadcrumb.title }}</li>
            {% else %}
                <li class="breadcrumb-item">
                    <a href="{{ path(breadcrumb.routeName, breadcrumb.routeParameters) }}">{{ breadcrumb.title }}</a>
                </li>
            {% endif %}
        {% endfor %}
    </ol>
{% endif %}

面包屑解析器

面包屑解析器允许在面包屑具有动态值时进行完全控制。

#[Route(name: 'faq')]
#[Meta(resolver: FaqResolver::class)]
public function __invoke(): Response
{
   ...
namespace App\Seo\Meta;

use Lens\Bundle\SeoBundle\Attribute\Breadcrumb;
use Lens\Bundle\SeoBundle\BreadcrumbResolverInterface;
use Symfony\Component\HttpFoundation\Request;

class FaqResolver implements BreadcrumbResolverInterface
{
    public function resolveMeta(Request $request, Breadcrumb $breadcrumb): void
    {
        $faq = $request->attributes->get('faq');

        $breadcrumb->title = $faq->question ?? $request->attributes->get('uri');
        $breadcrumb->routeParameters['uri'] = $request->attributes->get('uri');
        $breadcrumb->routeParameters['_locale'] = $request->getLocale();
    }

结构化数据

提供类,帮助使用spatie/schema-org设置结构化数据。

<?php

use Spatie\SchemaOrg\Schema;
use Lens\Bundle\SeoBundle\StructuredData\StructuredDataBuilder;

class Index extends AbstractController
{
    #[Route(name: 'homepage')]
    public function __invoke(StructuredDataBuilder $structuredData): Response
    {
        $url = rtrim($this->generateUrl('homepage_route_name', [], UrlGeneratorInterface::ABSOLUTE_URL), '/');

        $address = Schema::postalAddress()
            ->streetAddress('Energiestraat 5')
            ->addressLocality('Hattem')
            ->postalCode('8051TE')
            ->addressCountry('NL');

        return Schema::organization()
            ->name('LENS Verkeersleermiddelen')
            ->address($address)
            ->url($url)
            ->sameAs($url);
    
        // Usually you would do the organization in a listener, so it works on all requests.
        $structuredData->addSchema($organization);

        return $this->render('homepage.html.twig');
    }
}

您也可以创建一个工厂服务以实现可复用性,如下所示

class Organization implements StructuredDataInterface
{
    public function __construct(
        private UrlGeneratorInterface $urlGenerator,
    ) {
    }

    public function __invoke(array $context = []): \Spatie\SchemaOrg\Organization
    {
        $url = rtrim($this->urlGenerator->generate('web_common_index', [], UrlGeneratorInterface::ABSOLUTE_URL), '/');

        $address = Schema::postalAddress()
            ->streetAddress('Energiestraat 5')
            ->addressLocality('Hattem')
            ->postalCode('8051TE')
            ->addressCountry('NL');

        return Schema::organization()
            ->name('LENS Verkeersleermiddelen')
            ->address($address)
            ->url($url)
            ->sameAs($url);
    }
}

这反过来又改变了控制器为

class Index extends AbstractController
{
    #[Route(name: 'homepage')]
    public function __invoke(StructuredDataBuilder $structuredData, Organization $organization): Response
    {
        $structuredData->addSchema($organization);

        return $this->render('homepage.html.twig');
    }
}

可调用的方法将自动被调用(但如果你这样做也没关系)。

将结构化数据添加到响应中

Event/AppendStructuredDataToResponse监听器将在存在时自动将结构化数据添加到响应中,就在关闭body标签之前。如果出于某种原因你需要不同的用例,你可以使用StructuredDataBuilder服务的toArray/toScript函数来做你的事情。

手动添加额外的结构化数据

您可以使用StructuredDataBuilder服务从几乎任何地方添加额外的结构化数据到响应中。例如,将服务暴露给twig,您可以直接这样做

{% do lens_seo_structured_data.addFromArray({ foo: 'bar' }) %}
$structeredData->addFromString('{"foo":"bar"}');

配置

以下是默认可用的配置选项。

lens_seo:
    structured_data:
        json_encode_options: 320 # int bitmask, see https://php.ac.cn/manual/en/function.json-encode.php unescaped slashes (64) & unescaped unicode (256)

    twig:
        globals:
            prefix: 'lens_seo_' # prefix for the global variable names listed below, null to disable

            meta:
                enabled: true # enables the global variable
                name: 'meta' # name of the global variable
            
            breadcrumbs:
                enabled: true
                name: 'breadcrumbs'
            
            structured_data:
                enabled: true
                name: 'structured_data'

我们当前的常用示例配置

lens_seo:
    twig:
        globals:
            # removes all prefixes allows direct access to: meta, breadcrumbs and structuredData.
            prefix: ~

            structured_data:
                # looks prettier when using e.g.: structuredData.addFromArray. We do not use snake
                # case anymore, and the other functions are already one word.
                name: 'structuredData'

when@dev:
    lens_seo:
        structured_data:
            json_encode_options: 448 # Adds pretty print (128) in dev