قراءة البيانات وكتابتها على الويب

(اختياري) إنشاء نموذج أوّلي واختباره باستخدام Firebase Local Emulator Suite

قبل التحدّث عن كيفية قراءة تطبيقك من Realtime Database والكتابة إليه، دعنا نقدّم مجموعة من الأدوات التي يمكنك استخدامها لإنشاء نماذج أولية واختبار وظائف Realtime Database، وهي: Firebase Local Emulator Suite. إذا كنت بصدد تجربة نماذج بيانات مختلفة أو تحسين قواعد الأمان أو البحث عن الطريقة الأكثر فعالية من حيث التكلفة للتفاعل مع الخلفية، قد يكون من المفيد أن تتمكّن من العمل محليًا بدون نشر الخدمات المباشرة.

يُعدّ Realtime Database المحاكي جزءًا من Local Emulator Suite، ما يتيح لتطبيقك التفاعل مع المحتوى والإعدادات المحاكية لقاعدة البيانات، بالإضافة إلى موارد المشروع المحاكية (الدوال وقواعد البيانات الأخرى وقواعد الأمان) بشكل اختياري.

لا يتطلّب استخدام محاكي Realtime Database سوى بضع خطوات:

  1. إضافة سطر من الرمز البرمجي إلى إعدادات الاختبار في تطبيقك للاتصال بالمحاكي
  2. من جذر دليل مشروعك المحلي، شغِّل firebase emulators:start.
  3. إجراء مكالمات من رمز النموذج الأولي لتطبيقك باستخدام حزمة تطوير البرامج (SDK) الخاصة بمنصة Realtime Database كالمعتاد، أو باستخدام واجهة برمجة التطبيقات REST الخاصة بمنصة Realtime Database

يتوفّر شرح تفصيلي يتضمّن Realtime Database وCloud Functions. ننصحك أيضًا بالاطّلاع على مقدمة Local Emulator Suite.

الحصول على مرجع قاعدة بيانات

لقراءة البيانات أو كتابتها في قاعدة البيانات، تحتاج إلى مثيل من firebase.database.Reference:

Web

import { getDatabase } from "firebase/database";

const database = getDatabase();

Web

var database = firebase.database();

كتابة البيانات

يتناول هذا المستند أساسيات استرداد البيانات وكيفية ترتيب بيانات Firebase وتصفيتها.

يتم استرداد بيانات Firebase من خلال ربط أداة معالجة غير متزامنة بـ firebase.database.Reference. يتم تشغيل أداة معالجة البيانات مرة واحدة للحالة الأولية للبيانات، ومرة أخرى كلما تغيرت البيانات.

عمليات الكتابة الأساسية

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

Web

import { getDatabase, ref, set } from "firebase/database";

function writeUserData(userId, name, email, imageUrl) {
  const db = getDatabase();
  set(ref(db, 'users/' + userId), {
    username: name,
    email: email,
    profile_picture : imageUrl
  });
}

Web

function writeUserData(userId, name, email, imageUrl) {
  firebase.database().ref('users/' + userId).set({
    username: name,
    email: email,
    profile_picture : imageUrl
  });
}

يؤدي استخدام set() إلى الكتابة فوق البيانات في الموقع الجغرافي المحدّد، بما في ذلك أي عقد فرعية.

قراءة البيانات

الاستماع إلى أحداث القيمة

لقراءة البيانات في مسار معيّن والاستماع إلى التغييرات، استخدِم onValue() لمراقبة الأحداث. يمكنك استخدام هذا الحدث لقراءة لقطات ثابتة من المحتوى في مسار معيّن، كما كانت موجودة في وقت الحدث. يتم تشغيل هذه الطريقة مرة واحدة عند ربط أداة معالجة البيانات، ومرة أخرى في كل مرة تتغير فيها البيانات، بما في ذلك بيانات العناصر التابعة. يتم تمرير لقطة إلى دالة معاودة الاتصال الخاصة بالحدث، وتحتوي هذه اللقطة على جميع البيانات في ذلك الموقع، بما في ذلك بيانات العناصر التابعة. إذا لم تتوفّر بيانات، ستعرض اللقطة false عند طلب exists() وnull عند طلب val().

يوضّح المثال التالي تطبيقًا للتدوين على وسائل التواصل الاجتماعي يسترد عدد النجوم التي حصل عليها منشور من قاعدة البيانات:

Web

import { getDatabase, ref, onValue } from "firebase/database";

