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

Redirect Bundle Laravel Package

kunstmaan/redirect-bundle

KunstmaanRedirectBundle is a Symfony bundle for managing URL redirects from an admin interface. Create and maintain redirects to handle moved content, avoid broken links, and preserve SEO when URLs change.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony 2 bundle, not a Laravel package. If you're coming from Laravel, read it as the equivalent of spatie/laravel-redirects — a Doctrine-backed table of 301/302 rules with an admin CRUD screen bolted onto Kunstmaan's CMS admin. You can't composer require it into a Laravel app (and probably shouldn't; last release was April 2014, 1 star, zero dependents). Use it as a reference implementation or install it inside a legacy Symfony 2.3–2.8 app that already runs Kunstmaan Bundles.

Prerequisites

  • Symfony 2.3–2.8 with symfony/framework-bundle, doctrine/orm
  • kunstmaan/admin-bundle + kunstmaan/node-bundle (the admin UI, layout Twig templates, and AbstractEntity base class live there)
  • A working Kunstmaan admin login/dashboard — the redirect screen renders inside that layout

Install and wire up

composer require kunstmaan/redirect-bundle:dev-master
// app/AppKernel.php
$bundles = [
    // ...
    new Kunstmaan\AdminBundle\KunstmaanAdminBundle(),
    new Kunstmaan\NodeBundle\KunstmaanNodeBundle(),
    new Kunstmaan\RedirectBundle\KunstmaanRedirectBundle(),
];
# app/config/routing.yml
kunstmaan_redirect:
    resource: "@KunstmaanRedirectBundle/Resources/config/routing.yml"
    prefix:   /{_locale}/admin
php app/console doctrine:schema:update --force   # creates the kuma_redirects table

Where to look first, in this order

  1. EventListener/RedirectListener.php — the actual runtime behaviour (a kernel.request listener that short-circuits with a RedirectResponse). Everything else is CRUD around it.
  2. Entity/RedirectRepository.php — the lookup semantics (getRedirectFor($domain, $origin)), including the null-domain fallback.
  3. AdminList/RedirectAdminListConfigurator.php — which columns/actions the editor sees, and where to add filters or an export.
  4. Resources/config/services.yml — listener priority; you will likely touch this.
  5. README.md — short; the code is the documentation.

First use case

Log into the admin, create a redirect with origin = /old-page, target = /new-page, permanent = true, then verify from the outside:

curl -I http://localhost/old-page
# HTTP/1.1 301 Moved Permanently
# Location: http://localhost/new-page

Implementation Patterns

Editorial workflow vs. developer workflow. Editors create rules one at a time in the admin UI ("we killed this landing page"). Developers should seed and bulk-manage rules via fixtures or a console command, because typing 400 legacy URLs into a CMS list widget is not a plan.

Bulk import of a legacy URL map — the single highest-value use of this bundle:

// src/Acme/SiteBundle/Command/ImportRedirectsCommand.php
$em = $this->getContainer()->get('doctrine.orm.entity_manager');
$repo = $em->getRepository('KunstmaanRedirectBundle:Redirect');

foreach (array_map('str_getcsv', file($csvPath)) as list($origin, $target)) {
    $origin = '/'.ltrim(strtolower(trim($origin)), '/');
    if (!$redirect = $repo->findOneBy(['origin' => $origin, 'domain' => null])) {
        $redirect = new Redirect();
        $redirect->setOrigin($origin);
        $redirect->setDomain(null);
    }
    $redirect->setTarget($target);
    $redirect->setPermanent(true);
    $em->persist($redirect);
}
$em->flush();

Upsert by (domain, origin) rather than blind-insert; re-running the import is then safe.

Domain scoping for multi-site. The domain column holds a host; null acts as a wildcard used by the repository's domain = :domain OR domain IS NULL lookup. Convention that works: store null for rules that apply everywhere, and an explicit host only when the same path must go to different targets per site. Be aware the fallback means a specific-domain rule cannot "block" a wildcard rule — the wildcard still matches on other hosts.

Permanent vs. temporary. permanent = true emits 301, otherwise 302. Default to 301 for anything that represents a permanent IA change (SEO link equity), 302 for campaign/seasonal paths where the old URL will come back. Do not use 301 for anything you might untangle later — browsers cache it forever.

