وقت الدقة العالية: كيفية التعامل مع كسور الثانية في MySQL و PHP


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


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


سأستخدم مصطلح "وقت الدقة العالية". في وثائق MySQL سترى مصطلح "الثواني الكسرية" ، لكن ترجمتها الحرفية تبدو غريبة ، لكنني لم أجد ترجمة أخرى ثابتة.


متى تستخدم الوقت بدقة عالية؟


بالنسبة للمبتدئين ، سأعرض لقطة شاشة لصندوق الوارد الوارد في صندوق الوارد الخاص بي ، والذي يوضح الفكرة جيدًا:


رسالتين من مرسل واحد


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


لقد صادفت المواقف التالية التي يكون فيها وقت الدقة العالية ذا صلة:


  1. تريد قياس الوقت بين بعض العمليات. كل شيء بسيط للغاية هنا: كلما زادت دقة الطوابع الزمنية عند حدود الفاصل ، زادت دقة النتيجة. إذا كنت تستخدم ثواني كاملة ، فيمكنك ارتكاب خطأ لمدة ثانية واحدة (إذا وقعت على حدود الثواني). إذا كنت تستخدم ستة منازل عشرية ، فسيكون الخطأ ستة أوامر من حيث الحجم.
  2. لديك مجموعة حيث من المحتمل وجود العديد من الكائنات في نفس وقت الإنشاء. مثال على ذلك ، دردشة مألوفة للجميع ، حيث يتم فرز قائمة جهات الاتصال حسب وقت آخر رسالة. إذا ظهر تنقل صفحة تلو الأخرى ، فهناك خطر فقد جهات الاتصال على حدود الصفحة. يمكن حل هذه المشكلة دون وقت من الدقة العالية بسبب الفرز والصفحات من خلال زوج من الحقول (الوقت + المعرف الفريد لكائن) ، ولكن هذا الحل له عيوبه (على الأقل تعقيد استعلامات SQL ، ولكن ليس ذلك فقط). زيادة دقة الوقت ستساعد على تقليل احتمالية حدوث المشكلات وتجنب تعقيد النظام.
  3. تحتاج إلى الاحتفاظ بسجل تغيير لبعض الكائنات. هذا مهم بشكل خاص في عالم الخدمات ، حيث يمكن أن تحدث التعديلات بالتوازي وفي أماكن مختلفة تمامًا. على سبيل المثال ، يمكنني الاستشهاد بالعمل مع صور لمستخدمينا ، حيث يمكن إجراء العديد من العمليات المختلفة بشكل متوازٍ (يمكن للمستخدم جعل الصورة خاصة أو حذفها ، ويمكن إدارتها في أحد الأنظمة العديدة ، اقتصاصها لاستخدامها كصورة في الدردشة ، إلخ.) ).

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



العمل مع الوقت عالية الدقة في الخلية


يدعم MySQL ثلاثة أنواع من الأعمدة التي يمكن تخزين TIME : TIME و DATETIME و TIMESTAMP . في البداية ، يمكنهم فقط تخزين القيم التي كانت مضاعفات ثانية واحدة (على سبيل المثال ، 2019-08-14 19:20:21). في الإصدار 5.6.4 ، الذي تم إصداره في ديسمبر 2011 ، أصبح من الممكن العمل مع الجزء الكسري من الثانية. للقيام بذلك ، عند إنشاء العمود ، تحتاج إلى تحديد عدد المنازل العشرية ، التي يجب تخزينها في الجزء الكسري من الطابع الزمني. الحد الأقصى لعدد الأحرف المدعومة هو ستة أحرف ، مما يسمح لك بتخزين الوقت بدقة على ميكروثانية. إذا حاولت استخدام المزيد من الأحرف ، فسوف تحصل على خطأ.


