diff --git a/composer.json b/composer.json
index 0731cd5..b5ecfb2 100644
--- a/composer.json
+++ b/composer.json
@@ -28,6 +28,7 @@
"webmozart/assert": "^1.9"
},
"require-dev": {
+ "doctrine/doctrine-bundle": "^1.12.13",
"phpspec/phpspec": "^6.1",
"phpunit/phpunit": "^8.5",
"roave/security-advisories": "dev-master",
diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php
index 6b6c211..19c6e9f 100644
--- a/src/DependencyInjection/Configuration.php
+++ b/src/DependencyInjection/Configuration.php
@@ -5,9 +5,12 @@
namespace Setono\SyliusRedirectPlugin\DependencyInjection;
use Setono\SyliusRedirectPlugin\Form\Type\RedirectType;
+use Setono\SyliusRedirectPlugin\Model\NotFound;
use Setono\SyliusRedirectPlugin\Model\Redirect;
+use Setono\SyliusRedirectPlugin\Repository\NotFoundRepository;
use Setono\SyliusRedirectPlugin\Repository\RedirectRepository;
use Sylius\Bundle\ResourceBundle\Controller\ResourceController;
+use Sylius\Bundle\ResourceBundle\Form\Type\DefaultResourceType;
use Sylius\Bundle\ResourceBundle\SyliusResourceBundle;
use Sylius\Component\Resource\Factory\Factory;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
@@ -26,7 +29,9 @@ public function getConfigTreeBuilder(): TreeBuilder
$rootNode
->addDefaultsIfNotSet()
->children()
- ->scalarNode('driver')->defaultValue(SyliusResourceBundle::DRIVER_DOCTRINE_ORM)->end()
+ ->scalarNode('driver')
+ ->defaultValue(SyliusResourceBundle::DRIVER_DOCTRINE_ORM)
+ ->end()
->integerNode('remove_after')
->info('0 means disabled. If the value is > 0 then redirects that have not been accessed in the last x days will be removed')
->defaultValue(0)
@@ -61,6 +66,22 @@ private function addResourcesSection(ArrayNodeDefinition $node): void
->end()
->end()
->end()
+ ->arrayNode('not_found')
+ ->addDefaultsIfNotSet()
+ ->children()
+ ->variableNode('options')->end()
+ ->arrayNode('classes')
+ ->addDefaultsIfNotSet()
+ ->children()
+ ->scalarNode('model')->defaultValue(NotFound::class)->cannotBeEmpty()->end()
+ ->scalarNode('controller')->defaultValue(ResourceController::class)->cannotBeEmpty()->end()
+ ->scalarNode('repository')->defaultValue(NotFoundRepository::class)->cannotBeEmpty()->end()
+ ->scalarNode('factory')->defaultValue(Factory::class)->end()
+ ->scalarNode('form')->defaultValue(DefaultResourceType::class)->cannotBeEmpty()->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
->end()
->end()
->end()
diff --git a/src/EventListener/NotFoundSubscriber.php b/src/EventListener/NotFoundSubscriber.php
index 19d8766..eba9a04 100644
--- a/src/EventListener/NotFoundSubscriber.php
+++ b/src/EventListener/NotFoundSubscriber.php
@@ -5,17 +5,27 @@
namespace Setono\SyliusRedirectPlugin\EventListener;
use Doctrine\Common\Persistence\ObjectManager;
+use Setono\SyliusRedirectPlugin\Model\NotFoundInterface;
+use Setono\SyliusRedirectPlugin\Model\RedirectionPath;
+use Setono\SyliusRedirectPlugin\Repository\NotFoundRepositoryInterface;
use Setono\SyliusRedirectPlugin\Resolver\RedirectionPathResolverInterface;
use Sylius\Component\Channel\Context\ChannelContextInterface;
+use Sylius\Component\Resource\Factory\FactoryInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Webmozart\Assert\Assert;
-class NotFoundSubscriber implements EventSubscriberInterface
+/**
+ * This subscriber listens to 404s in the application. It has two outcomes, either it:
+ * - redirects the user to a destination specified in the redirect table, or
+ * - logs the 404, so the user can act upon it
+ */
+final class NotFoundSubscriber implements EventSubscriberInterface
{
/** @var ObjectManager */
private $objectManager;
@@ -26,14 +36,24 @@ class NotFoundSubscriber implements EventSubscriberInterface
/** @var RedirectionPathResolverInterface */
private $redirectionPathResolver;
+ /** @var FactoryInterface */
+ private $notFoundFactory;
+
+ /** @var NotFoundRepositoryInterface */
+ private $notFoundRepository;
+
public function __construct(
ObjectManager $objectManager,
ChannelContextInterface $channelContext,
- RedirectionPathResolverInterface $redirectionPathResolver
+ RedirectionPathResolverInterface $redirectionPathResolver,
+ FactoryInterface $notFoundFactory,
+ NotFoundRepositoryInterface $notFoundRepository
) {
$this->objectManager = $objectManager;
$this->channelContext = $channelContext;
$this->redirectionPathResolver = $redirectionPathResolver;
+ $this->notFoundFactory = $notFoundFactory;
+ $this->notFoundRepository = $notFoundRepository;
}
public static function getSubscribedEvents(): array
@@ -59,11 +79,30 @@ public function onKernelException(ExceptionEvent $event): void
);
if ($redirectionPath->isEmpty()) {
- return;
+ $this->log($event->getRequest());
+ } else {
+ $this->redirect($event, $redirectionPath);
}
- $redirectionPath->markAsAccessed();
$this->objectManager->flush();
+ }
+
+ private function log(Request $request): void
+ {
+ $notFound = $this->notFoundRepository->findOneByUrl($request->getUri());
+ if (null === $notFound) {
+ /** @var NotFoundInterface $notFound */
+ $notFound = $this->notFoundFactory->createNew();
+
+ $this->objectManager->persist($notFound);
+ }
+
+ $notFound->onRequest($request);
+ }
+
+ private function redirect(ExceptionEvent $event, RedirectionPath $redirectionPath): void
+ {
+ $redirectionPath->markAsAccessed();
$lastRedirect = $redirectionPath->last();
Assert::notNull($lastRedirect);
diff --git a/src/Model/NotFound.php b/src/Model/NotFound.php
new file mode 100644
index 0000000..f4c7516
--- /dev/null
+++ b/src/Model/NotFound.php
@@ -0,0 +1,82 @@
+id;
+ }
+
+ public function getUrl(): ?string
+ {
+ return $this->url;
+ }
+
+ public function setUrl(string $url): void
+ {
+ $this->url = $url;
+ }
+
+ public function getCount(): int
+ {
+ return $this->count;
+ }
+
+ public function setCount(int $count): void
+ {
+ $this->count = $count;
+ }
+
+ public function isIgnored(): bool
+ {
+ return $this->ignored;
+ }
+
+ public function setIgnored(bool $ignored): void
+ {
+ $this->ignored = $ignored;
+ }
+
+ public function getLastRequestAt(): ?DateTimeInterface
+ {
+ return $this->lastRequestAt;
+ }
+
+ public function setLastRequestAt(DateTimeInterface $lastRequestAt): void
+ {
+ $this->lastRequestAt = $lastRequestAt;
+ }
+
+ public function onRequest(Request $request): void
+ {
+ ++$this->count;
+ $this->lastRequestAt = new DateTime();
+ $this->url = $request->getUri();
+ }
+}
diff --git a/src/Model/NotFoundInterface.php b/src/Model/NotFoundInterface.php
new file mode 100644
index 0000000..6895484
--- /dev/null
+++ b/src/Model/NotFoundInterface.php
@@ -0,0 +1,32 @@
+createQueryBuilder('o')
+ ->andWhere('o.url = :url')
+ ->setParameter('url', $url)
+ ->getQuery()
+ ->getOneOrNullResult()
+ ;
+ }
+}
diff --git a/src/Repository/NotFoundRepositoryInterface.php b/src/Repository/NotFoundRepositoryInterface.php
new file mode 100644
index 0000000..bb1161d
--- /dev/null
+++ b/src/Repository/NotFoundRepositoryInterface.php
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Resources/config/services/event_listener.xml b/src/Resources/config/services/event_listener.xml
index b1b69f4..e7c83c9 100644
--- a/src/Resources/config/services/event_listener.xml
+++ b/src/Resources/config/services/event_listener.xml
@@ -15,13 +15,15 @@
-
-
+
+
+
+
diff --git a/tests/Application/.env b/tests/Application/.env
index 203a87a..5dc3167 100644
--- a/tests/Application/.env
+++ b/tests/Application/.env
@@ -12,7 +12,7 @@ APP_SECRET=EDITME
# Format described at http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# For a sqlite database, use: "sqlite:///%kernel.project_dir%/var/data.db"
# Set "serverVersion" to your server version to avoid edge-case exceptions and extra database calls
-DATABASE_URL=mysql://root:root@127.0.0.1/setono_sylius_redirect_%kernel.environment%?serverVersion=5.5
+DATABASE_URL=mysql://root@127.0.0.1/setono_sylius_redirect_%kernel.environment%?serverVersion=5.5
###< doctrine/doctrine-bundle ###
###> symfony/swiftmailer-bundle ###