Installation:
composer require braune-digital/redirect-bundle "~1.1"
Register the bundle in config/bundles.php (Laravel) or AppKernel.php (Symfony):
BrauneDigital\RedirectBundle\BrauneDigitalRedirectBundle::class,
First Use Case:
Redirect a legacy URL (/old-page) to a new one (/new-page) with a 301 (permanent) status code:
$redirectManager = app('braune_digital.redirect.manager');
$redirectManager->create('/old-page', '/new-page', 301);
Verify Setup:
Check the redirects table in your database (auto-created by Doctrine migrations). Ensure the Redirect entity exists.
Creating Redirects Programmatically:
Use the RedirectManager to persist redirects dynamically (e.g., during migrations or admin actions):
$manager->create('/deprecated', '/new-endpoint', 302); // Temporary redirect (302)
Middleware Integration: Add a middleware to check redirects before routing:
// app/Http/Middleware/CheckRedirects.php
public function handle($request, Closure $next) {
$redirect = app('braune_digital.redirect.manager')->findByPath($request->path());
if ($redirect) {
return redirect()->to($redirect->getRedirectPath())->setStatusCode($redirect->getStatusCode());
}
return $next($request);
}
Register in app/Http/Kernel.php:
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\CheckRedirects::class,
// ...
],
];
Bulk Redirects: Import redirects from a CSV/Excel file during deployment:
$csv = new \League\Csv\Reader(...);
foreach ($csv->getRecords() as $record) {
$manager->create($record['old_path'], $record['new_path'], $record['status_code'] ?? 301);
}
Admin Panel: Expose CRUD operations via a Laravel Nova/Livewire admin panel:
// Nova Resource
public static $redirectManager;
public function fields(Request $request) {
return [
new Textfield('old_path'),
new Textfield('redirect_path'),
new Select('status_code', [301 => 'Permanent', 302 => 'Temporary']),
new Button('Create', function () {
self::$redirectManager->create($this->old_path, $this->redirect_path, $this->status_code);
}),
];
}
Doctrine Events:
Listen for prePersist/preUpdate to auto-generate slugs or validate paths:
// src/Events/RedirectSubscriber.php
public function onPrePersist(PrePersistEventArgs $args) {
$redirect = $args->getEntity();
if ($redirect instanceof Redirect) {
$redirect->setOldPath(strtolower(trim($redirect->getOldPath(), '/')));
}
}
API Endpoints: Expose redirects as JSON for frontend use:
Route::get('/api/redirects', function () {
return Redirect::all()->map(fn ($r) => [
'old_path' => $r->getOldPath(),
'new_path' => $r->getRedirectPath(),
'status' => $r->getStatusCode(),
]);
});
Testing:
Mock the RedirectManager in PHPUnit:
$mockManager = Mockery::mock('BrauneDigital\RedirectBundle\Manager\RedirectManager');
$mockManager->shouldReceive('findByPath')->andReturn($redirectEntity);
$this->app->instance('braune_digital.redirect.manager', $mockManager);
Path Matching:
/blog/{slug}), use regex or prefix-based matching:
// Custom repository method
public function findByRegexPath($path) {
return $this->createQueryBuilder('r')
->where("r.oldPath REGEXP :path")
->setParameter('path', $path)
->getOneOrNullResult();
}
Status Code Validation:
statusCode by default. Ensure only HTTP-compliant codes (e.g., 301, 302, 307) are used:
// Add validation in Redirect entity
public function setStatusCode($code) {
if (!in_array($code, [301, 302, 307, 308])) {
throw new \InvalidArgumentException("Invalid status code: $code");
}
$this->statusCode = $code;
}
Database Schema:
redirects table. If using a custom schema, override the Entity\Redirect class or configure Doctrine mappings:
# config/packages/braune_digital_redirect.yaml
braune_digital_redirect:
entity: App\Entity\CustomRedirect
Caching:
$cacheKey = "redirect:{$request->path()}";
$redirect = cache()->remember($cacheKey, now()->addHours(1), function () use ($request) {
return app('braune_digital.redirect.manager')->findByPath($request->path());
});
Missing Redirects:
RedirectManager is bound in the container. Check for typos in the service ID (braune_digital.redirect.manager).EntityManager is properly configured to auto-generate the redirects table.Redirect Loops:
if ($request->headers->get('X-Redirect-Checked')) {
return $next($request);
}
$request->headers->set('X-Redirect-Checked', 'true');
Case Sensitivity:
$manager->create('/Old-Path', '/new-path', 301);
// Query with lowercase:
$manager->findByPath(strtolower($request->path()));
Custom Logic:
Extend the Redirect entity to add metadata (e.g., created_at, expires_at):
/**
* @ORM\Column(type="datetime")
*/
private $expiresAt;
Batch Processing: Add a command to bulk-create redirects from a database dump:
// src/Console/Commands/ImportRedirects.php
public function handle() {
$oldRedirects = DB::table('legacy_redirects')->get();
foreach ($oldRedirects as $redirect) {
app('braune_digital.redirect.manager')->create(
$redirect->old_url,
$redirect->new_url,
$redirect->status_code ?? 301
);
}
}
Soft Deletes: Implement soft deletes for redirects:
// Redirect entity
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Column(type="boolean")
*/
private $isDeleted = false;
public function delete() {
$this->isDeleted = true;
$this->deletedAt = new \DateTime();
}
Update the repository to filter out soft-deleted records.
How can I help you explore Laravel packages today?