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

Routing Kit Bundle Laravel Package

nowo-tech/routing-kit-bundle

A lightweight routing kit bundle for PHP applications that simplifies route definition, request matching, and URL generation. It offers a clean, extensible API for organizing routing logic and adding reusable routing features to Symfony-style projects.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and register

composer require nowo-tech/routing-kit-bundle
// config/bundles.php
return [
    // ...
    NowoTech\RoutingKitBundle\RoutingKitBundle::class => ['all' => true],
];

Confirm the package namespace and service IDs against src/ before copying snippets — treat the class names below as illustrative.

2. Where to look first, in order

File / area Why
README.md Install steps, minimal config, the exact route/cache commands
src/DependencyInjection/Configuration.php The real config tree: storage service alias, locales list, default locale, prefix behaviour
src/DependencyInjection/*Extension.php Which services are aliased, tagged, or made public
`src/Resources/config/routing*.xml yaml`
Storage interface + default implementation The one extension point you are most likely to replace
src/Resources/views/ The Twig CRUD panel — where you will add access control
tests/ Working examples of routing assertions against a real kernel

3. Minimal config

# config/packages/routing_kit.yaml
routing_kit:
    locales: [en, fr, de]
    default_locale: en
    # storage: app.routing_kit.storage   # alias your own implementation here

4. Import the routing loader

# config/routes.yaml
routing_kit:
    resource: .
    type: routing_kit

If the loader is not registered, /foo may still resolve via your normal controllers while /{locale}/foo silently 404s — this is the single most common first-run failure.

5. First use case — end to end

  1. Open the Twig CRUD panel (route path is defined in the bundle's routing config; usually protected by nothing by default — put it behind a firewall role immediately).
  2. Create a canonical path for one existing route, e.g. route product_show, path /products/widget, locale fr.
  3. bin/console cache:clear (or hit the bundle's invalidation path) and verify:
    • GET /fr/products/widget → 200, correct locale in the request
    • GET /products/widget → 200 (dual access) with your canonical tag emitted
  4. Render the URL in Twig via the bundle's path helper instead of hardcoding path().

That is the whole loop: admin defines path → storage persists → router matches → template emits canonical URL.

Implementation Patterns

Storage swap (pluggable storage)

The bundle is DB-driven by design, but the persistence layer is an interface. The two realistic patterns:

  • Doctrine ORM: subclass or reuse the shipped entity, map it to your own table prefix, and alias the storage interface to a repository-backed service. Keeps the CRUD panel working unchanged.
  • Custom source (API, YAML import, legacy table): implement the storage interface, register the service, point routing_kit.storage at it. The CRUD panel becomes read-only or irrelevant — that is fine, the routing side still works.
final readonly class LegacyPathStorage implements PathStorageInterface
{
    public function __construct(private Connection $db) {}

    public function findCanonical(string $route, string $locale): ?string
    {
        // return '/products/widget' or null
    }
}

Keep return types identical to the interface; strict typing means a nullable drift breaks the router silently at match time, not at compile time.

Dual access + canonical strategy

The /foo and /{locale}/foo duality is a feature, not a redirect engine — pick a policy and enforce it in one place:

  • Canonical-only SEO: both serve 200, <link rel="canonical"> and hreflang point at the locale-prefixed URL. Cheapest, safest.
  • Redirect policy: 301 the unprefixed path to the default-locale prefixed path (or the reverse) in a listener with a low priority so it runs after the router has matched.

Put the policy in a kernel.request listener rather than in templates, otherwise you repeat it in every layout.

Twig integration

Use the bundle's helper for all outbound localized links — never concatenate '/' ~ locale ~ path(...):

<a href="{{ routing_kit_path('product_show', {slug: product.slug}, app.request.locale) }}">

Centralising this means a change in prefix behaviour (e.g. dropping /en/) is a config change, not a grep across templates.

Cache invalidation on admin writes

Wire invalidation to the write, not to the deploy:

  • On create/update/delete of a path row, dispatch the bundle's invalidation (or call it from a Doctrine lifecycle listener / postFlush).
  • In multi-server setups, the invalidation must reach shared storage (cache pool / message) — otherwise one node serves stale routes until cache:clear.
public function postUpdate(LifecycleEventArgs $args): void
{
    if ($args->getObject() instanceof CanonicalPath) {
        $this->routeCacheInvalidator->invalidate();
    }
}

SeoKitBundle bridge

When both bundles are installed, the path bridge feeds canonical paths into SEO path generation (sitemaps, hreflang sets). Pattern: define canonical paths first, then let SeoKit consume them; the reverse order produces sitemap entries pointing at 404s.

# ensure the bridge is enabled rather than hand-rolling sitemap URLs
seo_kit:
    routing_kit_bridge: true

Integration tips

  • Register the routing loader before any catch-all/{slug} route in config/routes.yaml, otherwise the locale-prefixed URL is swallowed by the generic route.
  • Seed production from your existing hardcoded paths with a one-off command rather than clicking through the CRUD panel.
  • Add a functional test per locale asserting both URL forms resolve — this is your regression net when upgrading Symfony minors.
  • In CI, run the matrix PHP 8.2 / Symfony 7.4 and PHP 8.4 / Symfony 8.1; the constraints differ and the bundle targets both.

Gotchas and Tips

Route cache is the number one source of "it didn't work"

Editing rows directly in the database does not invalidate the compiled route cache. Always go through the CRUD panel or the invalidation service. On deploy, cache:clear and cache:warmup are mandatory — a warmed cache built before migrations will bake in an empty path table.

Locale prefix shadowing real routes

If you have a real route at /de/about (unlikely) or any first segment matching a locale code, the locale loader will claim it. Constrain the locale segment with a requirements regex built from your configured locale list, and keep that list in config — not duplicated in the routing YAML.

Duplicate content penalty

Dual access means two URLs, one page. If you ship this without canonical/hreflang tags you have created an SEO problem while solving one. Make the canonical tag part of the acceptance criteria, not a follow-up ticket.

CRUD panel ships unsecured

The Twig CRUD routes are ordinary routes; nothing stops an anonymous user from rewriting your URLs. Add access_control entries or a firewall role before enabling in any non-local environment. Also validate submitted paths server-side: leading slash required, no query strings or fragments, no .. segments.

Silent 404s from storage mismatches

A path stored with a trailing slash, a missing leading slash, or the wrong route name will persist fine and simply never match. Add a validation step in the CRUD form and a bin/console sanity check that every stored row resolves through the router.

_locale default interaction

Symfony populates _locale from the request when a route has the parameter. If your controllers read $request->getLocale() without a LocaleListener priority consideration, unprefixed requests can fall back to the framework default rather than default_locale from the bundle config. Assert the resolved locale in a test for both URL forms.

Extension points, ranked by safety

  1. Storage interface — designed to be swapped, lowest risk.
  2. Twig template overrides — override by bundle path, keep the existing block structure so upstream template updates do not break your panel.
  3. Request listeners for redirect/canonical policy — additive, no BC risk.
  4. Router decorator — possible but fragile; avoid unless the bundle's loader genuinely cannot express your case.

Debugging checklist

bin/console debug:router | grep -i routing_kit     # is the loader even loaded?
bin/console debug:container --tag=routing_kit.*    # are your services tagged?
bin/console router:match /fr/products/widget       # what actually matched?

Then confirm the storage layer returns the row you expect for (route, locale) — in most reports, the router is fine and the storage lookup key is wrong.

Upgrade notes

Symfony 8.x requires PHP 8.4+. If your CI runs a single PHP version, a composer update will resolve to 7.4-era packages and mask the 8.1 incompatibilities you will hit in production. Pin the matrix early.

Contribution expectations (if you patch the bundle)

The repo enforces final classes, strict types, constructor injection, English PHPDoc, no BC breaks on config keys/service IDs/Twig blocks, and no agent co-author trailers in commits. Smallest viable patch plus the nearest test is the accepted shape.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource