Skip to content

Instantly share code, notes, and snippets.

@ostrolucky
Last active November 1, 2018 11:38
Show Gist options
  • Save ostrolucky/5061c77597ecc8a71be6e53dee6b22fb to your computer and use it in GitHub Desktop.
Save ostrolucky/5061c77597ecc8a71be6e53dee6b22fb to your computer and use it in GitHub Desktop.
<?php
declare(strict_types=1);
namespace App\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Persists route history to session.
*
* Exposes last_route_uri, which is useful as an replacement of referer,
* when we don't want to go to previous URI, but to URI of previous route
*
* Currently limited to 2 records, because we don't need more at the moment.
*
* History data is saved in following format:
* [
* ['route' => '', 'uri' => ''], <- n-1 route data
* ['route' => '', 'uri' => ''], <- n-2 route data
* ]
*/
class RouteHistorySubscriber implements EventSubscriberInterface
{
public const PREVIOUS_ROUTE_URI = 'last_route_uri';
public const URI_HISTORY = 'uri_buffer';
/**
* @return string[]
*/
public static function getSubscribedEvents(): array
{
return [KernelEvents::REQUEST => 'onKernelRequest', KernelEvents::RESPONSE => 'onKernelResponse'];
}
public function onKernelRequest(GetResponseEvent $event): void
{
if (!$event->isMasterRequest()) {
return;
}
$request = $event->getRequest();
$session = $request->getSession();
$currentRoute = $request->get('_route');
$history = $session->get(self::URI_HISTORY, []);
// Retrieve last URI with different route
$i = 0;
while ($currentRoute === ($history[$i]['route'] ?? null)) {
++$i;
}
// expose it to other layers, e.g. twig
$request->attributes->set(self::PREVIOUS_ROUTE_URI, $history[$i]['uri'] ?? null);
// Do not save internal requests, such as debug profiler ones
if ($currentRoute[0] === '_') {
return;
}
// Do not create new history record if last history record is from same route as current one
if ($currentRoute === ($history[0]['route'] ?? null)) {
return;
}
array_unshift($history, ['route' => $currentRoute, 'uri' => $request->getUri()]);
$session->set(self::URI_HISTORY, array_slice($history, 0, 2));
}
public function onKernelResponse(FilterResponseEvent $event): void
{
$session = $event->getRequest()->getSession();
// Remove routes with redirect responses, we don't want those
if (!$event->getResponse()->isRedirect()) {
return;
}
$session->set(self::URI_HISTORY, array_slice($session->get(self::URI_HISTORY, []), 1));
}
}
<?php
declare(strict_types=1);
namespace App\Tests\EventListener;
use App\EventListener\RouteHistorySubscriber;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class RouteHistorySubscriberTest extends TestCase
{
/**
* @var Session
*/
private static $session;
public static function setUpBeforeClass(): void
{
self::$session = new Session(new MockArraySessionStorage());
}
/**
* @dataProvider provider
*
* @param string[]|string[][] $expectedHistoryContent
*/
public function testOnKernelRequest(
string $inputRoute,
?string $expectedPreviousRoute,
array $expectedHistoryContent
): void {
$request = $this->getMockBuilder(Request::class)->setMethods(['get', 'getUri'])->getMock();
$httpKernel = $this->createMock(HttpKernelInterface::class);
$request->setSession(self::$session);
$event = new GetResponseEvent($httpKernel, $request, HttpKernelInterface::MASTER_REQUEST);
$listener = new RouteHistorySubscriber();
$request->method('get')->with('_route')->willReturn($inputRoute);
$request->method('getUri')->willReturn($inputRoute);
$listener->onKernelRequest($event);
$this->assertSame(
$expectedPreviousRoute,
$request->attributes->get(RouteHistorySubscriber::PREVIOUS_ROUTE_URI)
);
$historyRecords = array_map(function (string $route) {
return ['route' => $route, 'uri' => $route];
}, $expectedHistoryContent);
$this->assertEquals($historyRecords, self::$session->get(RouteHistorySubscriber::URI_HISTORY));
}
/**
* @return string[]|string[][]
*/
public function provider(): array
{
return [
'test handling empty history' => ['a', null, ['a']],
'test filling previous route information from history' => ['b', 'a', ['b', 'a']],
'test removing first route from history array' => ['c', 'b', ['c', 'b']],
'test not overwriting history record if route is same' => ['c', 'b', ['c', 'b']],
'test resume normal function' => ['d', 'c', ['d', 'c']],
];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment