Skip to content
intermediate Phase 33 · Controllers

Routes Deep Dive

Route matching, custom routers, URL rewrite integration, and advanced routing patterns.

45m
0 problems
Topic Progress 0%

Route Matching Process

Magento's route matching process resolves URLs to controllers.

Complete matching flow:

1. HTTP Request arrives
2. FrontController receives request
3. Router chain iterates
4. URL rewrites checked (url_rewrite table)
5. Route matching (routes.xml)
6. Controller resolution (frontName/controller/action)
7. Action execution

URL structure:

https://example.com/blog/post/view/id/5
         └─ domain ─┘└─ frontName ─┘└─ controller ─┘└─ action ─┘└─ params ─┘

Controller class resolution:

frontName: blog
controller: post
action: view

→ Vendor/Blog/Controller/Post/View.php
→ Class: Vendor\Blog\Controller\Post\View

Parameter extraction:

blog/post/view/id/5/key/value

→ id = 5
→ key = value

All URL segments after action become parameters

Route matching priority:

1. Exact URL rewrite match
2. Route with matching frontName
3. Default route (cms/index/index)
4. 404 Not Found

Custom Routers

Custom routers handle non-standard URL patterns.

Custom router class:

<?php
namespace Vendor\Module\Router;

use Magento\Framework\App\Action\Forward;
use Magento\Framework\App\ActionFactory;
use Magento\Framework\App\RouterInterface;
use Magento\Framework\App\RequestInterface;

class CustomRouter implements RouterInterface
{
    public function __construct(
        private ActionFactory $actionFactory
    ) {}
    
    public function match(RequestInterface $request): ?ActionInterface
    {
        $path = trim($request->getPathInfo(), '/');
        
        // Match: product/URL_KEY
        if (preg_match('/^product\/([\w-]+)$/', $path, $matches)) {
            $request->setRouteName('catalog');
            $request->setControllerName('product');
            $request->setActionName('view');
            $request->setParam('url_key', $matches[1]);
            
            return $this->actionFactory->create(
                \Magento\Catalog\Controller\Product\View::class
            );
        }
        
        // Match: category/URL_KEY
        if (preg_match('/^category\/([\w-]+)$/', $path, $matches)) {
            $request->setRouteName('catalog');
            $request->setControllerName('category');
            $request->setActionName('view');
            $request->setParam('url_key', $matches[1]);
            
            return $this->actionFactory->create(
                \Magento\Catalog\Controller\Category\View::class
            );
        }
        
        return null; // Let next router try
    }
}

Register custom router:

<!-- etc/di.xml -->
<type name="Magento\Framework\App\Router\Base">
    <arguments>
        <argument name="actionList" xsi:type="array">
            <item name="custom_router" xsi:type="object">
                Vendor\Module\Router\CustomRouter
            </item>
        </argument>
    </arguments>
</type>

Router priority:

// Routers execute in order of registration
// First match wins

// Priority order:
1. UrlRewriteRouter (URL rewrites)
2. DefaultRouter (standard routing)
3. Custom routers (registered via di.xml)

URL Rewrite Integration

URL rewrites intercept routing before standard route matching.

URL rewrite flow:

1. Request: /my-product.html
2. UrlRewriteRouter checks url_rewrite table
3. Found: target_path = catalog/product/view/id/5
4. Request is rewritten internally
5. Standard routing continues with rewritten path

Custom URL rewrite via code:

<?php
namespace Vendor\SEO\Service;

class UrlRewriteService
{
    public function __construct(
        private \Magento\UrlRewrite\Model\UrlRewriteFactory $rewriteFactory,
        private \Magento\UrlRewrite\Model\ResourceModel\UrlRewrite $rewriteResource
    ) {}
    
    public function createRewrite(
        string $requestPath,
        string $targetPath,
        int $storeId = 0,
        int $redirectType = 0
    ): void {
        $rewrite = $this->rewriteFactory->create();
        $rewrite->setStoreId($storeId);
        $rewrite->setRequestPath($requestPath);
        $rewrite->setTargetPath($targetPath);
        $rewrite->setRedirectType($redirectType);
        $rewrite->setEntityType('custom');
        $rewrite->setEntityId(0);
        
        $this->rewriteResource->save($rewrite);
    }
}

