Greetings! I'm Aneesh Sreedharan, CEO of 2Hats Logic Solutions. At 2Hats Logic Solutions, we are dedicated to providing technical expertise and resolving your concerns in the world of technology. Our blog page serves as a resource where we share insights and experiences, offering valuable perspectives on your queries.
A dynamic SEO URL is a URL of a web page that is dynamically generated from the database. It has to change every now and then. Here, we will discuss how we define our own SEO URLs.
For this scenario, you can make use of the Shopware built-in SeoUrlRoute classes, which hold all the necessary information about your dynamic route.
Let’s have a look at an example of the stories section. Each story page has a different seo url with respect to language. So the seo URL has to be changed based on language.
First need to create a translatable entity for stories with seo_url field:
Following will be the controller path to access the stories detail page in front end.
1 2 3 4 5 6 7 8 | * @Route("/story/{id}", name="frontend.story.detail", methods={"GET"}) */ public function storyDetail(Request $request, SalesChannelContext $context, $id): Response { $salesChannelContext = $context->getContext(); $criteria = new Criteria([$id]); */ |
Here as you can see, the route has got an id parameter. We will be receiving stories id instead of seo_url and corresponding stories will be fetched using id.
Then will create a migration to insert an entry inside the seo_url_template table.
1 2 3 4 5 6 7 8 9 10 11 12 13 | /** * Insert y$connection->executeStatement(' INSERT INTO `seo_url_template` (`id`, `sales_channel_id`, `route_name`, `entity_name`, `template`, `is_valid`, `custom_fields`, `created_at`, `updated_at`) VALUES (:id, NULL, :routeName, :entityName, :template, 1, NULL, :createdAt, NULL); ', [ 'id' => Uuid::randomBytes(), 'routeName' => 'frontend.story.detail', 'entityName' => 'stories', 'template' => 'story/{{ entry.seoUrl|lower }}', 'createdAt' => (new \DateTime())->format(Defaults::STORAGE_DATE_TIME_FORMAT), ]); our code here */ |
Then will create SeoUrlRoute classes, which hold all necessary information about your dynamic route and will then create the respective seo_url entries automatically.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | /** * use Shopware\Core\Content\Seo\SeoUrlRoute\SeoUrlMapping; use Shopware\Core\Content\Seo\SeoUrlRoute\SeoUrlRouteConfig; use Shopware\Core\Content\Seo\SeoUrlRoute\SeoUrlRouteInterface; class StorySeoUrlRoute implements SeoUrlRouteInterface { public const ROUTE_NAME = 'frontend.story.detail'; private $definition; public function __construct(StoryDefinition $definition) { $this->definition = $definition; } public function getConfig(): SeoUrlRouteConfig { return new SeoUrlRouteConfig( $this->definition, self::ROUTE_NAME, 'story/{{ entry.seoUrl|lower }}' ); } public function prepareCriteria(Criteria $criteria): void { $criteria->addFilter(new EqualsFilter('active', 1)); } public function getMapping(Entity $entry, ?SalesChannelEntity $salesChannel): SeoUrlMapping { if (!$entry instanceof StoryEntity) { throw new \InvalidArgumentException('Expected StoryEntity'); } return new SeoUrlMapping( $entry, ['id' => $entry->getId()], [ 'entry' => $entry, ] ); } }Insert your code here */ |
Will look at each step by step in detail. The custom “SeoUrlRoute” class has to implement the SeoUrlRouteInterface, which contains the following methods
- getConfig()
Here you have to return an instance of SeoUrlRouteConfig. Then contains,
- Your entity’s definition.
- Technical name of the route to be used.
- Desired SEO path.
- prepareCriteria()
Here you can adjust the criteria instance, which will be used to fetch your entities.For example you could add a filter on an active field and therefore only generate SEO URLs for active entities. Also you can add associations here.
- getMapping()
In this method, you have to return an instance of SeoUrlMapping.
It has to contain the actually available data for the SEO URL template. If you’re using
a variable entry.seoURl in the SEO URL template, you have to provide the data for the key entry here.
Following will be the services.xml entry for StorySeoUrlRoute. It has to be registered to the container using the tag shopware.seo_url.route.
1 2 3 4 5 6 7 | /** * Insert <service id="Project\Route\StorySeoUrlRoute"> <argument type="service" id="Project\Core\Content\Stories\StoryDefinition"/> <tag name="shopware.seo_url.route"/> </service> your code here */ |
Story subscriber
Every time when your entity is written, we have to let Shopware know that we want to generate the SEO URLs for those entities. Same if your entity is deleted, we need to update the same as well. This is done by reacting to the DAL events of your custom entity, to be specific we’re going to use the written event and deleted event.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | /** * Insertuse Shopware\Core\Content\Seo\SeoUrlUpdater; use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityDeletedEvent; use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Project\Route\StorySeoUrlRoute; class StorySeoUrlListener implements EventSubscriberInterface { /** * @var SeoUrlUpdater */ private $seoUrlUpdater; public function __construct(SeoUrlUpdater $seoUrlUpdater) { $this->seoUrlUpdater = $seoUrlUpdater; } public static function getSubscribedEvents(): array { return [ 'stories.written' => 'onStoryUpdated', 'stories.deleted' => 'onStoryDeleted', ]; } public function onStoryUpdated(EntityWrittenEvent $event): void { $this->seoUrlUpdater->update(StorySeoUrlRoute::ROUTE_NAME, $event->getIds()); } public function onStoryDeleted(EntityDeletedEvent $event) { $this->seoUrlUpdater->update(StorySeoUrlRoute::ROUTE_NAME, $event->getIds()); } } your code here */ |
For written events and deleted events of our custom entity, we need to provide the entity’s technical name with .written, .deleted suffix. Every time it is executed, we’re just using the said update method of the SeoUrlUpdater.
The subscriber has to be registered to the container for using it (services.xml)
The following example shows the dynamic seo url for a single story in English and German language in both the backend and front.
Table entries for both seo_url_template and seo_url
seo_url_template
seo_url
So when you execute the seo URL path (eg: story/story-in-english) in the browser, shopware by default will be checking in seo_url table and corresponding field path_info ( eg: /story/78f619ff640e4cf7a5590cb90a290e75) will be processed to the controller. And in the controller, we could get it as an entity id.
So, by following these methods you could easily manage Dynamic SEO URLs in your custom entities in shopware.