مثال:


 Test> CREATE TABLE `ChatContactsList` ( `chat_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY, `title` varchar(255) NOT NULL, `last_message_send_time` timestamp(2) NULL DEFAULT NULL ) ENGINE=InnoDB; Query OK, 0 rows affected (0.02 sec) Test> ALTER TABLE `ChatContactsList` MODIFY last_message_send_time TIMESTAMP(9) NOT NULL; ERROR 1426 (42000): Too-big precision 9 specified for 'last_message_send_time'. Maximum is 6. Test> ALTER TABLE `ChatContactsList` MODIFY last_message_send_time TIMESTAMP(3) NOT NULL; Query OK, 0 rows affected (0.09 sec) Records: 0 Duplicates: 0 Warnings: 0 Test> INSERT INTO ChatContactsList (title, last_message_send_time) VALUES ('Chat #1', NOW()); Query OK, 1 row affected (0.03 sec) Test> SELECT * FROM ChatContactsList; +---------+---------+-------------------------+ | chat_id | title | last_message_send_time | +---------+---------+-------------------------+ | 1 | Chat #1 | 2019-09-22 22:23:15.000 | +---------+---------+-------------------------+ 1 row in set (0.00 sec) 

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


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


 Test> UPDATE ChatContactsList SET last_message_send_time="2019-09-22 22:23:15.2345" WHERE chat_id=1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 Test> SELECT * FROM ChatContactsList; +---------+---------+-------------------------+ | chat_id | title | last_message_send_time | +---------+---------+-------------------------+ | 1 | Chat #1 | 2019-09-22 22:23:15.235 | +---------+---------+-------------------------+ 1 row in set (0.00 sec) 

عند استخدام التهيئة التلقائية والتحديث التلقائي لأعمدة TIMESTAMP باستخدام بنية النموذج DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP من المهم أن تكون القيم بنفس دقة الدقة في العمود نفسه:


 Test> ALTER TABLE ChatContactsList ADD COLUMN updated TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP; ERROR 1067 (42000): Invalid default value for 'updated' Test> ALTER TABLE ChatContactsList ADD COLUMN updated TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6); ERROR 1067 (42000): Invalid default value for 'updated' Test> ALTER TABLE ChatContactsList ADD COLUMN updated TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3); Query OK, 0 rows affected (0.07 sec) Records: 0 Duplicates: 0 Warnings: 0 Test> UPDATE ChatContactsList SET last_message_send_time='2019-09-22 22:22:22' WHERE chat_id=1; Query OK, 0 rows affected (0.00 sec) Rows matched: 1 Changed: 0 Warnings: 0 Test> SELECT * FROM ChatContactsList; +---------+---------+-------------------------+-------------------------+ | chat_id | title | last_message_send_time | updated | +---------+---------+-------------------------+-------------------------+ | 1 | Chat #1 | 2019-09-22 22:22:22.000 | 2019-09-22 22:26:39.968 | +---------+---------+-------------------------+-------------------------+ 1 row in set (0.00 sec) 

تعمل وظائف MySQL للعمل مع دعم الوقت مع الجزء الكسري من وحدات القياس. لن أدرجها جميعًا (أقترح البحث في الوثائق) ، لكنني سأقدم بعض الأمثلة:


 Test> SELECT NOW(2), NOW(4), NOW(4) + INTERVAL 7.5 SECOND; +------------------------+--------------------------+------------------------------+ | NOW(2) | NOW(4) | NOW(4) + INTERVAL 7.5 SECOND | +------------------------+--------------------------+------------------------------+ | 2019-09-22 21:12:23.31 | 2019-09-22 21:12:23.3194 | 2019-09-22 21:12:30.8194 | +------------------------+--------------------------+------------------------------+ 1 row in set (0.00 sec) Test> SELECT SUBTIME(CURRENT_TIME(6), CURRENT_TIME(3)), CURRENT_TIME(6), CURRENT_TIME(3); +-------------------------------------------+-----------------+-----------------+ | SUBTIME(CURRENT_TIME(6), CURRENT_TIME(3)) | CURRENT_TIME(6) | CURRENT_TIME(3) | +-------------------------------------------+-----------------+-----------------+ | 00:00:00.000712 | 21:12:50.793712 | 21:12:50.793 | +-------------------------------------------+-----------------+-----------------+ 1 row in set (0.00 sec) 

المشكلة الرئيسية المرتبطة باستخدام الجزء الكسري من الثواني في استعلامات SQL هي عدم تناسق الدقة في المقارنات ( > ، < ، BETWEEN ). قد تواجهها إذا كانت البيانات الموجودة في قاعدة البيانات لها دقة واحدة ، وفي الاستعلامات - أخرى. فيما يلي مثال صغير يوضح هذه المشكلة:


 #         Test> INSERT INTO ChatContactsList (title, last_message_send_time) VALUES ('Chat #2', '2019-09-22 21:16:39.123456'); Query OK, 0 row affected (0.00 sec) Test> SELECT chat_id, title, last_message_send_time FROM ChatContactsList WHERE title='Chat #2'; +---------+---------+-------------------------+ | chat_id | title | last_message_send_time | +---------+---------+-------------------------+ | 2 | Chat #2 | 2019-09-22 21:16:39.123 | <-     - ,    +---------+---------+-------------------------+ 1 row in set (0.00 sec) Test> SELECT title, last_message_send_time FROM ChatContactsList WHERE last_message_send_time >= '2019-09-22 21:16:39.123456'; <-    ,    INSERT- +---------+-------------------------+ | title | last_message_send_time | +---------+-------------------------+ | Chat #1 | 2019-09-22 22:22:22.000 | +---------+-------------------------+ 1 row in set (0.00 sec) <- Chat #2   - ,     ,     

في هذا المثال ، تكون دقة القيم في الاستعلام أعلى من دقة القيم في قاعدة البيانات ، وتحدث المشكلة "على الحدود من أعلى." في الحالة المعاكسة (إذا كانت قيمة الإدخال دقة أقل من القيمة في قاعدة البيانات) فلن تكون هناك مشكلة - MySQL ستجلب القيمة إلى الدقة المطلوبة في كل من INSERT و SELECT:


 Test> INSERT INTO ChatContactsList (title, last_message_send_time) VALUES ('Chat #3', '2019-09-03 21:20:19.1'); Query OK, 1 row affected (0.00 sec) Test> SELECT title, last_message_send_time FROM ChatContactsList WHERE last_message_send_time <= '2019-09-03 21:20:19.1'; +---------+-------------------------+ | title | last_message_send_time | +---------+-------------------------+ | Chat #3 | 2019-09-03 21:20:19.100 | +---------+-------------------------+ 1 row in set (0.00 sec) 

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


أفكار حول اختيار الدقة في الأعمدة ذات الأجزاء الكسرية بالثواني

يعتمد مقدار المساحة التي يشغلها الجزء الكسري من وحدة الوقت على عدد الأحرف في العمود. يبدو من الطبيعي اختيار معاني مألوفة: ثلاثة أو ستة منازل عشرية. لكن في حالة الأحرف الثلاثة ، ليس الأمر بهذه البساطة. في الواقع ، يستخدم MySQL بايت واحد لتخزين منزلتين عشريتين:


ثواني كسور الدقةمتطلبات التخزين
00 بايت
1 ، 21 بايت
3 ، 42 بايت
5 ، 63 بايت


التاريخ والوقت نوع متطلبات التخزين

اتضح أنه إذا قمت بتحديد ثلاثة منازل عشرية ، فأنت لا تستغل المساحة المشغولة بالكامل ويمكن أن تأخذ أربعة أحرف في نفس المساحة. بشكل عام ، أوصي بأن تستخدم دائمًا عددًا متساويًا من الأحرف ، وإذا لزم الأمر ، "اقتصاص" الأحرف غير الضرورية عند الإخراج. الخيار المثالي هو عدم الجشع وتأخذ ستة منازل عشرية. في أسوأ الحالات (مع نوع DATETIME) ، سيشغل هذا العمود 8 بايت ، أي نفس العدد الصحيح في العمود BIGINT.


انظر أيضا:



العمل مع الوقت بدقة عالية في PHP


لا يكفي أن يكون لديك وقت عالي الدقة في قاعدة البيانات - يجب أن تكون قادرًا على العمل به في شفرة برامجك. سأتحدث في هذا القسم عن ثلاث نقاط رئيسية:


  1. وقت الاستلام والتنسيق: سأشرح كيفية الحصول على الطابع الزمني قبل وضعه في قاعدة البيانات ، والحصول عليه من هناك والقيام بنوع من المعالجة.
  2. العمل مع الوقت في PDO: سأريك مثالاً على كيفية دعم PHP لوقت التنسيق في مكتبة قاعدة البيانات.
  3. العمل مع مرور الوقت في أطر العمل: سأتحدث عن استخدام الوقت في عمليات الترحيل لتغيير بنية قاعدة البيانات.

الحصول على وتنسيق الوقت


عند العمل مع الوقت ، هناك عدة عمليات أساسية تحتاج إلى القيام بها:


  • الحصول على النقطة الحالية في الوقت المناسب ؛
  • الحصول على لحظة في الوقت المناسب من بعض سلسلة مهيأ ؛
  • إضافة فترة إلى نقطة زمنية (أو طرح فترة) ؛
  • الحصول على سلسلة منسقة لفترة من الوقت.

سأخبرك في هذا الجزء بإمكانيات تنفيذ هذه العمليات في PHP.


الطريقة الأولى هي العمل مع طابع زمني كرقم . في هذه الحالة ، في كود PHP ، نعمل مع المتغيرات العددية ، والتي نعمل من خلال وظائف مثل time date time date . لا يمكن استخدام هذه الطريقة للعمل مع دقة عالية ، حيث أن الطوابع الزمنية في جميع هذه الوظائف هي عدد صحيح (مما يعني أنه سيتم فقد الجزء الكسري فيها).


فيما يلي توقيعات هذه الوظائف الرئيسية من الوثائق الرسمية:


time ( void ) : int
https://www.php.net/manual/ru/function.time.php

strtotime ( string $time [, int $now = time() ] ) : int
http://php.net/manual/ru/function.strtotime.php

date ( string $format [, int $timestamp = time() ] ) : string
https://php.net/manual/ru/function.date.php

strftime ( string $format [, int $timestamp = time() ] ) : string
https://www.php.net/manual/ru/function.strftime.php

نقطة مثيرة للاهتمام حول وظيفة التاريخ

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


حرف في شكل سلسلةوصفمثال على قيمة الإرجاع
شميكروثانية (أضيف في PHP 5.2.2). لاحظ أن التاريخ () سيعود دائمًا 000000 ، على النحو يستغرق معلمة عدد صحيح ، في حين يدعم DateTime :: format () ميكروثانية إذا تم إنشاء DateTime معهم.على سبيل المثال: 654321
الخامسمللي ثانية (تمت إضافته في PHP 7.0.0). الملاحظة هي نفسها بالنسبة لك.على سبيل المثال: 654

مثال:


 $now = time(); print date('Ymd H:i:s.u', $now); // 2019-09-11 21:27:18.000000 print date('Ymd H:i:s.v', $now); // 2019-09-11 21:27:18.000 

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


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

للعمل بشكل كامل مع الأجزاء الكسرية من الثانية ، تحتاج إلى استخدام الوحدة النمطية DateTime . مع بعض الحجوزات ، يسمح لك بإجراء جميع العمليات المذكورة أعلاه:


 //    : $time = new \DateTimeImmutable(); //      : $time = new \DateTimeImmutable('2019-09-12 21:32:43.908502'); $time = \DateTimeImmutable::createFromFormat('Ymd H:i:s.u', '2019-09-12 21:32:43.9085'); // / : $period = \DateInterval::createFromDateString('5 seconds'); $timeBefore = $time->add($period); $timeAfter = $time->sub($period); //      : print $time->format('Ymd H:i:s.v'); // '2019-09-12 21:32:43.908' print $time->format("Ymd H:i:su"); // '2019-09-12 21:32:43.908502' 

النقطة غير الواضحة عند استخدام `DateTimeImmutable :: createFromFormat`

تعني الحرف u في سلسلة التنسيق microseconds ، لكنها تعمل أيضًا بشكل صحيح في حالة الأجزاء الكسرية ذات الدقة الأقل. علاوة على ذلك ، هذه هي الطريقة الوحيدة لتحديد الأجزاء الكسرية من الثانية في سلسلة التنسيق. مثال:


 $time = \DateTimeImmutable::createFromFormat('Ymd H:i:s.u', '2019-09-12 21:32:43.9085'); // =>   DateTimeImmutable    2019-09-12 21:32:43.908500 $time = \DateTimeImmutable::createFromFormat('Ymd H:i:s.u', '2019-09-12 21:32:43.90'); // =>   DateTimeImmutable    2019-09-12 21:32:43.900000 $time = \DateTimeImmutable::createFromFormat('Ymd H:i:s.u', '2019-09-12 21:32:43'); // =>  false 

تكمن المشكلة الرئيسية لهذه الوحدة في الإزعاج عند العمل بفواصل زمنية تحتوي على ثواني كسرية (أو حتى استحالة مثل هذا العمل). \DateInterval على الرغم من احتوائها على جزء كسري من الثانية دقيقة لنفس المنازل العشرية الستة ، يمكنك فقط تهيئة هذا الجزء الكسري من خلال DateTime::diff . يمكن لمُنشئ فئة DateInterval وطريقة المصنع \DateInterval::createFromDateString العمل فقط مع الثواني الكاملة وعدم السماح بتحديد الجزء الكسري:


 //     -   $buggyPeriod1 = new \DateInterval('PT7.500S'); //       ,    $buggyPeriod2 = \DateInterval::createFromDateString('2 minutes 7.5 seconds'); print $buggyPeriod2->format('%R%H:%I:%S.%F') . PHP_EOL; //  "+00:02:00.000000" 

قد تنشأ مشكلة أخرى عند حساب الفرق بين نقطتين في الوقت باستخدام طريقة \DateTimeImmutable::diff . في PHP قبل الإصدار 7.2.12 ، كان هناك خلل بسبب وجود الأجزاء الكسرية من الثانية بشكل منفصل عن الأرقام الأخرى ويمكن أن تتلقى علامة خاصة بهم:


 $timeBefore = new \DateTimeImmutable('2019-09-12 21:20:19.987654'); $timeAfter = new \DateTimeImmutable('2019-09-14 12:13:14.123456'); $diff = $timeBefore->diff($timeAfter); print $diff->format('%R%a days %H:%I:%S.%F') . PHP_EOL; //  PHP  7.2.12+   "+1 days 14:52:54.135802" //       "+1 days 14:52:55.-864198" 

بشكل عام ، أنصحك أن تكون حذرًا عند العمل بفواصل زمنية وأن تقوم بتغطية هذه الشفرة بعناية بالاختبارات.


انظر أيضا:



العمل مع الوقت بدقة عالية في شركة تنمية نفط عمان


PDO و mysqli هما واجهاتان رئيسيتان للاستعلام عن قواعد بيانات MySQL من كود PHP. في سياق محادثة حول الوقت ، تتشابه مع بعضها البعض ، لذلك سأتحدث فقط عن واحد منهم - شركة تنمية نفط عمان.


عند العمل مع قواعد البيانات في PDO ، يظهر الوقت في مكانين:


  • كمعلمة تم تمريرها إلى الاستعلامات المنفذة ؛
  • كقيمة تأتي استجابةً لاستعلامات SELECT.

من الممارسات الجيدة تمرير العناصر النائبة عند تمرير المعلمات إلى الطلب. يمكن للعناصر النائبة نقل القيم من مجموعة صغيرة جدًا من الأنواع: القيم المنطقية والسلاسل والأعداد الصحيحة. لا يوجد نوع مناسب للتاريخ والوقت ، لذا يجب عليك تحويل القيمة يدويًا من كائن فئة DateTime / DateTimeImmutable إلى سلسلة.


 $now = new \DateTimeImmutable(); $db = new \PDO('mysql:...', 'user', 'password', [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]); $stmt = $db->prepare('INSERT INTO Test.ChatContactsList (title, last_message_send_time) VALUES (:title, :date)'); $result = $stmt->execute([':title' => "Test #1", ':date' => $now->format('Ymd H:i:s.u')]); 

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


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


 $stmt = $db->prepare('SELECT * FROM Test.ChatContactsList ORDER BY last_message_send_time DESC, chat_id DESC LIMIT 5'); $stmt->execute(); while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { $row['last_message_send_time'] = is_null($row['last_message_send_time']) ? null : new \DateTimeImmutable($row['last_message_send_time']); //  -  } 

تعليق

حقيقة أن PDO تقوم بإرجاع البيانات كسلسلة ليست صحيحة تمامًا. عند تلقي القيم ، من الممكن تعيين نوع القيمة للعمود باستخدام PDOStatement::bindColumn . لم أتحدث عن هذا لأن هناك مجموعة محدودة من الأنواع التي لا تساعد في التواريخ.

لسوء الحظ ، هناك مشكلة يجب أن تكون على دراية بها. في PHP قبل الإصدار 7.3 ، يوجد خطأ بسبب PDO ، عندما يتم PDO::ATTR_EMULATE_PREPARES سمة PDO::ATTR_EMULATE_PREPARES "تقطع" الجزء الكسري من الثانية عند تلقيها من قاعدة البيانات. يمكن العثور على التفاصيل ومثال في وصف الأخطاء على php.net . في PHP 7.3 ، تم إصلاح هذا الخطأ وحذر من أن هذا التغيير يكسر التوافق مع الإصدارات السابقة .


إذا كنت تستخدم الإصدار 7.2 من PHP أو الأقدم ولم تكن قادرًا على تحديثه أو تمكين PDO::ATTR_EMULATE_PREPARES ، PDO::ATTR_EMULATE_PREPARES هذا الخطأ عن طريق تصحيح استعلامات SQL التي ترجع الوقت باستخدام جزء كسري بحيث يحتوي هذا العمود على نوع سلسلة. يمكن القيام بذلك ، على سبيل المثال ، مثل هذا:


 SELECT *, CAST(last_message_send_time AS CHAR) AS last_message_send_time_fixed FROM ChatContactsList ORDER BY last_message_send_time DESC LIMIT 1; 

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


انظر أيضا:



العمل مع الوقت بدقة عالية في يي 2


توفر معظم الأُطر الحديثة وظيفة الترحيل التي تتيح لك تخزين محفوظات تغييرات مخطط قاعدة البيانات في الكود وتغييره بشكل متزايد. إذا كنت تستخدم عمليات الترحيل وترغب في استخدام وقت عالي الدقة ، فيجب أن يدعمها إطارك. لحسن الحظ ، هذا يعمل خارج الإطار في جميع الأطر الرئيسية.


في هذا القسم ، سأظهر كيف يتم تنفيذ هذا الدعم في Yii (في الأمثلة التي استخدمتها الإصدار 2.0.26). حول Laravel و Symfony وغيرها ، لن أكتب حتى لا تنتهي المقالة ، لكنني سأكون سعيدًا إذا أضفت تفاصيل في التعليقات أو المقالات الجديدة حول هذا الموضوع.


في الترحيل ، نكتب رمزًا يصف التغييرات في مخطط البيانات. عند إنشاء جدول جديد ، نصف جميع الأعمدة الخاصة به باستخدام طرق خاصة من فئة \ yii \ db \ Migration (يتم الإعلان عنها في علبة SchemaBuilderTrait ). تعد أساليب time timestamp time التي يمكن أن تأخذ قيمة إدخال للدقة مسؤولة عن وصف الأعمدة التي تحتوي على التاريخ والوقت.


مثال على الترحيل الذي يتم فيه إنشاء جدول جديد بعمود زمني عالي الدقة:


 use yii\db\Migration; class m190914_141123_create_news_table extends Migration { public function up() { $this->createTable('news', [ 'id' => $this->primaryKey(), 'title' => $this->string()->notNull(), 'content' => $this->text(), 'published' => $this->timestamp(6), //     ]); } public function down() { $this->dropTable('news'); } } 

وهذا مثال على الترحيل الذي تتغير فيه الدقة في عمود موجود:


 class m190916_045702_change_news_time_precision extends Migration { public function up() { $this->alterColumn( 'news', 'published', $this->timestamp(6) ); return true; } public function down() { $this->alterColumn( 'news', 'published', $this->timestamp(3) ); return true; } } 

ActiveRecord - : , DateTime-. , — «» PDO::ATTR_EMULATE_PREPARES . Yii , . , , PDO.


انظر أيضا:



استنتاج


, , — , . , , . , !

Source: https://habr.com/ru/post/ar469615/


All Articles