Lokalisasi sementara pada Symfony 4 + Twig

Kebutuhan untuk pelokalan sementara produk muncul ketika produk tumbuh ke skala yang membutuhkan pekerjaan di zona waktu yang berbeda (bukti). Saya ingin menjelaskan varian ide sederhana untuk menyelesaikan kasus ini.

Latar belakangnya adalah ini: kami mengembangkan sistem CRM / ERP ceruk, dan kemudian kami diberitahu bahwa besok mereka akan bekerja dengan sistem ini pada waralaba dari Vladivostok ke Kaliningrad. Sayangnya, skenario seperti itu pada awalnya tidak dipikirkan, dan kami mulai belajar bagaimana melakukan ini dengan biaya minimal dan kenyamanan maksimal.

Secara total, tiga tugas ternyata diperbesar: bagaimana kami menampilkan data dan bagaimana kami masuk, dan di antara mereka tugas bagaimana kami menyimpan semuanya. Karena waktu, seperti yang Anda tahu, secara relatif harfiah dan kiasan, diputuskan untuk menyimpan waktu seperti sebelumnya di UTC + 3 Moskow, tetapi memprosesnya di input dan output (dan perlu diingat bahwa titik referensi adalah UTC + 3). Tentu saja, kami memahami bahwa ada solusi lain dalam hal ini dan arah lainnya. Anda dapat mengonversi semua entri yang ada ke UTC + -0, serta menggunakan tipe khusus dalam DBMS yang menyimpan zona waktu, Anda dapat menulis sendiri tipe kustom ini, jika tiba-tiba database tidak sepenuhnya mendukung fitur-fitur tersebut. Tetapi dibimbing oleh prinsip kesederhanaan, kami mengikuti jalan yang diusulkan, terlebih lagi, pada pandangan pertama, ia tidak kehilangan banyak hal bagi yang lain,dan logika untuk menentukan zona waktu yang diinginkan cukup sederhana.

Setelah Moskow menjadi titik referensi, kami menambahkan parameter zona waktu khusus untuk setiap pengguna, serta sejumlah entitas terkait (organisasi, kota, aplikasi, transaksi, dll.). Kemudian dimungkinkan untuk secara jelas menentukan di mana zona waktu pengguna atau entitas tempat ia bekerja. Logikanya ada standar dan justru sering spesifik untuk proyek. Kami membungkus logika ini dalam layanan dan mendapatkan zona waktu di mana Anda perlu

$localizationService->getTimezone();

Keputusan untuk melokalkan tanggal di templat adalah sebagai berikut: saat menginisialisasi ekstensi Twig, zona waktu diubah ke yang diinginkan:

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

Situasi kami semakin diperumit oleh fakta bahwa setelah tanggal-waktu ditampilkan, perlu membuat subskrip “01.01.2020 12:30 (Moskow)”, sehingga, misalnya, dalam urutan bersyarat / tugas / transaksi yang terkait dengan zona waktu, informasi tentang waktu sabuk. Untuk alasan praktis, ini diperlukan agar call center tunggal dapat bekerja dengan nyaman dengan zona waktu yang berbeda dalam kerangka tugas / aplikasi / transaksi.

Semua logika untuk menentukan prioritas zona waktu disambungkan ke getTimezone yang disebutkan di atas.

Lebih lanjut, kami dihadapkan dengan fakta bahwa jika Anda membuat filter atau fungsi ranting Anda, Anda perlu mengubah banyak templat, tetapi Anda ingin menghindarinya. Karena itu, setelah sedikit bertanya, kami memutuskan untuk mendefinisikan kembali tanggal standar ranting-filter

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

Juga, karena kami mengambil filter standar, versi yang lama telah didefinisikan ulang:

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

Kode filter tanggal standar dapat ditemukan di / ranting / ranting / lib / Ranting / Ekstensi / Inti :: twig_date_format_filter. Meskipun pada kenyataannya dalam banyak kasus pilihan sederhana, tidak jauh berbeda akan dilakukan:

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

Tentu saja, Anda juga dapat memotong atau mendefinisikan kembali bagian Twig yang lebih substansial, tetapi jika fungsionalitas dari filter standar cocok untuk Anda, Anda dapat mengeluarkannya secara terpisah dan tidak kehilangan apa pun.

Tetap untuk menyelesaikan masalah memasukkan tanggal-waktu. Satu solusi:

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

Kemudian panggil sebelum menyimpan tanggal-waktu, misalnya, dalam pendengar. Opsi ini datang kepada kami, karena pada dasarnya waktu dan tanggal dalam proyek diperbaiki untuk acara tertentu, dan tidak dimasukkan secara manual. Untuk kasus ekstrim lain, di mana waktu secara konstan diperkenalkan melalui formulir, solusinya mungkin tidak optimal.

Sebagai bonus. Proyek ini menggunakan bundel Omines datatables ke tabel output. Di sana solusinya bahkan lebih mudah. Alih-alih DateTimeColumn untuk pelokalan, yang berikut ini digunakan:

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

Terima kasih atas waktu Anda. Jika seseorang membantu meningkatkan hal-hal dasar dari solusi, saya akan sangat berterima kasih. Kita berbicara tentang yang dasar, karena jelas bahwa kodenya vakum dan dalam kenyataannya memiliki DI yang jauh lebih besar dan segala macam barang untuk penggunaan internal dalam proyek.

Singkatnya. Ide solusi sederhana untuk pelokalan sementara cepat proyek disajikan. Itu tidak tergantung pada versi, atau jika itu, itu lemah. Solusi ini berhasil dimigrasi dari Symfony 4.2 ke 5.

All Articles