توطين مؤقت على Symfony 4 + Twig

تنشأ الحاجة إلى التوطين المؤقت للمنتج عندما ينمو المنتج إلى هذا النطاق الذي يتطلب العمل في مناطق زمنية مختلفة (دليل). أود أن أصف متغيرًا لفكرة بسيطة لحل هذه الحالة.

الخلفية هي: لقد طورنا نظام CRM / ERP متخصصًا ، ثم قيل لنا أنهم سيعملون غدًا مع هذا النظام على امتياز من فلاديفوستوك إلى كالينينغراد. لسوء الحظ ، لم يتم التفكير في مثل هذا السيناريو في الأصل ، وبدأنا نتعلم كيفية القيام بذلك بأقل تكلفة وأقصى راحة.

في المجموع ، تبين أن ثلاث مهام تم تكبيرها: كيف نخرج البيانات وكيف ندخل ، وبينها المهمة كيف نخزنها كلها. نظرًا لأن الوقت ، كما تعلمون ، هو حرفياً ومجازياً نسبيًا ، فقد تقرر تخزين الوقت كما كان من قبل في موسكو UTC + 3 ، ولكن معالجته عند المدخلات والمخرجات (وتذكر أن النقطة المرجعية هي UTC + 3). بالطبع ، فهمنا أن هناك حلولًا أخرى في هذا الاتجاه وفي اتجاهات أخرى. يمكنك تحويل جميع الإدخالات الموجودة إلى UTC + -0 ، بالإضافة إلى استخدام أنواع متخصصة في نظام إدارة قواعد البيانات (DBMS) الذي يقوم بتخزين المنطقة الزمنية ، يمكنك كتابة هذا النوع المخصص بنفسك ، إذا كانت قاعدة البيانات لا تدعم هذه الميزات بشكل كامل فجأة. ولكن مسترشداً بمبدأ البساطة ، ذهبنا على طول المسار المقترح ، علاوة على ذلك ، للوهلة الأولى ، لم يفقد الكثير للبقية ،وكان منطق تحديد المنطقة الزمنية المطلوبة بسيطًا جدًا.

بعد أن أصبحت موسكو نقطة مرجعية ، أضفنا معلمة منطقة زمنية مخصصة لكل مستخدم ، بالإضافة إلى عدد من الكيانات ذات الصلة (المنظمة ، المدينة ، الطلبات ، الصفقات ، إلخ). ثم كان من الممكن تحديد المنطقة الزمنية للمستخدم أو الكيان الذي يعمل معه بشكل لا لبس فيه. المنطق هناك معيار ومحدود في كثير من الأحيان على وجه التحديد للمشاريع. لقد قمنا بلف هذا المنطق في الخدمة وحصلنا على منطقة زمنية حيث تحتاج إليها

$localizationService->getTimezone();

كان قرار توطين التواريخ في القوالب كما يلي: عند تهيئة ملحقات Twig ، تم تغيير المنطقة الزمنية إلى المنطقة المطلوبة:

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

كان وضعنا أكثر تعقيدًا من حقيقة أنه بعد عرض أي وقت ، من الضروري إنشاء نص منخفض "01.01.2020 12:30 (موسكو)" ، بحيث ، على سبيل المثال ، في أمر / مهمة / معاملة مشروطة مرتبطة بالمنطقة الزمنية ، معلومات حول الوقت حزام. ولأسباب عملية ، يعد ذلك ضروريًا حتى يتمكن مركز اتصال واحد من العمل بشكل مريح مع مناطق زمنية مختلفة في إطار مهمة / تطبيق / معاملة.

تم ربط كل منطق تحديد أولوية المناطق الزمنية في getTimezone المذكورة أعلاه.

علاوة على ذلك ، واجهنا حقيقة أنه إذا قمت بعمل مرشح أو وظيفة غصين ، فستحتاج إلى تغيير مجموعة من القوالب ، لكنك أردت تجنب ذلك. لذلك ، بعد قليل من السؤال ، قررنا إعادة تعريف التاريخ القياسي لفلتر التصفية

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

أيضًا ، نظرًا لأننا أخذنا الفلتر القياسي ، فقد تم إعادة تعريف الإصدار القديم:

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

يمكن العثور على رمز مرشح التاريخ القياسي في / twig / twig / lib / Twig / Extension / Core :: twig_date_format_filter. على الرغم من أنه في معظم الحالات ، فإن الخيار البسيط ، لا يختلف كثيرًا:

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

بالطبع ، يمكنك أيضًا شوكة أو إعادة تعريف الجزء الأكثر جوهرية من Twig ، ولكن إذا كانت وظيفة المرشح القياسي تناسبك ، يمكنك ببساطة أخراجه بشكل منفصل وعدم فقدان أي شيء.

يبقى حل مشكلة دخول التاريخ والوقت. حل واحد:

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

ثم اتصل قبل حفظ وقت التاريخ ، على سبيل المثال ، في المستمعين. جاء هذا الخيار إلينا ، لأنه في الأساس يتم إصلاح الوقت والتاريخ في المشروع لأحداث معينة ، ولا يتم إدخاله يدويًا. لحالة متطرفة أخرى ، حيث يتم إدخال الوقت باستمرار من خلال النماذج ، قد لا يكون الحل الأمثل.

كمكافأة. يستخدم المشروع Omines datatables-bundle لجداول الإخراج. كان الحل أسهل. بدلاً من DateTimeColumn للترجمة ، تم استخدام ما يلي:

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

شكرا لك على وقتك. إذا ساعد شخص ما في تحسين الأشياء الأساسية للحل ، فسأكون ممتنًا للغاية. نحن نتحدث عن الأساسيات ، حيث من الواضح أن الشفرة هي فراغ وفي الواقع لديها DI أكبر بكثير وجميع أنواع الأشياء الجيدة للاستخدام الداخلي في المشروع.

باختصار. يتم تقديم فكرة حل بسيط للتوطين المؤقت السريع للمشروع. لا يعتمد على الإصدارات ، أو إذا كان كذلك ، فهو ضعيف. تم ترحيل هذا الحل بنجاح من Symfony 4.2 إلى 5.

All Articles