src/EventSubscriber/TimezoneSubscriber.php line 85

Open in your IDE?
  1. <?php
  2. namespace App\EventSubscriber;
  3. use App\Doctrine\DBAL\Types\DateTimeType;
  4. use App\Entity\UserDateTimeInterface;
  5. use App\Util\DateTimeKernel;
  6. use Doctrine\DBAL\DBALException;
  7. use Doctrine\DBAL\Types\Type;
  8. use Symfony\Component\Console\ConsoleEvents;
  9. use Symfony\Component\Console\Event\ConsoleCommandEvent;
  10. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  11. use Symfony\Component\HttpFoundation\RequestStack;
  12. use Symfony\Component\HttpKernel\Event\RequestEvent;
  13. use Symfony\Component\HttpKernel\KernelEvents;
  14. use Symfony\Component\HttpKernel\KernelInterface;
  15. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  16. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  17. use Twig\Environment;
  18. use Twig\Extension\CoreExtension;
  19. /**
  20.  * Timezone management and additional \DateTime methods.
  21.  */
  22. class TimezoneSubscriber implements EventSubscriberInterface
  23. {
  24.     /** @var KernelInterface */
  25.     private $kernel;
  26.     /** @var RequestStack */
  27.     private $requestStack;
  28.     /** @var TokenStorageInterface */
  29.     private $tokenStorage;
  30.     /** @var Environment */
  31.     private $twig;
  32.     /** @var string */
  33.     private $timeZoneDatabase null;
  34.     /** @var string */
  35.     private $timeZoneClient null;
  36.     /**
  37.      * @param KernelInterface       $kernel
  38.      * @param RequestStack          $requestStack
  39.      * @param TokenStorageInterface $tokenStorage
  40.      * @param Environment           $twig
  41.      * @param string|null           $timeZoneDatabase
  42.      * @param string|null           $timeZoneClient
  43.      */
  44.     public function __construct(
  45.         KernelInterface $kernel,
  46.         RequestStack $requestStack,
  47.         TokenStorageInterface $tokenStorage,
  48.         Environment $twig,
  49.         string $timeZoneDatabase null,
  50.         string $timeZoneClient null
  51.     ) {
  52.         $this->kernel $kernel;
  53.         $this->requestStack $requestStack;
  54.         $this->tokenStorage $tokenStorage;
  55.         $this->twig $twig;
  56.         $this->timeZoneDatabase $timeZoneDatabase;
  57.         $this->timeZoneClient $timeZoneClient;
  58.         $this->initializeTypeOverrides();
  59.     }
  60.     /**
  61.      * {@inheritdoc}
  62.      */
  63.     public static function getSubscribedEvents(): array
  64.     {
  65.         return [
  66.             ConsoleEvents::COMMAND => 'onConsoleCommand',
  67.             KernelEvents::REQUEST => 'onKernelRequest',
  68.         ];
  69.     }
  70.     /**
  71.      * @param ConsoleCommandEvent $event
  72.      */
  73.     public function onConsoleCommand(ConsoleCommandEvent $event): void
  74.     {
  75.         $this->updateDateTimeKernel();
  76.     }
  77.     /**
  78.      * @param RequestEvent $event
  79.      */
  80.     public function onKernelRequest(RequestEvent $event): void
  81.     {
  82.         $this->updateDateTimeKernel();
  83.     }
  84.     /**
  85.      * @throws DBALException
  86.      */
  87.     private function initializeTypeOverrides(): void
  88.     {
  89.         // Save all datetime objects in the configured time zone,
  90.         // See {@link http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/cookbook/working-with-datetime.html}
  91.         Type::overrideType('datetime'DateTimeType::class);
  92.         Type::overrideType('datetimetz'DateTimeType::class);
  93.     }
  94.     /**
  95.      * Method called:.
  96.      *
  97.      * - before controller action and
  98.      * - before console command.
  99.      */
  100.     private function updateDateTimeKernel(): void
  101.     {
  102.         $this->updateKernelTimeZones();
  103.         if (!$this->updateKernelDateTime()) {
  104.             throw new \RuntimeException('Unable to initialize the DateTimeKernel object.');
  105.         }
  106.     }
  107.     /**
  108.      * Updates the timezones.
  109.      */
  110.     private function updateKernelTimeZones(): void
  111.     {
  112.         DateTimeKernel::setTimeZoneDatabase(new \DateTimeZone($this->timeZoneDatabase ?: date_default_timezone_get()));
  113.         DateTimeKernel::setTimeZoneClient(new \DateTimeZone($this->timeZoneClient ?: date_default_timezone_get()));
  114.         /** @var TokenInterface $token */
  115.         if ($token $this->tokenStorage->getToken()) {
  116.             $activeUser $token->getUser();
  117.             if ($activeUser instanceof UserDateTimeInterface) {
  118.                 DateTimeKernel::setTimeZoneClient(new \DateTimeZone($activeUser->getTimezone()));
  119.             }
  120.         }
  121.         // Set PHP server timezone
  122.         date_default_timezone_set(DateTimeKernel::getTimeZoneClient()->getName());
  123.         // Set Twig default timezone
  124.         $this->twig->getExtension(CoreExtension::class)
  125.             ->setTimezone(DateTimeKernel::getTimeZoneClient());
  126.     }
  127.     /**
  128.      * Prerequisites: Method {@see self::updateKernelTimeZones()} must be called before.
  129.      *
  130.      * @return bool
  131.      */
  132.     private function updateKernelDateTime(): bool
  133.     {
  134.         return $this->updateKernelDateTimeByRequestTime()
  135.             || $this->updateKernelDateTimeByKernelStartTime()
  136.             || $this->updateKernelDateTimeByCurrentServerTime();
  137.     }
  138.     /**
  139.      * @return bool
  140.      */
  141.     private function updateKernelDateTimeByRequestTime(): bool
  142.     {
  143.         if (null === $this->requestStack || null === $this->requestStack->getMainRequest()) {
  144.             return false;
  145.         }
  146.         $request $this->requestStack->getMainRequest();
  147.         if (is_numeric($request->server->get('REQUEST_TIME_FLOAT'))) {
  148.             $datetime = \DateTimeImmutable::createFromFormat('U.u'$request->server->get('REQUEST_TIME_FLOAT'));
  149.         } elseif (is_numeric($request->server->get('REQUEST_TIME'))) {
  150.             $datetime = new \DateTimeImmutable(sprintf('@%d'$request->server->get('REQUEST_TIME')));
  151.         }
  152.         if ($datetime ?? null) {
  153.             DateTimeKernel::setDateTimeServer($datetime);
  154.         }
  155.         return true;
  156.     }
  157.     /**
  158.      * @return bool
  159.      */
  160.     private function updateKernelDateTimeByKernelStartTime(): bool
  161.     {
  162.         if (null === $this->kernel || !is_numeric($this->kernel->getStartTime())) {
  163.             return false;
  164.         }
  165.         DateTimeKernel::setDateTimeServer(
  166.             new \DateTimeImmutable(sprintf('@%d'$this->kernel->getStartTime()))
  167.         );
  168.         return true;
  169.     }
  170.     /**
  171.      * @return bool
  172.      */
  173.     private function updateKernelDateTimeByCurrentServerTime(): bool
  174.     {
  175.         DateTimeKernel::setDateTimeServer(
  176.             new \DateTimeImmutable('now', new \DateTimeZone('+00:00'))
  177.         );
  178.         return true;
  179.     }
  180. }