Localização temporária no Symfony 4 + Twig

A necessidade de localização temporária do produto surge quando o produto cresce em uma escala que requer trabalho em diferentes fusos horários (evidência). Eu gostaria de descrever uma variante de uma idéia simples para resolver este caso.

O pano de fundo é o seguinte: desenvolvemos um sistema de CRM / ERP de nicho e nos disseram que amanhã eles trabalhariam com esse sistema em uma franquia de Vladivostok a Kaliningrado. Infelizmente, esse cenário não foi originalmente pensado e começamos a aprender como fazer isso com custo mínimo e conveniência máxima.

No total, três tarefas acabaram sendo ampliadas: como produzimos os dados e como entramos, e entre elas armazenamos a tarefa. Como o tempo, como você sabe, é relativamente literal e figurativamente, foi decidido armazenar o tempo como antes no UTC + 3 de Moscou, mas processá-lo na entrada e na saída (e lembre-se de que o ponto de referência é UTC + 3). Obviamente, entendemos que existem outras soluções nesta e em outras direções. Você pode converter todas as entradas existentes para UTC + -0, além de usar tipos especializados no DBMS que armazenam o fuso horário, você mesmo pode escrever esse tipo personalizado, se de repente o banco de dados não suportar totalmente esses recursos. Mas guiados pelo princípio da simplicidade, seguimos o caminho proposto, ainda mais, à primeira vista, ele não perdeu muito para o resto,e a lógica para determinar o fuso horário desejado era bastante simples.

Depois que Moscou se tornou um ponto de referência, adicionamos um parâmetro de fuso horário personalizado a cada usuário, além de várias entidades relacionadas (organização, cidade, aplicativos, transações, etc.). Em seguida, foi possível estabelecer de forma inequívoca em qual fuso horário o usuário ou entidade com a qual ele trabalha. A lógica é padrão e, com frequência, específica para os projetos. Envolvemos essa lógica no serviço e obtivemos um fuso horário no qual você precisa

$localizationService->getTimezone();

A decisão de localizar datas nos modelos foi a seguinte: ao inicializar as extensões do Twig, o fuso horário foi alterado para o desejado:

function __construct(Environment $twig, LocalizationService $localizationService) {
    $twig->getExtension('Twig_Extension_Core')->setTimezone($localizationService->getTimezone());
}

Nossa situação ficou ainda mais complicada pelo fato de que, após a exibição de qualquer data e hora, é necessário criar um subscrito "01.01.2020 12:30 (Moscou)", para que, por exemplo, em uma ordem / tarefa / transação condicional vinculada ao fuso horário, informações sobre a hora cinto. Por motivos práticos, isso é necessário para que um único call center possa trabalhar confortavelmente com diferentes fusos horários na estrutura de uma tarefa / aplicativo / transação.

Toda a lógica para determinar a prioridade dos fusos horários foi conectada ao getTimezone acima mencionado.

Além disso, fomos confrontados com o fato de que, se você criar seu filtro ou função de galho, precisará alterar vários modelos, mas deseja evitar isso. Portanto, após algumas perguntas, decidimos redefinir a data padrão do filtro de galho

...
new TwigFilter('date', [$this, 'date'], ['needs_environment' => true]),
...

function date(Twig_Environment $env, $date, $format = null, $timezone = null)
{
    $appendix = '';
    if (format && strpos($format, 'H:i') !== false)
        $appendix = ' ('.DateTimeFunctions::getRussianAbbrev($this->localizationService->getTimezone()).')';   
...
    //    date   $result
...
   return $result.$appendix;
}

Além disso, desde que adotamos o filtro padrão, a versão antiga foi redefinida:

...
new TwigFilter('native_date', [$this, 'nativeDate'], [ 'needs_environment' => true]),
...
public function nativeDate(Twig_Environment $env, $date, $format = null, $timezone = null)
{
    //    date 
}

O código de filtro de data padrão pode ser encontrado em / twig / twig / lib / Twig / Extension / Core :: twig_date_format_filter. Embora, na verdade, na maioria dos casos, uma opção simples, não muito diferente, faça:

$date->setTimeZone($timezone)
$result = $date->format($format);

Obviamente, você também pode bifurcar ou redefinir a parte mais substancial do Twig, mas se a funcionalidade do filtro padrão for adequada para você, basta retirá-lo separadamente e não perder nada.

Resta resolver o problema de inserir a data e hora. Uma solução:

private function getOffsetHours()
{
    if (!$this->isInit)
        $this->init();

    $local = new \DateTime('now', new \DateTimeZone($this->getTimezone()));
    $user = new \DateTime('now');

    $localOffset = $local->getOffset() / 3600;
    $globalOffset = $user->getOffset() / 3600;

    $diff = $globalOffset - $localOffset;
    return $diff;
}

public function toGlobalTime(\DateTimeInterface $dateTime): \DateTimeInterface 
{
    if (!$this->isInit)
        $this->init();

    $offsetHours = $this->getOffsetHours();

    if ($offsetHours > 0) {
        return $dateTime->modify('+ '.$offsetHours.' hours');
    } else  if($offsetHours < 0){
        return $dateTime->modify($offsetHours.' hours');
    }

    return $dateTime;
}

Em seguida, ligue antes de salvar a data e a hora, por exemplo, nos ouvintes. Essa opção veio até nós, já que basicamente a hora e a data no projeto são fixadas para determinados eventos e não inseridas manualmente. Para outro caso extremo, onde o tempo é constantemente introduzido através de formulários, a solução pode não ser ótima.

Como um bônus. O projeto usa o Omines datatables-bundle para gerar tabelas. Lá a solução foi ainda mais fácil. Em vez de DateTimeColumn para localização, foi utilizado o seguinte:

class CustomDateTimeColumn extends DateTimeColumn
{
    
    private $localizationService;
    private $timeZone;
    
    public function __construct(LocalizationService $localizationService)
    {
        $this->localizationService = $localizationService;
        $this->timeZone = $localizationService->getTimezoneObject();
    }
    
   
    public function normalize($value)
    {
        $value->setTimeZone($this->timeZone);
        return parent::normalize($value);
    }
}

Obrigado pelo seu tempo. Se alguém ajudar a melhorar as coisas básicas da solução, ficarei muito agradecido. Estamos falando de códigos básicos, pois é claro que o código é vazio e, na realidade, possui uma DI muito maior e todo tipo de brindes para uso interno no projeto.

Em suma. É apresentada a idéia de uma solução simples para rápida localização temporária do projeto. Não depende de versões ou, se depender, é fraco. Esta solução foi migrada com sucesso do Symfony 4.2 para 5.

All Articles