Keeping the map honest when slugs change. The bundle does nothing on its own here. Hook Kunstmaan's node save/update event: capture the previous URL before persisting a NodeTranslation, then create a Redirect from old path → new path. That is the single integration that makes the table self-maintaining instead of decaying.

Programmatic lookups for your own features. The repository is the right entry point rather than raw DQL:

$redirect = $em->getRepository('KunstmaanRedirectBundle:Redirect')
    ->getRedirectFor($request->getHost(), $request->getPathInfo());
if ($redirect) {
    return new RedirectResponse($redirect->getTarget(), $redirect->isPermanent() ? 301 : 302);
}

Testing. Functional test the listener end-to-end rather than mocking Doctrine; the listener is the whole point:

$client->request('GET', '/old-page');
$this->assertEquals(301, $client->getResponse()->getStatusCode());
$this->assertEquals('/new-page', $client->getResponse()->headers->get('Location'));

If the listener makes unrelated functional tests brittle (it fires on every request), disable it in the test environment's services.yml and cover it in a dedicated test case.

In Laravel terms, the same architecture is a redirects table (origin, target, status, domain nullable), a global middleware that runs before the router, and a Nova/Filament resource on top. The Kunstmaan bundle is a direct analogue and the pattern ports cleanly.

Gotchas and Tips

  • Abandoned and Symfony-2-only. Last release 2014-04-17. It targets symfony/framework-bundle ~2.x and Kunstmaan's AbstractEntity; it will not resolve against Symfony 3+, let alone Laravel. Pin an exact tag/commit and treat the repo as a reference, or budget a rewrite.
  • Listener priority is load-bearing. It must run on kernel.request before the router resolves the path (priority: 32 is the usual value). Too low and the router throws NotFoundHttpException first; too high and you intercept the profiler/admin toolbar. Add early returns for _profiler, _wdt, and your admin route prefix.
  • Redirects bypass the firewall. Firing at high kernel.request priority means the redirect happens before security, so a redirect on /admin/foo fires for anonymous users too. Usually fine, occasionally an information leak — guard the listener against admin paths.
  • It's a single hop, by design. A→B, B→C means two browser round-trips and two 301s. Loops (A→B→A) are not detected; browsers will bail with ERR_TOO_MANY_REDIRECTS and you will get a bug report. Run an occasional audit script over the table to detect cycles and chains.
  • No wildcards, no regex. Matching is an exact origin comparison against getPathInfo(). /blog/* will not work. Prefix or pattern rules require a custom listener (or, in Laravel, a rule with a {wildcard} segment).
  • Normalise on write, not on read. Lowercase the path, strip trailing slashes, strip the query string before storing. getPathInfo() keeps trailing slashes; /About and /about are different rows and one will silently 404.
  • Query strings are dropped. The redirect target is used verbatim, so ?utm_source= is lost. If campaign attribution matters, append $request->getQueryString() in the listener.
  • Host/port and proxies. getHost() returns the port-free host; behaviour behind a load balancer depends on trusted_proxies being configured. Mismatched hosts are the classic "redirect works locally, not in prod" cause.
  • No caching layer. Every cache miss does a database query on kernel.request — including for 404s. On a content-heavy site, cache the origin→target map (Doctrine result cache or a custom compiler pass writing a PHP array) or the listener becomes your slowest component.
  • Uniqueness constraints bite on re-import. If the schema has a unique index on origin/domain, blind inserts from a CSV will throw; upsert as shown above. Check the generated table definition before relying on the index existing — 2014-era doctrine:schema:update output varies by Doctrine version.
  • Admin UI is not standalone. The Twig templates extend Kunstmaan admin layouts; without kunstmaan/admin-bundle installed and routed you get blank pages or Twig "template not found" errors rather than a helpful message.
  • Extension point for the admin list is RedirectAdminListConfigurator — override buildFields() to show note/domain, buildFilters() to filter by domain, and add getExportFields() if editors want CSV of the whole map. Route to your subclass via the kunstmaan_redirect.admin_controller service definition.
  • Translations live in Resources/translations/messages.*.yml; add a locale file rather than editing the vendor copy.
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.
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
uni-method/symfony-lib