Skip to content

Log 404s #65

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: 2.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
23 changes: 22 additions & 1 deletion src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -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()
Expand Down
47 changes: 43 additions & 4 deletions src/EventListener/NotFoundSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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);
Expand Down
82 changes: 82 additions & 0 deletions src/Model/NotFound.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusRedirectPlugin\Model;

use DateTimeInterface;
use Safe\DateTime;
use Sylius\Component\Resource\Model\TimestampableTrait;
use Symfony\Component\HttpFoundation\Request;

class NotFound implements NotFoundInterface
{
use TimestampableTrait;

/** @var int */
protected $id;

/** @var string */
protected $url;

/** @var int */
protected $count = 0;

/** @var bool */
protected $ignored = false;

/** @var DateTimeInterface */
protected $lastRequestAt;

public function getId(): ?int
{
return $this->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();
}
}
32 changes: 32 additions & 0 deletions src/Model/NotFoundInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusRedirectPlugin\Model;

use DateTimeInterface;
use Sylius\Component\Resource\Model\ResourceInterface;
use Sylius\Component\Resource\Model\TimestampableInterface;
use Symfony\Component\HttpFoundation\Request;

interface NotFoundInterface extends ResourceInterface, TimestampableInterface
{
public function getId(): ?int;

public function getUrl(): ?string;

public function setUrl(string $url): void;

public function getCount(): int;

public function setCount(int $count): void;

public function getLastRequestAt(): ?DateTimeInterface;

public function setLastRequestAt(DateTimeInterface $lastRequestAt): void;

/**
* Is called when a 404 occurs in the application
*/
public function onRequest(Request $request): void;
}
21 changes: 21 additions & 0 deletions src/Repository/NotFoundRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusRedirectPlugin\Repository;

use Setono\SyliusRedirectPlugin\Model\NotFoundInterface;
use Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository;

class NotFoundRepository extends EntityRepository implements NotFoundRepositoryInterface
{
public function findOneByUrl(string $url): ?NotFoundInterface
{
return $this->createQueryBuilder('o')
->andWhere('o.url = :url')
->setParameter('url', $url)
->getQuery()
->getOneOrNullResult()
;
}
}
13 changes: 13 additions & 0 deletions src/Repository/NotFoundRepositoryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusRedirectPlugin\Repository;

use Setono\SyliusRedirectPlugin\Model\NotFoundInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;

interface NotFoundRepositoryInterface extends RepositoryInterface
{
public function findOneByUrl(string $url): ?NotFoundInterface;
}
24 changes: 24 additions & 0 deletions src/Resources/config/doctrine/model/NotFound.orm.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>

<doctrine-mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping"
xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<mapped-superclass name="Setono\SyliusRedirectPlugin\Model\NotFound" table="setono_sylius_redirect__not_found">
<id name="id" column="id" type="integer">
<generator strategy="AUTO"/>
</id>

<field name="url" type="string"/>
<field name="count" type="integer"/>
<field name="ignored" type="boolean"/>
<field name="lastRequestAt" type="datetime"/>
<field name="createdAt" column="created_at" type="datetime">
<gedmo:timestampable on="create"/>
</field>
<field name="updatedAt" column="updated_at" type="datetime" nullable="true">
<gedmo:timestampable on="update"/>
</field>
</mapped-superclass>
</doctrine-mapping>
6 changes: 4 additions & 2 deletions src/Resources/config/services/event_listener.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
<argument type="service" id="sylius.context.channel"/>
<argument type="service" id="setono_sylius_redirect.resolver.redirection_path"/>
<tag name="kernel.event_subscriber"/>
</service>
</service>

<service id="setono_sylius_redirect.event_listener.not_found"
class="Setono\SyliusRedirectPlugin\EventListener\NotFoundSubscriber">
<argument type="service" id="setono_sylius_redirect.manager.redirect"/>
<argument type="service" id="sylius.context.channel"/>
<argument type="service" id="setono_sylius_redirect.resolver.redirection_path"/>
<argument type="service" id="setono_sylius_redirect.factory.not_found"/>
<argument type="service" id="setono_sylius_redirect.repository.not_found"/>
<tag name="kernel.event_subscriber"/>
</service>
</services>
Expand Down
2 changes: 1 addition & 1 deletion tests/Application/.env
Original file line number Diff line number Diff line change
Expand Up @@ -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://[email protected]/setono_sylius_redirect_%kernel.environment%?serverVersion=5.5
###< doctrine/doctrine-bundle ###

###> symfony/swiftmailer-bundle ###
Expand Down