Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Router Bundle Laravel Package

ongr/router-bundle

Symfony bundle that adds Elasticsearch-backed routing: store route definitions as documents, match incoming URLs through a custom router, and generate paths from indexed content. Useful for dynamic CMS pages and large route sets managed outside static Symfony routing.

View on GitHub
Deep Wiki
Context7

Getting Started

The bundle replaces Symfony's file-based route collection with routes stored as Elasticsearch documents, so URLs can be created/edited at runtime (CMS pages, landing pages, per-tenant routes) without a deploy.

Install and register

composer require ongr/router-bundle

v2.0.1 is a composer-metadata-only release: it fixes a wrong ongr/elasticsearch-bundle version constraint in composer.json. Nothing in the PHP code changed, but the constraint fix means dependency resolution behaves differently than the 1.x/2.0.0 tag — before this patch the require could fail outright or resolve to an unexpected ES bundle line. So after installing, always confirm what you actually got:

composer show ongr/router-bundle ongr/elasticsearch-bundle elasticsearch/elasticsearch

The bundle still targets a Symfony 2.x/3.x stack and the old ES client; a corrected constraint does not mean Symfony 4+/ES 6+ support. Check the resolved versions before you start — it is not compatible with Symfony 4+ without patching.

// app/AppKernel.php — ONGR bundles must be registered after FrameworkBundle
$bundles = [
    new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
    // ...
    new ONGR\ElasticsearchBundle\ONGRElasticsearchBundle(),
    new ONGR\RouterBundle\ONGRRouterBundle(),
];

Configure the ES manager the router should read from

# app/config/config.yml
ongr_es:
    managers:
        default:
            index:
                index_name: app
                hosts: [localhost:9200]
            mappings:
                - AppBundle:Route

ongr_router:
    es_manager: default

Define the route document

namespace AppBundle\Document;

use ONGR\RouterBundle\Document\Route as BaseRoute;
use ONGR\ElasticsearchBundle\Annotation as ES;

/**
 * @ES\Document(type="route")
 */
class Route extends BaseRoute
{
}

Create the index and seed one route

php app/console ongr:es:index:create
$route = new Route();
$route->setName('promo_show');           // ES document id == Symfony route name
$route->setPath('/promo/{slug}');
$route->setController('AppBundle:Promo:show');
$route->setDefaults(['_format' => 'html']);
$route->setRequirements(['slug' => '[a-z0-9-]+']);

$manager = $this->get('es.manager.default');
$manager->persist($route);
$manager->commit();
$manager->getConnection()->refresh();     // or you won't see it for ~1s

First use case to prove it works: hit /promo/spring-sale and then run php app/console debug:router promo_show. If the route shows up in debug:router you have correctly swapped the router service; if not, the stock router.default is still winning and you need to check bundle ordering / the ongr.router.router service alias.

Where to look first in the source

  • Routing/RouteProvider.php — how documents become a RouteCollection
  • Routing/RouteCompiler.php — array/document → Symfony\Component\Routing\Route
  • Document/Route.php — the exact fields (path, controller, defaults, requirements, options, host, schemes, methods, condition)
  • Resources/config/services.yml — service ids you will decorate

Implementation Patterns

Treat routes as content, not code. The idiomatic ONGR pattern is an admin CRUD screen that persists Route documents. Wrap creation in a small factory so the invariant "document id == route name" is enforced in one place:

class RouteFactory
{
    public function create(string $name, string $path, string $controller, array $defaults = []): Route
    {
        $route = new Route();
        $route->setName($name);
        $route->setPath($path);
        $route->setController($controller);
        $route->setDefaults($defaults);
        $route->setRequirements([]);
        $route->setOptions([]);
        $route->setSchemes([]);
        $route->setMethods([]);
        $route->setHost('');

        return $route;
    }
}

Fixture/import sync for static routes. For routes that are really code (homepage, sitemap), keep a YAML file in the repo and push it into ES during deploy, upserting by name. This gives you version control for the stable routes while still allowing runtime routes for the dynamic ones:

foreach ($yaml['routes'] as $name => $def) {
    $route = $repository->find($name) ?: $this->factory->create($name, $def['path'], $def['controller']);
    $route->setDefaults($def['defaults'] ?? []);
    $manager->persist($route);
}
$manager->commit();

Generate URLs the normal way. Once the router is swapped, generate() and absolute_url() work unchanged, because the provider returns compiled Symfony\Component\Routing\Route objects:

return $this->redirectToRoute('promo_show', ['slug' => 'spring-sale']);

This is the main reason to use the bundle rather than hand-rolling a matcher — Twig's path() keeps working.

Localised routes. Emit one document per locale with the same path but different _locale defaults, and make the path itself prefixed (/de/promo/{slug}). Names must still be unique, so suffix them (promo_show.de). Because matching is first-match-wins over an unordered collection, put the stricter/prefix paths in requirements rather than relying on insertion order.

Integration points

  • Decorate ongr.router.route_provider if you need filtering (e.g. only routes for the current tenant/domain) instead of forking the bundle.
  • Override ongr.router.route_compiler if you need extra document fields mapped onto the Symfony Route.
  • Use a kernel.request listener for anything the router can't express (redirects, 410s) rather than abusing condition.

Warm it. Since RouteProvider builds the collection from ES, add a cache warmer or a kernel.cache_warmer-adjacent deploy step that serialises the collection, and have your provider read that on cold cache. Without this you pay an ES round-trip per request.

Gotchas and Tips

Pin the constraint yourself, don't trust v2.0.1's fix blindly. The release exists precisely because the ongr/elasticsearch-bundle requirement was wrong. That tells you the constraint had been shipping broken and untested for years. Commit composer.lock, and in CI assert the resolved ongr/elasticsearch-bundle and elasticsearch/elasticsearch versions match what your code was written against — a future constraint edit can silently swap the client underneath you.

Elasticsearch near-real-time refresh. The single most common "the route doesn't exist" bug. After `commit(

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment