مشغِّلات Realtime Database (الجيل الأول)

باستخدام Cloud Functions، يمكنك معالجة الأحداث في Firebase Realtime Database بدون الحاجة إلى تعديل رمز العميل. يتيح لك Cloud Functions تشغيل عمليات Realtime Database بامتيازات إدارية كاملة، ويضمن معالجة كل تغيير في Realtime Database بشكل فردي. يمكنك إجراء تغييرات على Firebase Realtime Database من خلال DataSnapshot أو من خلال مدير SDK.

في مراحل النشاط العادية، تؤدي دالة Firebase Realtime Database ما يلي:

  1. ينتظر التغييرات في موقع جغرافي معيّن على Realtime Database.
  2. يتم تفعيله عند وقوع حدث معيّن وينفّذ مهامه (راجِع ما الذي يمكنني فعله باستخدام Cloud Functions؟ للحصول على أمثلة لحالات الاستخدام).
  3. تتلقّى كائن بيانات يحتوي على لقطة من البيانات المخزّنة في المستند المحدّد.

تفعيل دالة Realtime Database

يمكنك إنشاء دوال جديدة لأحداث Realtime Database باستخدام functions.database. للتحكّم في وقت تفعيل الدالة، حدِّد أحد معالِجات الأحداث، وحدِّد مسار Realtime Database الذي سيتم فيه الاستماع إلى الأحداث.

ضبط معالِج الأحداث

تتيح لك الدوال التعامل مع أحداث Realtime Database على مستويين من الخصوصية: يمكنك الاستماع إلى أحداث الإنشاء أو التعديل أو الحذف فقط، أو يمكنك الاستماع إلى أي تغيير من أي نوع في المسار. يتيح Cloud Functions معالِجات الأحداث هذه لـ Realtime Database:

  • onWrite()، والذي يتم تشغيله عند إنشاء البيانات أو تحديثها أو حذفها في Realtime Database.
  • onCreate()، الذي يتم تشغيله عند إنشاء بيانات جديدة في Realtime Database.
  • onUpdate()، والذي يتم تشغيله عند تحديث البيانات في Realtime Database .
  • onDelete()، الذي يتم تشغيله عند حذف البيانات من Realtime Database .

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

للتحكّم في وقت ومكان تشغيل الدالة، يمكنك استدعاء ref(path) لتحديد مسار، وتحديد مثيل Realtime Database اختياريًا باستخدام instance('INSTANCE_NAME'). إذا لم تحدّد مثيلاً، يتم نشر الدالة في مثيل Realtime Database التلقائي لمشروع Firebase، على سبيل المثال:

  • مثيل Realtime Database التلقائي: functions.database.ref('/foo/bar')
  • مثيل باسم "my-app-db-2": functions.database.instance('my-app-db-2').ref('/foo/bar')

توجّه هذه الطرق الدالة للتعامل مع عمليات الكتابة في مسار معيّن ضمن مثيل Realtime Database. تتطابق مواصفات المسار مع جميع عمليات الكتابة التي تلمس مسارًا، بما في ذلك عمليات الكتابة التي تحدث في أي مكان أسفله. إذا ضبطت المسار للدالة على /foo/bar، فستتم مطابقة الأحداث في كلا الموقعين التاليين:

 /foo/bar
 /foo/bar/baz/really/deep/path

في كلتا الحالتين، يفسّر Firebase أنّ الحدث يقع في /foo/bar، وتتضمّن بيانات الحدث البيانات القديمة والجديدة في /foo/bar. إذا كانت بيانات الأحداث كبيرة، ننصحك باستخدام دوال متعددة في مسارات أعمق بدلاً من دالة واحدة بالقرب من جذر قاعدة البيانات. للحصول على أفضل أداء، اطلب البيانات على أعمق مستوى ممكن فقط.

يمكنك تحديد مكوّن مسار كحرف بدل من خلال إحاطته بأقواس معقوفة، حيث يتطابق ref('foo/{bar}') مع أي عنصر فرعي من /foo. تتوفّر قيم مكوّنات المسار البديلة هذه ضمن العنصر EventContext.params في الدالة. في هذا المثال، تكون القيمة متاحة على النحو التالي: context.params.bar.

يمكن أن تتطابق المسارات التي تحتوي على أحرف بدل مع أحداث متعددة من عملية كتابة واحدة. إدراج

{
  "foo": {
    "hello": "world",
    "firebase": "functions"
  }
}

يطابق المسار "/foo/{bar}" مرتين: مرة مع "hello": "world" ومرة أخرى مع "firebase": "functions".

التعامل مع بيانات الأحداث

عند معالجة حدث Realtime Database، يكون عنصر البيانات الذي يتم عرضه هو DataSnapshot. بالنسبة إلى أحداث onWrite أو onUpdate، تكون المَعلمة الأولى هي عنصر Change يحتوي على لقطتَين تمثّلان حالة البيانات قبل الحدث الذي أدّى إلى تفعيل المشغّل وبعده. بالنسبة إلى الحدثَين onCreate وonDelete، يكون عنصر البيانات الذي يتم عرضه لقطة من البيانات التي تم إنشاؤها أو حذفها.

في هذا المثال، تسترد الدالة اللقطة للمسار المحدّد، وتحوّل السلسلة في ذلك الموقع إلى أحرف كبيرة، وتكتب تلك السلسلة المعدّلة في قاعدة البيانات:

// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snapshot, context) => {
      // Grab the current value of what was written to the Realtime Database.
      const original = snapshot.val();
      functions.logger.log('Uppercasing', context.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing asynchronous tasks inside a Functions such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the Realtime Database returns a Promise.
      return snapshot.ref.parent.child('uppercase').set(uppercase);
    });

الوصول إلى معلومات مصادقة المستخدم

من EventContext.auth وEventContext.authType، يمكنك الوصول إلى معلومات المستخدم، بما في ذلك الأذونات، للمستخدم الذي شغّل دالة. يمكن أن يكون هذا مفيدًا لفرض قواعد الأمان، مما يسمح للدالة بإكمال عمليات مختلفة بناءً على مستوى أذونات المستخدم:

const functions = require('firebase-functions/v1');
const admin = require('firebase-admin');

exports.simpleDbFunction = functions.database.ref('/path')
    .onCreate((snap, context) => {
      if (context.authType === 'ADMIN') {
        // do something
      } else if (context.authType === 'USER') {
        console.log(snap.val(), 'written by', context.auth.uid);
      }
    });

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

exports.impersonateMakeUpperCase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snap, context) => {
      const appOptions = JSON.parse(process.env.FIREBASE_CONFIG);
      appOptions.databaseAuthVariableOverride = context.auth;
      const app = admin.initializeApp(appOptions, 'app');
      const uppercase = snap.val().toUpperCase();
      const ref = snap.ref.parent.child('uppercase');

      const deleteApp = () => app.delete().catch(() => null);

      return app.database().ref(ref).set(uppercase).then(res => {
        // Deleting the app is necessary for preventing concurrency leaks
        return deleteApp().then(() => res);
      }).catch(err => {
        return deleteApp().then(() => Promise.reject(err));
      });
    });

قراءة القيمة السابقة

يحتوي العنصر Change على السمة before التي تتيح لك فحص ما تم حفظه في Realtime Database قبل الحدث. تعرض السمة before قيمة DataSnapshot حيث تشير جميع الطرق (مثل val() وexists()) إلى القيمة السابقة. يمكنك قراءة القيمة الجديدة مرة أخرى إما باستخدام DataSnapshot الأصلي أو قراءة السمة after. هذه السمة في أي Change هي DataSnapshot أخرى تمثّل حالة البيانات بعد وقوع الحدث.

على سبيل المثال، يمكن استخدام السمة before للتأكّد من أنّ الدالة تحوّل النص إلى أحرف كبيرة فقط عند إنشائه لأول مرة:

exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onWrite((change, context) => {
      // Only edit data when it is first created.
      if (change.before.exists()) {
        return null;
      }
      // Exit when the data is deleted.
      if (!change.after.exists()) {
        return null;
      }
      // Grab the current value of what was written to the Realtime Database.
      const original = change.after.val();
      console.log('Uppercasing', context.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing asynchronous tasks inside a Functions such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the Realtime Database returns a Promise.
      return change.after.ref.parent.child('uppercase').set(uppercase);
    });