URL rewrite with router:

public function match(RequestInterface $request): ?ActionInterface
{
    $path = trim($request->getPathInfo(), '/');
    
    // Check for custom URL pattern
    if ($this->isCustomPattern($path)) {
        // Create URL rewrite on-the-fly
        $this->createRewrite($path, $this->getTargetPath($path));
        
        // Return forward to trigger rewrite
        $this->request->setPathInfo('/' . $this->getTargetPath($path));
        return null; // Let URL rewrite handle it
    }
    
    return null;
}

Debugging rewrites:

# Check rewrites
php bin/magento urlrewrite:index

# Clear rewrites
php bin/magento urlrewrite:reindex

# Check specific rewrite
mysql -u root -p magento -e "SELECT * FROM url_rewrite WHERE request_path = '/my-product.html';"

Advanced Routing Patterns

Advanced routing patterns for complex URL requirements.

Route with multiple parameters:

<route url="/V1/blog/:blogId/posts/:postId" method="GET">
    <service class="Vendor\Blog\Api\PostRepositoryInterface" method="getBlogPost"/>
</route>

Catch-all routes:

// Router that catches unmatched URLs
public function match(RequestInterface $request): ?ActionInterface
{
    $path = trim($request->getPathInfo(), '/');
    
    // If no route matched, try custom resolution
    if (!$request->getRouteName()) {
        $request->setRouteName('vendor_custom');
        $request->setControllerName('index');
        $request->setActionName('view');
        $request->setParam('path', $path);
        
        return $this->actionFactory->create(
            \Vendor\Custom\Controller\Index\View::class
        );
    }
    
    return null;
}

Route with condition:

public function match(RequestInterface $request): ?ActionInterface
{
    // Only route if specific condition is met
    if (!$this->isEnabled()) {
        return null;
    }
    
    $path = trim($request->getPathInfo(), '/');
    
    if ($this->matchesPattern($path)) {
        // Handle routing
    }
    
    return null;
}

Performance considerations:

1. Keep router logic simple (regex is expensive)
2. Return null quickly for non-matching paths
3. Use URL rewrites for static patterns
4. Cache router results when possible

Quiz

1. What is the first thing Magento checks during route matching?

Question 1 options

2. What should a custom router return if it doesn't match?

Question 2 options

3. How are URL parameters extracted from a path like blog/post/view/id/5?

Question 3 options

4. Where are URL rewrites stored?

Question 4 options

Flashcards

Question

What is the route matching priority?

Answer

1. URL rewrites 2. Route matching 3. Default route 4. 404

Question

How does a custom router signal no match?

Answer

Return null from the match() method

Question

What URL segments become parameters?

Answer

All segments after the action become key/value pairs

Question

What class is the entry point for routing?

Answer

Magento\Framework\App\FrontController

Question

How do you register a custom router?

Answer

Via di.xml argument for Magento\Framework\App\Router\Base

Revision Notes

Key Takeaways

  • 1. Route matching checks URL rewrites first, then routes.xml
  • 2. Custom routers implement RouterInterface and return null for no match
  • 3. URL segments after action become key/value parameters
  • 4. Router priority: URL rewrites > default router > custom routers
  • 5. URL rewrites intercept routing before standard matching
  • 6. Keep router logic simple for performance

Interview Tips

  • Explain the complete route matching flow
  • Describe how to create a custom router
  • Discuss URL rewrite integration with routing
  • Know performance implications of routing

Cheat Sheet

Routes Deep Dive Cheat Sheet

Matching flow:

  1. URL rewrites (url_rewrite table)
  2. Route matching (routes.xml)
  3. Controller resolution
  4. Action execution

Custom router:

class MyRouter implements RouterInterface
{
    public function match(RequestInterface $request): ?ActionInterface
    {
        // Return action or null
    }
}

Register:

<type name="Magento\Framework\App\Router\Base">
    <arguments>
        <argument name="actionList" xsi:type="array">
            <item name="my_router" xsi:type="object">MyRouter</item>
        </argument>
    </arguments>
</type>

URL structure:
/frontName/controller/action/param1/value1/param2/value2