Localisation temporaire sur Symfony 4 + Twig

La nécessité d'une localisation temporaire du produit survient lorsque le produit atteint une telle échelle qui nécessite un travail dans différents fuseaux horaires (preuves). Je voudrais décrire une variante d'une idée simple pour résoudre ce cas.

Le contexte est le suivant: nous avons développé un système de niche CRM / ERP, puis on nous a dit que demain, ils travailleraient avec ce système sur une franchise de Vladivostok à Kaliningrad. Malheureusement, un tel scénario n'a pas été pensé à l'origine, et nous avons commencé à apprendre comment le faire avec un coût minimal et une commodité maximale.

Au total, trois tâches se sont avérées être élargies: comment nous sortons les données et comment nous entrons, et entre elles la tâche comment nous les stockons toutes. Comme le temps, comme vous le savez, est relativement littéral et figuré, il a été décidé de stocker le temps comme auparavant à Moscou UTC + 3, mais de le traiter en entrée et en sortie (et toujours garder à l'esprit que le point de référence est UTC + 3). Bien sûr, nous avons compris qu'il existe d'autres solutions dans cette direction et dans d'autres. Vous pouvez convertir toutes les entrées existantes en UTC + -0, ainsi que d'utiliser des types spécialisés dans le SGBD qui stockent le fuseau horaire, vous pouvez écrire ce type personnalisé vous-même, si soudain la base de données ne prend pas pleinement en charge ces fonctionnalités. Mais guidés par le principe de simplicité, nous avons suivi le chemin proposé, d'autant plus qu'à première vue, il n'a pas perdu grand-chose au reste,et la logique pour déterminer le fuseau horaire souhaité était assez simple.

Après que Moscou est devenue un point de référence, nous avons ajouté un paramètre de fuseau horaire personnalisé à chaque utilisateur, ainsi qu'un certain nombre d'entités connexes (organisation, ville, applications, transactions, etc.). Il a ensuite été possible d'établir sans ambiguïté dans quel fuseau horaire l'utilisateur ou l'entité avec laquelle il travaille. La logique y est standard et précisément souvent spécifique aux projets. Nous avons intégré cette logique dans le service et obtenu un fuseau horaire où vous devez

$localizationService->getTimezone();

La décision de localiser les dates dans les modèles a été la suivante: lors de l'initialisation des extensions Twig, le fuseau horaire a été changé pour celui souhaité:

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

Notre situation a été encore compliquée par le fait qu'après l'affichage d'une date-heure, il est nécessaire de faire un indice «01.01.2020 12:30 (Moscou)», de sorte que, par exemple, dans une commande / tâche / transaction conditionnelle liée au fuseau horaire, des informations sur l'heure ceinture. Pour des raisons pratiques, cela est nécessaire pour qu'un seul centre d'appels puisse travailler confortablement avec différents fuseaux horaires dans le cadre d'une tâche / application / transaction.

Toute la logique de détermination de la priorité des fuseaux horaires a été connectée au getTimezone susmentionné.

De plus, nous étions confrontés au fait que si vous créez votre filtre ou fonction de brindille, vous devrez changer un tas de modèles, mais vous voudriez éviter cela. Par conséquent, après une petite question, nous avons décidé de redéfinir la date standard du filtre à brindilles

...
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;
}

De plus, depuis que nous avons pris le filtre standard, l'ancienne version a été redéfinie:

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

Le code de filtre de date standard se trouve dans / twig / twig / lib / Twig / Extension / Core :: twig_date_format_filter. Bien qu'en fait, dans la plupart des cas, une option simple et peu différente fera l'affaire:

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

Bien sûr, vous pouvez également bifurquer ou redéfinir la partie la plus substantielle de Twig, mais si la fonctionnalité du filtre standard vous convient, vous pouvez simplement le retirer séparément et ne rien perdre.

Il reste à résoudre le problème de la saisie de la date-heure. Une solution:

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;
}

Appelez ensuite avant d'enregistrer la date et l'heure, par exemple, dans les écouteurs. Cette option nous est venue, car, fondamentalement, l'heure et la date du projet sont fixées pour certains événements et ne sont pas entrées manuellement. Dans un autre cas extrême, où le temps est constamment introduit à travers les formulaires, la solution peut ne pas être optimale.

En prime. Le projet utilise le bundle de tables de données Omines pour générer des tableaux. Là, la solution était encore plus simple. Au lieu de DateTimeColumn pour la localisation, ce qui suit a été utilisé:

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);
    }
}

Merci pour votre temps. Si quelqu'un aide à améliorer les choses de base de la solution, je serai très reconnaissant. Nous parlons de bases, car il est clair que le code est vide et en réalité a une DI beaucoup plus grande et toutes sortes de goodies à usage interne dans le projet.

En résumé. L'idée d'une solution simple pour une localisation temporaire rapide du projet est présentée. Cela ne dépend pas des versions, ou si c'est le cas, il est faible. Cette solution a migré avec succès de Symfony 4.2 vers 5.

All Articles