Last active
November 1, 2018 11:38
-
-
Save ostrolucky/5061c77597ecc8a71be6e53dee6b22fb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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