{"id":20956316,"url":"https://github.com/umanit/seo-bundle","last_synced_at":"2025-10-13T05:12:55.901Z","repository":{"id":51055038,"uuid":"166444152","full_name":"umanit/seo-bundle","owner":"umanit","description":"Seo capabilities for Doctrine entities","archived":false,"fork":false,"pushed_at":"2024-07-10T13:48:26.000Z","size":335,"stargazers_count":5,"open_issues_count":3,"forks_count":2,"subscribers_count":6,"default_branch":"main","last_synced_at":"2024-10-12T23:46:49.459Z","etag":null,"topics":["breadcrumb","schema","seo-bundle","seo-capabilities","seo-metadata","url-history"],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/umanit.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-01-18T17:02:31.000Z","updated_at":"2024-07-10T13:48:27.000Z","dependencies_parsed_at":"2024-07-10T16:07:40.904Z","dependency_job_id":null,"html_url":"https://github.com/umanit/seo-bundle","commit_stats":{"total_commits":189,"total_committers":6,"mean_commits":31.5,"dds":0.5185185185185186,"last_synced_commit":"3b0369d770ec5b54badff53408f3e74fb2ae99ec"},"previous_names":[],"tags_count":34,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/umanit%2Fseo-bundle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/umanit%2Fseo-bundle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/umanit%2Fseo-bundle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/umanit%2Fseo-bundle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/umanit","download_url":"https://codeload.github.com/umanit/seo-bundle/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225160284,"owners_count":17430282,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["breadcrumb","schema","seo-bundle","seo-capabilities","seo-metadata","url-history"],"created_at":"2024-11-19T01:25:23.716Z","updated_at":"2025-10-13T05:12:50.880Z","avatar_url":"https://github.com/umanit.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Umanit Seo Bundle\n\nThis bundle adds SEO capabilities for any model entities.\n\n## Features\n\n- [x] 301 Redirects when accessing an old URL\n- [x] SEO Metadata (title and description)\n- [x] Canonical URL\n- [x] Schema.org\n- [x] Breadcrumb\n- [ ] Sitemap\n\n## Installation\n\n`$ composer require umanit/seo-bundle`\n\n## Configuration\n\nThe template needs to be declared in your Twig configuration, **before** other templates. You must choose one between:\n\n```yaml\ntwig:\n    # ...\n    form_themes:\n        - '@UmanitSeo/sylius/form/fields.html.twig'\n        # OR\n        - '@UmanitSeo/sonata/form/fields.html.twig'\n        - ...\n```\n\nYou can configure your bundle further by creating a `umanit_seo.yaml` configuration file. Here is the default\nconfiguration provided by the bundle:\n\n```yaml\n# Default configuration for extension with alias: \"umanit_seo\"\numanit_seo:\n\n    # Historize URLs of entities which implements HistorizableUrlModelInterface\n    url_historization:\n        enabled: true\n\n        # Redirect code used by UrlRedirectorSubscriber\n        redirect_code: 301\n\n        # Cache service used to store entities dependencies. **MUST** implements \\Symfony\\Contracts\\Cache\\CacheInterface\n        cache_service: cache.app\n\n    # Defines the default templates used to render breadcrumbs\n    templates:\n        breadcrumb_json_ld: '@UmanitSeo/breadcrumb/breadcrumb.json-ld.html.twig'\n        breadcrumb_microdata: '@UmanitSeo/breadcrumb/breadcrumb.microdata.html.twig'\n        breadcrumb_rdfa: '@UmanitSeo/breadcrumb/breadcrumb.rdfa.html.twig'\n    metadata:\n        form_type:\n\n            # Automaticaly add a SeoMetadataType on FormType which handled an entity which implements HasSeoMetadataInterface\n            add_seo_metadata_type: true\n\n            # FQCN of the FormType used to renders SEO Metadata fields\n            class_fqcn: Umanit\\SeoBundle\\Form\\Type\\SeoMetadataType\n\n            # Injects Google Code Prettify when rendering breadcrumb and schema.org in FormType.\n            inject_code_prettify: true\n        default_title: 'Umanit Seo - Customize this default title to your needs.'\n        title_prefix: ''\n        title_suffix: ''\n        default_description: 'Umanit Seo - Customize this default description to your needs.'\n```\n\n## Usage\n\n1. [Basic Usage](#basic-usage)\n1. [Seo Metadata](#seo-metadata)\n1. [Schema.org](#schemaorg-implementation)\n1. [Breadcrumb](#breadcrumb)\n1. [Enabling 301 redirects](#enabling-301-redirects)\n1. [Twig functions reference](#twig-functions-reference)\n1. [Protips](#protips)\n\n### Basic usage\n\nIn order to function properly, SeoBundle must be able to generate a URL for a given entity. To do so, the\n`umanit_seo.routable` service uses handlers to process the entity.\n\nA handler is a service which implements `Umanit\\SeoBundle\\Handler\\Routable\\RoutableHandlerInterface`. A `supports`\nmethod indicated if the service can handle the given entity and a `process` method do the job by returning a\n`Umanit\\SeoBundle\\Model\\Route` object.\n\nThe `Umanit\\SeoBundle\\Model\\Route` object has a `name` attribute, which is the name of the route used to access the\nentity and a `parameters` attribute used to build the route.\n\n**You must implement the interface `Umanit\\SeoBundle\\Model\\RoutableModelInterface` on your entity and create a handler\nto process it.**\n\n```php\n\u003c?php\n\ndeclare(strict_types=1);\n\nnamespace App\\Entity;\n\nuse Doctrine\\ORM\\Mapping as ORM;\nuse Umanit\\SeoBundle\\Model\\RoutableModelInterface;\n\n #[ORM\\Entity]\nclass Page implements RoutableModelInterface\n{\n    // ...\n}\n```\n\n```php\n\u003c?php\n\ndeclare(strict_types=1);\n\nnamespace App\\Seo\\Routable;\n\nuse App\\Entity\\Page;\nuse Umanit\\SeoBundle\\Handler\\Routable\\RoutableHandlerInterface;\nuse Umanit\\SeoBundle\\Model\\RoutableModelInterface;\nuse Umanit\\SeoBundle\\Model\\Route;\n\nclass PageHandler implements RoutableHandlerInterface\n{\n    public function supports(RoutableModelInterface $entity): bool\n    {\n        return $entity instanceof Page;\n    }\n\n    public function process(RoutableModelInterface $entity): Route\n    {\n        return new Route('app_page_show', ['slug' =\u003e $entity-\u003egetSlug()]);\n    }\n}\n```\n\nSeoBundle will now be able to generate a URL from the entity. If you ever change the slug of a page, the old URL will be\nredirected to the new one.\n\nIf you wanted to generate the URL by yourself you would have done something like the following example:\n\n```twig\n{{ path('app_page_show', { 'slug': my_page.slug }) }}\"\n```\n\nYou can now do like so:\n\n```twig\n{{ path(my_page) }}\n```\n\n_**Note:** You can use the `canonical()` function without passing it an entity, SeoBundle will automatically resolve the\nentity associated to the current accessed route and generate the url from it._\n\nUsually, you'll want to use the `canonical()` function directly within your main layout.\n\n### Seo Metadata\n\nUse the `seo_metadata(your_entity)` twig function in your templates.\n\nSeoBundle will automatically find the most pertinent fields in your entity to deduct title and description.\n\nAgain, `seo_metadata()` can be used without passing it any entity.\n\n#### Administrating metadata\n\nIn order to administrate Seo Metadata, you'll need again to tune-up your entity.\n\nMake your entity implement the `HasSeoMetadataInterface` and use the `SeoMetadataTrait`\n\n```php\n\u003c?php\n\ndeclare(strict_types=1);\n\nnamespace App\\Entity;\n\nuse Umanit\\SeoBundle\\Doctrine\\Model\\SeoMetadataTrait;\nuse Umanit\\SeoBundle\\Model\\HasSeoMetadataInterface;\nuse Umanit\\SeoBundle\\Model\\RoutableModelInterface;\n\nclass Page implements RoutableModelInterface, HasSeoMetadataInterface\n{\n    use SeoMetadataTrait;\n\n    // ...\n}\n```\n\nIf the configuration `umanit_seo.metadata.form_type.add_seo_metadata_type` is not disabled, all form which handles your\nentity as `data` will automatically have a new `SeoMetadataType` form type.\n\nThis will add a subform with two fields, `title` and `description`.\n\n_**Note:** The form type class can be customized with `umanit_seo.metadata.form_type.class_fqcn`._\n\n### Schema.org implementation\n\nTo generate valid [schema.org](https://schema.org/) json microdata, SeoBundle must be able to process the given entity.\nTo do so, the `umanit_seo.schemable` service uses handlers to process the entity.\n\nA handler is a service which implements `Umanit\\SeoBundle\\Handler\\Schemable\\SchemableHandlerInterface`. A `supports`\nmethod indicated if the service can handle the given entity and a `process` method do the job by returning a\n`Spatie\\SchemaOrg\\BaseType` object.\n\nThe `Spatie\\SchemaOrg\\BaseType` object is provided by the library\n[spatie/schema-org](https://github.com/spatie/schema-org).\n\n**You must implement the interface `Umanit\\SeoBundle\\Model\\SchemableModelInterface` on your entity and create a handler\nto process it.**\n\n```php\n\u003c?php\n\ndeclare(strict_types=1);\n\nnamespace App\\Entity;\n\nuse Doctrine\\ORM\\Mapping as ORM;\nuse Umanit\\SeoBundle\\Model\\SchemableModelInterface;\n\n #[ORM\\Entity]\nclass Page implements SchemableModelInterface\n{\n    // ...\n}\n```\n\n```php\n\u003c?php\n\ndeclare(strict_types=1);\n\nnamespace App\\Seo\\Schemable;\n\nuse App\\Entity\\Page;\nuse Spatie\\SchemaOrg\\BaseType;\nuse Spatie\\SchemaOrg\\Schema;\nuse Umanit\\SeoBundle\\Handler\\Schemable\\SchemableHandlerInterface;\nuse Umanit\\SeoBundle\\Model\\SchemableModelInterface;\n\nclass PageHandler implements SchemableHandlerInterface\n{\n    public function supports(SchemableModelInterface $entity): bool\n    {\n        return $entity instanceof Page;\n    }\n\n    /**\n     * @param Page $entity\n    */\n    public function process(SchemableModelInterface $entity): BaseType\n    {\n        return Schema::mensClothingStore()\n                     -\u003ename($entity-\u003egetName())\n                     -\u003eurl($entity-\u003egetSlug())\n                     -\u003econtactPoint(Schema::contactPoint()-\u003eareaServed('Worldwide'))\n            ;\n    }\n}\n```\n\nNext, add the twig function `seo_schema_org()` at the bottom of your layout.\n\nThe function will format and display the json schema of the current entity as you defined it.\n\n```html\n\n\u003cscript type=\"application/ld+json\"\u003e\n    {\n        \"@context\": \"https:\\/\\/schema.org\",\n        \"@type\": \"MensClothingStore\",\n        \"name\": \"Test\",\n        \"email\": \"test@umanit.fr\",\n        \"contactPoint\": {\n            \"@type\": \"ContactPoint\",\n            \"areaServed\": \"Worldwide\"\n        }\n    }\n\n\u003c/script\u003e\n```\n\n### Breadcrumb\n\nYou can easily generate your breadcrumb in 3 different formats; `Microdata`, `RDFa` or `JSON-LD` as described by\n[the specification](https://schema.org/BreadcrumbList). To do so, the `umanit_seo.breadcrumbable` service uses handlers\nto process the entity.\n\nA handler is a service which implements `Umanit\\SeoBundle\\Handler\\Breadcrumbable\\BreadcrumbableHandlerInterface`. A\n`supports` method indicated if the service can handle the given entity and a `process` method do the job by returning a\n`Umanit\\SeoBundle\\Model\\Breadcrumb` object.\n\nThe `Umanit\\SeoBundle\\Model\\Breadcrumb` obect has a `format` attribute, which is one of the previously mentionned and a\n`items` attributes which is an array of `Umanit\\SeoBundle\\Model\\BreadcrumbItem`. Each `BreadcrumbItem` has a `label`\nattribute and an optionnal `url` attribute.\n\n**You must implement the interface `Umanit\\SeoBundle\\Model\\BreadcrumbableModelInterface` on your entity and create a\nhandler to process it.**\n\n```php\n\u003c?php\n\ndeclare(strict_types=1);\n\nnamespace App\\Entity;\n\nuse Doctrine\\ORM\\Mapping as ORM;\nuse Umanit\\SeoBundle\\Model\\BreadcrumbableModelInterface;\n\n #[ORM\\Entity]\nclass Page implements BreadcrumbableModelInterface\n{\n    // ...\n}\n```\n\n```php\n\u003c?php\n\ndeclare(strict_types=1);\n\nnamespace App\\Seo\\Breadcrumbable;\n\nuse App\\Entity\\Page;\nuse Umanit\\SeoBundle\\Handler\\Breadcrumbable\\BreadcrumbableHandlerInterface;\nuse Umanit\\SeoBundle\\Model\\Breadcrumb;\nuse Umanit\\SeoBundle\\Model\\BreadcrumbableModelInterface;\nuse Umanit\\SeoBundle\\Model\\BreadcrumbItem;\n\nclass PageHandler implements BreadcrumbableHandlerInterface\n{\n    public function supports(BreadcrumbableModelInterface $entity): bool\n    {\n        return $entity instanceof Page;\n    }\n\n    /**\n     * @param Page $entity\n     */\n    public function process(BreadcrumbableModelInterface $entity): Breadcrumb\n    {\n        $breadcrumb = new Breadcrumb();\n\n        $breadcrumb-\u003esetItems([\n            new BreadcrumbItem('Homepage', '/'),\n            new BreadcrumbItem(\n                $entity-\u003egetCategory()-\u003egetName(),\n                $this-\u003erouter-\u003egenerate('app_page_category_show', ['slug' =\u003e $entity-\u003egetCategory()-\u003egetSlug()])\n            ),\n            new BreadcrumbItem($entity-\u003egetName()),\n        ]);\n\n        return $breadcrumb;\n    }\n}\n```\n\n_**Note:** If the processed entity implements the `RoutableModelInterface`, you can omit the `url` attribute to let the\nservice `umanit_seo.routable` generate it for you._\n\nYou can now use the twig function `seo_breadcrumb()` like the following examples:\n\n```twig\n{{ seo_breadcrumb() }} {# Will generate the breadcrumb from the current entity using microdata format #}\n{{ seo_breadcrumb(entity=my_entity, format='json-ld') }} {# Will generate the breadcrumb from my_entity using json-ld format #}\n{{ seo_breadcrumb(format='rdfa') }} {# Will generate the breadcrumb from the current entity using rdfa format #}\n```\n\n### Enabling 301 redirects\n\nIn order to enable URL history and 301 redirects on an entity, ensure the configuration\n`umanit_seo.url_historization.enabled` is active (yes by default) then implement the interface\n`Umanit\\SeoBundle\\Model\\HistorizableUrlModelInterface` and use the trait\n`Umanit\\SeoBundle\\Doctrine\\Model\\HistorizableUrlTrait`.\n\n```php\n\u003c?php\n\ndeclare(strict_types=1);\n\nnamespace App\\Entity;\n\nuse Doctrine\\ORM\\Mapping as ORM;\nuse Umanit\\SeoBundle\\Doctrine\\Model\\HistorizableUrlTrait;\nuse Umanit\\SeoBundle\\Model\\HistorizableUrlModelInterface;\n\n #[ORM\\Entity]\nclass Page implements HistorizableUrlModelInterface\n{\n    use HistorizableUrlTrait;\n\n    // ...\n}\n```\n\n### Twig functions reference\n\n```html\n{{ path(entity, parameters = []) }}                 # Path to an Seo entity\n{{ url(entity, parameters = []) }}                  # Url to an Seo entity\n{{ seo_canonical(entity = null, parameters = []) }} # Canonical link of an Seo entity\n{{ seo_title(entity = null) }}                      # Title (without markup) of an Seo entity\n{{ seo_metadata(entity = null) }}                   # Metadata of an entity (title and description, with markup)\n{{ seo_schema_org(entity = null) }}                 # Json schema of an entity (with markup)\n{{ seo_breadcrumb(entity = null, format = null) }}  # Breadcrumb from an entity (default format to 'microdata')\n```\n\n### Protips\n\n* The `HistorizableUrlModelInterface` extends the `RoutableModelInterface`, so you don't need to implement both,\n* you can use a custom HTTP code when redirecting by overriding `umanit_seo.url_historization.redirect_code`,\n* you can use a custom cache service for `Umanit\\SeoBundle\\Doctrine\\EventSubscriber\\UrlHistoryWriter` by overriding\n  `umanit_seo.url_historization.cache_service`,\n* if one of your service needs the `@router`, you can implement `Umanit\\SeoBundle\\Service\\RouterAwareInterface` and use\n  the trait `Umanit\\SeoBundle\\Service\\RouterAwareTrait` (usefull for breadcrumb handlers!).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fumanit%2Fseo-bundle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fumanit%2Fseo-bundle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fumanit%2Fseo-bundle/lists"}