const db = getDatabase();
const starCountRef = ref(db, 'posts/' + postId + '/starCount');
onValue(starCountRef, (snapshot) => {
  const data = snapshot.val();
  updateStarCount(postElement, data);
});

Web

var starCountRef = firebase.database().ref('posts/' + postId + '/starCount');
starCountRef.on('value', (snapshot) => {
  const data = snapshot.val();
  updateStarCount(postElement, data);
});

يتلقّى المستمع snapshot يحتوي على البيانات في الموقع الجغرافي المحدّد في قاعدة البيانات في وقت الحدث. يمكنك استرداد البيانات في snapshot باستخدام الطريقة val().

قراءة البيانات مرة واحدة

قراءة البيانات مرة واحدة باستخدام get()

تم تصميم حزمة SDK لإدارة التفاعلات مع خوادم قواعد البيانات، سواء كان تطبيقك متصلاً بالإنترنت أو غير متصل.

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

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

يمكن أن يؤدي الاستخدام غير الضروري لـ get() إلى زيادة استخدام النطاق الترددي وإلى فقدان الأداء، ويمكن تجنُّب ذلك باستخدام أداة استماع في الوقت الفعلي كما هو موضّح أعلاه.

Web

import { getDatabase, ref, child, get } from "firebase/database";

const dbRef = ref(getDatabase());
get(child(dbRef, `users/${userId}`)).then((snapshot) => {
  if (snapshot.exists()) {
    console.log(snapshot.val());
  } else {
    console.log("No data available");
  }
}).catch((error) => {
  console.error(error);
});

Web

const dbRef = firebase.database().ref();
dbRef.child("users").child(userId).get().then((snapshot) => {
  if (snapshot.exists()) {
    console.log(snapshot.val());
  } else {
    console.log("No data available");
  }
}).catch((error) => {
  console.error(error);
});

قراءة البيانات مرة واحدة باستخدام مراقب

في بعض الحالات، قد تحتاج إلى عرض القيمة من ذاكرة التخزين المؤقت المحلية على الفور، بدلاً من التحقّق من وجود قيمة معدَّلة على الخادم. في هذه الحالات، يمكنك استخدام once() للحصول على البيانات من ذاكرة التخزين المؤقت على القرص المحلي على الفور.

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

Web

import { getDatabase, ref, onValue } from "firebase/database";
import { getAuth } from "firebase/auth";

const db = getDatabase();
const auth = getAuth();

const userId = auth.currentUser.uid;
return onValue(ref(db, '/users/' + userId), (snapshot) => {
  const username = (snapshot.val() && snapshot.val().username) || 'Anonymous';
  // ...
}, {
  onlyOnce: true
});

Web

var userId = firebase.auth().currentUser.uid;
return firebase.database().ref('/users/' + userId).once('value').then((snapshot) => {
  var username = (snapshot.val() && snapshot.val().username) || 'Anonymous';
  // ...
});

تعديل البيانات أو حذفها

تعديل حقول معيّنة

لكتابة بيانات في عناصر فرعية معيّنة من عقدة في الوقت نفسه بدون الكتابة فوق العُقد الفرعية الأخرى، استخدِم الطريقة update().

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

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

Web

import { getDatabase, ref, child, push, update } from "firebase/database";

function writeNewPost(uid, username, picture, title, body) {
  const db = getDatabase();

  // A post entry.
  const postData = {
    author: username,
    uid: uid,
    body: body,
    title: title,
    starCount: 0,
    authorPic: picture
  };

  // Get a key for a new Post.
  const newPostKey = push(child(ref(db), 'posts')).key;

  // Write the new post's data simultaneously in the posts list and the user's post list.
  const updates = {};
  updates['/posts/' + newPostKey] = postData;
  updates['/user-posts/' + uid + '/' + newPostKey] = postData;

  return update(ref(db), updates);
}

Web

function writeNewPost(uid, username, picture, title, body) {
  // A post entry.
  var postData = {
    author: username,
    uid: uid,
    body: body,
    title: title,
    starCount: 0,
    authorPic: picture
  };

  // Get a key for a new Post.
  var newPostKey = firebase.database().ref().child('posts').push().key;

  // Write the new post's data simultaneously in the posts list and the user's post list.
  var updates = {};
  updates['/posts/' + newPostKey] = postData;
  updates['/user-posts/' + uid + '/' + newPostKey] = postData;

  return firebase.database().ref().update(updates);
}

