agence-adeliom/easy-redirect-bundle
Symfony bundle to manage URL redirects and log 404 errors. Intercepts 404s, looks up matching redirects, performs redirects, and updates hit count/last accessed. Stores 404 path/URL/timestamp/referer for stats and clears matching 404s when redirects change.
Install the Bundle
composer require agence-adeliom/easy-redirect-bundle
Ensure your composer.json includes the GitHub recipes endpoint for Flex compatibility.
Configure Database Run migrations:
php bin/console doctrine:migration:diff
php bin/console doctrine:migration:migrate
Or force schema update (if not using migrations):
php bin/console doctrine:schema:update --force
Integrate with EasyAdmin
Add EasyRedirectTrait to your DashboardController and include the redirect menu item:
use Adeliom\EasyRedirectBundle\Admin\EasyRedirectTrait;
class DashboardController extends AbstractDashboardController {
use EasyRedirectTrait;
public function configureMenuItems(): iterable {
yield from $this->configRedirectEntry();
// ... other menu items
}
}
Basic Configuration
Update config/packages/easy_redirect.yaml:
easy_redirect:
redirect_class: App\Entity\Redirect # Replace with your custom entity
not_found_class: App\Entity\NotFound # Replace with your custom entity
remove_not_founds: true # Auto-clean 404s when redirects are updated
/old-url).https://example.com/new-url)./old-url in your browser. The bundle will:
count and updating lastAccessedAt).remove_not_founds: true).Use the RedirectManager service to create/update redirects without EasyAdmin:
use Adeliom\EasyRedirectBundle\Manager\RedirectManagerInterface;
public function __construct(
private RedirectManagerInterface $redirectManager
) {}
public function createRedirect(string $sourcePath, string $destinationUrl): void {
$redirect = $this->redirectManager->create($sourcePath, $destinationUrl);
$this->redirectManager->save($redirect);
}
The bundle auto-captures 404s via Symfony’s KernelEvents::EXCEPTION listener. To manually log a 404:
use Adeliom\EasyRedirectBundle\Manager\NotFoundManagerInterface;
public function __construct(
private NotFoundManagerInterface $notFoundManager
) {}
public function logNotFound(string $path, string $referer = null): void {
$this->notFoundManager->create($path, $referer);
}
Extend the default Redirect and NotFound entities:
// src/Entity/Redirect.php
namespace App\Entity;
use Adeliom\EasyRedirectBundle\Entity\Redirect as BaseRedirect;
class Redirect extends BaseRedirect {
// Add custom fields (e.g., `public ?string $customField = null;`)
}
Update easy_redirect.yaml to reference your entities.
Override the default CRUD controller for advanced features:
use Adeliom\EasyRedirectBundle\Admin\EasyRedirectCrudController;
class CustomRedirectCrudController extends EasyRedirectCrudController {
public function configureFields(string $pageName): iterable {
yield from parent::configureFields($pageName);
// Add custom fields (e.g., $this->addFieldHelper('customField'));
}
}
Create a controller to fetch redirects via API:
use Adeliom\EasyRedirectBundle\Repository\RedirectRepositoryInterface;
class RedirectApiController {
public function __construct(
private RedirectRepositoryInterface $redirectRepository
) {}
public function listRedirects(): Response {
$redirects = $this->redirectRepository->findAll();
return $this->json($redirects);
}
}
Use the NotFoundManager to bulk-delete or export 404 logs:
$notFoundManager->deleteOlderThan(new \DateTime('-30 days'));
Entity Configuration Mismatch
redirect_class/not_found_class in easy_redirect.yaml after extending entities.php bin/console cache:clear) and verify entities exist in the database.Circular Redirects
/a → /b and /b → /a creates an infinite loop.Redirect entity:
public function validateDestination(string $destination): void {
if ($this->sourcePath === $destination) {
throw new \InvalidArgumentException('Circular redirect detected.');
}
}
Performance with High 404 Volume
NotFound records with many entries slows down the dashboard.path and createdAt fields in your NotFound entity:
/**
* @ORM\Index(name="idx_path_created", columns={"path", "createdAt"})
*/
EasyAdmin Menu Item Conflicts
configRedirectEntry() clashes with existing menu items.public function configureMenuItems(): iterable {
yield MenuItem::linkToCrud('Redirects', 'fas fa-exchange-alt', Redirect::class);
// ... other items
}
Check Listener Activation
Verify the RedirectListener is registered:
php bin/console debug:event-dispatcher KernelEvents::EXCEPTION
Should show Adeliom\EasyRedirectBundle\EventListener\RedirectListener.
Log Redirect/404 Events Enable debug mode to see intercepted requests:
# config/packages/dev/easy_redirect.yaml
easy_redirect:
debug: true
Database Schema Validation Compare your schema with the bundle’s defaults:
php bin/console doctrine:schema:validate
Custom Redirect Logic
Override the RedirectListener to add pre/post-redirect logic:
use Adeliom\EasyRedirectBundle\EventListener\RedirectListener as BaseListener;
class CustomRedirectListener extends BaseListener {
public function onKernelException(GetResponseForExceptionEvent $event): void {
// Custom logic before parent::onKernelException()
parent::onKernelException($event);
}
}
Register it in services.yaml:
services:
App\EventListener\CustomRedirectListener:
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException, priority: -10 }
Add Redirect Metadata
Extend the Redirect entity to store additional data (e.g., redirectReason):
class Redirect extends BaseRedirect {
#[ORM\Column(nullable: true)]
public ?string $redirectReason = null;
}
Update the form type (src/Form/RedirectType.php) to include the new field.
Export 404 Reports
Create a custom command to export NotFound data to CSV:
use Adeliom\EasyRedirectBundle\Repository\NotFoundRepositoryInterface;
class ExportNotFoundCommand extends Command {
protected function execute(InputInterface $input, OutputInterface $output): int {
$notFounds = $this->notFoundRepository->findAll();
$csv = new \League\Csv\Writer($output->getOutputStream());
$csv->insertOne(['Path', 'Referer', 'Created At']);
foreach ($notFounds as $notFound) {
$csv->insertOne([
$notFound->getPath(),
$notFound->getReferer(),
$notFound->getCreatedAt()->format('Y-m-d H:i:s'),
]);
}
return Command::SUCCESS;
}
}
remove_not_founds Behavior
true, updating a redirect deletes all NotFound records with matching path.**Case-S
How can I help you explore Laravel packages today?