vendor/symfony/http-kernel/EventListener/AbstractSessionListener.php line 142

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\HttpKernel\EventListener;
  11. use Psr\Container\ContainerInterface;
  12. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  13. use Symfony\Component\HttpFoundation\Session\Session;
  14. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  15. use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
  16. use Symfony\Component\HttpKernel\Event\RequestEvent;
  17. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  18. use Symfony\Component\HttpKernel\Exception\UnexpectedSessionUsageException;
  19. use Symfony\Component\HttpKernel\KernelEvents;
  20. /**
  21.  * Sets the session onto the request on the "kernel.request" event and saves
  22.  * it on the "kernel.response" event.
  23.  *
  24.  * In addition, if the session has been started it overrides the Cache-Control
  25.  * header in such a way that all caching is disabled in that case.
  26.  * If you have a scenario where caching responses with session information in
  27.  * them makes sense, you can disable this behaviour by setting the header
  28.  * AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER on the response.
  29.  *
  30.  * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  31.  * @author Tobias Schultze <http://tobion.de>
  32.  *
  33.  * @internal
  34.  */
  35. abstract class AbstractSessionListener implements EventSubscriberInterface
  36. {
  37.     const NO_AUTO_CACHE_CONTROL_HEADER 'Symfony-Session-NoAutoCacheControl';
  38.     protected $container;
  39.     private $sessionUsageStack = [];
  40.     private $debug;
  41.     public function __construct(ContainerInterface $container nullbool $debug false)
  42.     {
  43.         $this->container $container;
  44.         $this->debug $debug;
  45.     }
  46.     public function onKernelRequest(RequestEvent $event)
  47.     {
  48.         if (!$event->isMasterRequest()) {
  49.             return;
  50.         }
  51.         $session null;
  52.         $request $event->getRequest();
  53.         if ($request->hasSession()) {
  54.             // no-op
  55.         } elseif (method_exists($request'setSessionFactory')) {
  56.             $request->setSessionFactory(function () { return $this->getSession(); });
  57.         } elseif ($session $this->getSession()) {
  58.             $request->setSession($session);
  59.         }
  60.         $session $session ?? ($this->container && $this->container->has('initialized_session') ? $this->container->get('initialized_session') : null);
  61.         $this->sessionUsageStack[] = $session instanceof Session $session->getUsageIndex() : 0;
  62.     }
  63.     public function onKernelResponse(ResponseEvent $event)
  64.     {
  65.         if (!$event->isMasterRequest()) {
  66.             return;
  67.         }
  68.         $response $event->getResponse();
  69.         $autoCacheControl = !$response->headers->has(self::NO_AUTO_CACHE_CONTROL_HEADER);
  70.         // Always remove the internal header if present
  71.         $response->headers->remove(self::NO_AUTO_CACHE_CONTROL_HEADER);
  72.         if (!$session $this->container && $this->container->has('initialized_session') ? $this->container->get('initialized_session') : $event->getRequest()->getSession()) {
  73.             return;
  74.         }
  75.         if ($session->isStarted()) {
  76.             /*
  77.              * Saves the session, in case it is still open, before sending the response/headers.
  78.              *
  79.              * This ensures several things in case the developer did not save the session explicitly:
  80.              *
  81.              *  * If a session save handler without locking is used, it ensures the data is available
  82.              *    on the next request, e.g. after a redirect. PHPs auto-save at script end via
  83.              *    session_register_shutdown is executed after fastcgi_finish_request. So in this case
  84.              *    the data could be missing the next request because it might not be saved the moment
  85.              *    the new request is processed.
  86.              *  * A locking save handler (e.g. the native 'files') circumvents concurrency problems like
  87.              *    the one above. But by saving the session before long-running things in the terminate event,
  88.              *    we ensure the session is not blocked longer than needed.
  89.              *  * When regenerating the session ID no locking is involved in PHPs session design. See
  90.              *    https://bugs.php.net/61470 for a discussion. So in this case, the session must
  91.              *    be saved anyway before sending the headers with the new session ID. Otherwise session
  92.              *    data could get lost again for concurrent requests with the new ID. One result could be
  93.              *    that you get logged out after just logging in.
  94.              *
  95.              * This listener should be executed as one of the last listeners, so that previous listeners
  96.              * can still operate on the open session. This prevents the overhead of restarting it.
  97.              * Listeners after closing the session can still work with the session as usual because
  98.              * Symfonys session implementation starts the session on demand. So writing to it after
  99.              * it is saved will just restart it.
  100.              */
  101.             $session->save();
  102.         }
  103.         if ($session instanceof Session $session->getUsageIndex() === end($this->sessionUsageStack) : !$session->isStarted()) {
  104.             return;
  105.         }
  106.         if ($autoCacheControl) {
  107.             $response
  108.                 ->setExpires(new \DateTime())
  109.                 ->setPrivate()
  110.                 ->setMaxAge(0)
  111.                 ->headers->addCacheControlDirective('must-revalidate');
  112.         }
  113.         if (!$event->getRequest()->attributes->get('_stateless'false)) {
  114.             return;
  115.         }
  116.         if ($this->debug) {
  117.             throw new UnexpectedSessionUsageException('Session was used while the request was declared stateless.');
  118.         }
  119.         if ($this->container->has('logger')) {
  120.             $this->container->get('logger')->warning('Session was used while the request was declared stateless.');
  121.         }
  122.     }
  123.     public function onFinishRequest(FinishRequestEvent $event)
  124.     {
  125.         if ($event->isMasterRequest()) {
  126.             array_pop($this->sessionUsageStack);
  127.         }
  128.     }
  129.     public function onSessionUsage(): void
  130.     {
  131.         if (!$this->debug) {
  132.             return;
  133.         }
  134.         if (!$requestStack $this->container && $this->container->has('request_stack') ? $this->container->get('request_stack') : null) {
  135.             return;
  136.         }
  137.         $stateless false;
  138.         $clonedRequestStack = clone $requestStack;
  139.         while (null !== ($request $clonedRequestStack->pop()) && !$stateless) {
  140.             $stateless $request->attributes->get('_stateless');
  141.         }
  142.         if (!$stateless) {
  143.             return;
  144.         }
  145.         if (!$session $this->container && $this->container->has('initialized_session') ? $this->container->get('initialized_session') : $requestStack->getCurrentRequest()->getSession()) {
  146.             return;
  147.         }
  148.         if ($session->isStarted()) {
  149.             $session->save();
  150.         }
  151.         throw new UnexpectedSessionUsageException('Session was used while the request was declared stateless.');
  152.     }
  153.     public static function getSubscribedEvents(): array
  154.     {
  155.         return [
  156.             KernelEvents::REQUEST => ['onKernelRequest'128],
  157.             // low priority to come after regular response listeners, but higher than StreamedResponseListener
  158.             KernelEvents::RESPONSE => ['onKernelResponse', -1000],
  159.             KernelEvents::FINISH_REQUEST => ['onFinishRequest'],
  160.         ];
  161.     }
  162.     /**
  163.      * Gets the session object.
  164.      *
  165.      * @return SessionInterface|null A SessionInterface instance or null if no session is available
  166.      */
  167.     abstract protected function getSession();
  168. }