يستخدم هذا المثال push() لإنشاء مشاركة في العقدة التي تحتوي على مشاركات لجميع المستخدمين في /posts/$postid، واسترداد المفتاح في الوقت نفسه. يمكن استخدام المفتاح بعد ذلك لإنشاء إدخال ثانٍ في مشاركات المستخدم على /user-posts/$userid/$postid.

باستخدام هذه المسارات، يمكنك إجراء تعديلات متزامنة على مواقع متعددة في شجرة JSON من خلال طلب واحد إلى update()، كما هو موضّح في المثال الذي ينشئ المشاركة الجديدة في كلا الموقعين. تكون التعديلات المتزامنة التي يتم إجراؤها بهذه الطريقة كلّية: إما أن تنجح جميع التعديلات أو أن تتعذّر جميعها.

إضافة دالة ردّ الاتصال عند اكتمال العملية

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

Web

import { getDatabase, ref, set } from "firebase/database";

const db = getDatabase();
set(ref(db, 'users/' + userId), {
  username: name,
  email: email,
  profile_picture : imageUrl
})
.then(() => {
  // Data saved successfully!
})
.catch((error) => {
  // The write failed...
});

Web

firebase.database().ref('users/' + userId).set({
  username: name,
  email: email,
  profile_picture : imageUrl
}, (error) => {
  if (error) {
    // The write failed...
  } else {
    // Data saved successfully!
  }
});

حذف البيانات

أبسط طريقة لحذف البيانات هي استدعاء remove() على مرجع إلى موقع تلك البيانات.

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

الحصول على Promise

لمعرفة الوقت الذي يتم فيه إرسال بياناتك إلى خادم Firebase Realtime Database، يمكنك استخدام Promise. يمكن أن تعرض كلّ من set() وupdate() Promise يمكنك استخدامه لمعرفة وقت إتمام عملية الكتابة في قاعدة البيانات.

إلغاء ربط أدوات المعالجة

تتم إزالة عمليات الاسترجاع من خلال استدعاء الطريقة off() في مرجع قاعدة بيانات Firebase.

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

لا يؤدي استدعاء off() على أداة معالجة بيانات تابعة إلى إزالة أدوات معالجة البيانات المسجّلة على العُقد التابعة تلقائيًا، بل يجب أيضًا استدعاء off() على أي أدوات معالجة بيانات تابعة لإزالة وظيفة معاودة الاتصال.

حفظ البيانات كمعاملات

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

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

Web

import { getDatabase, ref, runTransaction } from "firebase/database";

function toggleStar(uid) {
  const db = getDatabase();
  const postRef = ref(db, '/posts/foo-bar-123');

  runTransaction(postRef, (post) => {
    if (post) {
      if (post.stars && post.stars[uid]) {
        post.starCount--;
        post.stars[uid] = null;
      } else {
        post.starCount++;
        if (!post.stars) {
          post.stars = {};
        }
        post.stars[uid] = true;
      }
    }
    return post;
  });
}

Web

function toggleStar(postRef, uid) {
  postRef.transaction((post) => {
    if (post) {
      if (post.stars && post.stars[uid]) {
        post.starCount--;
        post.stars[uid] = null;
      } else {
        post.starCount++;
        if (!post.stars) {
          post.stars = {};
        }
        post.stars[uid] = true;
      }
    }
    return post;
  });
}

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

الزيادات الذرية من جهة الخادم

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

Web

function addStar(uid, key) {
  import { getDatabase, increment, ref, update } from "firebase/database";
  const dbRef = ref(getDatabase());

  const updates = {};
  updates[`posts/${key}/stars/${uid}`] = true;
  updates[`posts/${key}/starCount`] = increment(1);
  updates[`user-posts/${key}/stars/${uid}`] = true;
  updates[`user-posts/${key}/starCount`] = increment(1);
  update(dbRef, updates);
}

Web

function addStar(uid, key) {
  const updates = {};
  updates[`posts/${key}/stars/${uid}`] = true;
  updates[`posts/${key}/starCount`] = firebase.database.ServerValue.increment(1);
  updates[`user-posts/${key}/stars/${uid}`] = true;
  updates[`user-posts/${key}/starCount`] = firebase.database.ServerValue.increment(1);
  firebase.database().ref().update(updates);
}

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

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

العمل على البيانات بلا اتصال بالإنترنت

إذا فقد أحد العملاء اتصاله بالشبكة، سيستمر تطبيقك في العمل بشكل صحيح.

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

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

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

سنتحدّث أكثر عن السلوك غير الإلكتروني في مقالة مزيد من المعلومات عن الإمكانات الإلكترونية وغير الإلكترونية.

الخطوات التالية