FirebaseのCloud Firestoreをつかってみた。

らら
らら

はじめに

昔、Firebaseでたてに、すこしやってみて、sumみたいのができなくて、チャットあぷりなんだ・・・で、

終わって以降、さわってなかったので、どうも集計らしきができるらしいので再度チャレンジ・・

Firebase

Firebaseは、2011年にFirebase, Inc.が開発したモバイル・Webアプリケーション開発プラットフォーム

2014年にGoogleに買収されたらしい。

サービス


	Firebase Cloud Messaging
	Firebase Authentication
	Firebase Realtime Database
	Cloud Firestore
	Firebase Storage
	Firebase Hosting

データベース系は2つある・・・

今回の目的は、Firestore Database Cloud Firestore

Realtime Databaseは別ものぽい

Realtime DatabaseのJavascriptの記述例


var db = firebase.database();
db.ref("user_data").push({
	name: "名前",
	zip: "485",
});

Cloud FirestoreのJavascriptの記述例


var db = firebase.firestore();
db.collection("user_data").add({
	name: "名前",
	zip: "485"
}).then((doc) => {
	console.log("追加しました");
}).catch((error) => {
	console.log(error);
});

ぐぐって、サンプル拾ったりする場合は、上記を注意してみるとよいかも・・

PHPなどのライブラリ等も、同様な感じになっているので・・


$realtimeDatabase	= $factory->createDatabase();
$firestore			= $factory->createFirestore();

無料利用枠/1日あたり


データの保存	1GBストレージ
ドキュメントの読み込み	50,000
ドキュメントの書き込み	20,000
ドキュメントの削除	20,000

Cloud Firestoreの場合。用語が・・・

コレクション→ドキュメント→コレクション

データベース→レコード→フィールド


コレクション+ドキュメント+コレクション+フィールド
            |                         +フィールド
            |                         +フィールド
            +ドキュメント+コレクション+フィールド
                                      +フィールド
                                      +フィールド

認証とセキュリティ

Firebase Authenticationを使用しない場合・デバックモードで

下記のタグをさえ書いてしまえば、どこからでも登録できる?


	<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js"></script>
	<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-auth.js"></script>
	<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-firestore.js"></script>
	<script>
		var config = {
			apiKey: "",
			authDomain: "xxxxxxxxfirebaseapp.com",
			databaseURL: "https://xxxxxxxxxx.firebaseio.com",
			projectId: "xxxxxxxx",
			storageBucket: "xxxxxxxx.appspot.com",
			messagingSenderId: "",
			appId: ":web"
		};
		//////////////////////////////////////////////////////////////
		firebase.initializeApp(config);
		var db = firebase.firestore();
		db.collection("user_data").add({
			name: "名前",
			zip: "485"
		}).then((doc) => {
			console.log("追加しました");
		}).catch((error) => {
			console.log(error);
		});
	</script>

認証を使用した場合

ログインページを利用して入力されたID,パスワードか、下記の中でログイン情報を記述すると・・・セキュリティの意味がない...


	<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js"></script>
	<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-auth.js"></script>
	<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-firestore.js"></script>
	<script>
		var config = {
			apiKey: "",
			authDomain: "xxxxxxxxfirebaseapp.com",
			databaseURL: "https://xxxxxxxxxx.firebaseio.com",
			projectId: "xxxxxxxx",
			storageBucket: "xxxxxxxx.appspot.com",
			messagingSenderId: "",
			appId: ":web"
		};
		//////////////////////////////////////////////////////////////
		firebase.initializeApp(config);
		email = "xxxxx";
		password = "xxxxxx";
		firebase.auth().signInWithEmailAndPassword(auth, email, password).then(function(result) {
			var db = firebase.firestore();
			db.collection("user_data").add({
				name: "名前",
				zip: "485"
			}).then((doc) => {
				console.log("追加しました");
			}).catch((error) => {
				console.log(error);
			});
		}).catch(function(error) {
			console.log(error);
		});
	</script>

匿名ログイン

読み出しだけで制御すれば、あり?


	<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js"></script>
	<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-auth.js"></script>
	<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-firestore.js"></script>
	<script>
		var config = {
			apiKey: "",
			authDomain: "xxxxxxxxfirebaseapp.com",
			databaseURL: "https://xxxxxxxxxx.firebaseio.com",
			projectId: "xxxxxxxx",
			storageBucket: "xxxxxxxx.appspot.com",
			messagingSenderId: "",
			appId: ":web"
		};
		//////////////////////////////////////////////////////////////
		firebase.initializeApp(config);
		firebase.auth().signInAnonymously().then(function(result) {
			var id ="??";
			var db = firebase.firestore();
			db.collection("user_data").doc(id).get()
			.then((doc) => {
				var data = doc.data();
			})
			.catch((error) => {
				console.log(error);
			});
		}).catch(function(error) {
			console.log(error);
		});
	</script>

カスタムトークン認証・・

PHP内に、ログイン情報埋め込み、秘密鍵のjson埋め込み・ダウンロード禁止

PHPファイルの自サーバーアクセスのみ許可など・・・

あとは、トークン有効期間・・の制御?


	<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
	<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js"></script>
	<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-auth.js"></script>
	<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-firestore.js"></script>
	<script>
	(function ($) {
		var config = {
			apiKey: "",
			authDomain: "xxxxxxxxfirebaseapp.com",
			databaseURL: "https://xxxxxxxxxx.firebaseio.com",
			projectId: "xxxxxxxx",
			storageBucket: "xxxxxxxx.appspot.com",
			messagingSenderId: "",
			appId: ":web"
		};
		//////////////////////////////////////////////////////////////
		firebase.initializeApp(config);
		$.ajax({
			url: "firebaseClientToken.php",
			method: "POST",
			success: function(token) {
				firebase.auth().signInWithCustomToken(token)
				.then(function (result) {
					db = firebase.firestore();
					//処理・・
				})
				.catch(function (error) {
					alert("failed to anonymously sign-in"+error.code);
				});
			},
			error: function(xhr) {
			}
		});
	}(jQuery));
	</script>

Authentication

Monthly active users 50k/month

1 か月のアクティブ ユーザー(SAML / OIDC) 50/month

今回のテーマ

Firebase Hostingからnode.jsとかしないで、サーバーサイドからアクセスできるのか・・検証・・・

レンタルサーバーとか想定・・

Cloud Firestore

PHP側で接続アカウント隠蔽・トークンの作成とか・・できるのか・・とか・・

ぐぐると・・エンドユーザーもログインが必要な感じだけど・・

データベースのようなアカウント風でつかえるのか・・匿名ログインとかもあるようだけど・・

権限とか使って・・制限とか・・どうなのとか・・

Firebaseで準備

はじめに、当然Googleアカウントが必要なので、準備・・

下記からログインして

https://firebase.google.com/?hl=ja

下記からFirebaseプロジェクトを作る。

Cloud Firestore

プロジェクト名をいれて、各種同意して続行する。

Cloud Firestore

現在は、テストなのでアナリティクスは無効で、本番は検討が必要かと・・

Cloud Firestore

作成ボタンを押すと下記が出るので・・続行・・

Cloud Firestore

まずは、Hostingを選択

Cloud Firestore

始めるを・・

Cloud Firestore

ウェブアプリケーションにしたいからチェックして次へ

Cloud Firestore

なんとく読んで・・次へ

Cloud Firestore

テストなので適当に・・・w

Cloud Firestore

ウェブアプリでしたいので、scriptタグを・・選択

貼り付けタグは、コピペして保存しておく・・サンプルもちょっとだけみて。。次へ・・・

Cloud Firestore

わからんけど・・コンソールに進むとかを押してみる・・

Cloud Firestore

Cloud Firestoreを選択して・・

Cloud Firestore

メニューは、Firestore Databaseで中身は・・Cloud Firestoreなのね。。データベース作成を押してみる

Cloud Firestore

何も考えず・・Tokyoで・・

Cloud Firestore

テストなので、変なパーミッションではまるのいやなので・・テストモードで・・・

本番は、本番で・・・・

Cloud Firestore

コレクション開始?!

Cloud Firestore

コレクションID・・・

Cloud Firestore

ドキュメントID・・・

まぁ。フィールド入れて・・・ここでは、構造とデータを登録する感じなので・・注意・・

Cloud Firestore

ここから、APIを使う、アカウントを登録しておく・・Googleアカウントとかそのまま使うと・・

こわいし・・、

この時点で、プロジェクトの設定などを作成するGoogleアカウントとAPI、アプリを利用するアカウントの2個

出てきます・・ここでは、API利用、アプリ利用のアカウントを登録していく感じかと・・

なんか、ぐぐっても、古い画面ばかりで・・

Cloud Firestore

ユーザを追加をぽっちっと・・・

ちょっとこの辺りの順序がさだかで、ないです・・

Cloud Firestore Cloud Firestore

先に・・こっちでプロバイダーを押してから、ログイン情報登録だったと・・

Cloud Firestore

メアドとパスワードで・・・とりえずね。

Cloud Firestore

Firebaseをドキュメントみてたら。UIDよく出てきてたから...

コピーボタン出るし、、コピーしておく・・

Cloud Firestore

ドキュメントで秘密鍵のjsonがってあったので、どこか。探して・・

プロジェクトの設定にあったので、メモ・・

とりあえず・・ダウンロード・・・・・迷路だね。。まじ・・

Cloud Firestore

今のところ・・・下記の3つ?


	Firebase Authentication
	Cloud Firestore
	Firebase Hosting

外部サーバー用のPHPを調べてみる・・

ぐぐってみると・・どれも古い・・動かない・・

おいらが悪いのか・・・

で、いろいろやって、動いたのが・・ここ・・

https://github.com/kreait/firebase-php

https://firebase-php.readthedocs.io/en/7.15.0/

上記も、firebase-php 3.0とか4.0の情報ばかりで・・

自分が。。下記したら、5.6で実行すると・・エラーの嵐・・・・

phpはあんま情報ないのね・


composer require kreait/firebase-php

とりえず・・下記でカスタムトークンが取得できた・・・

var_dumpでダンプしながら。デバックモードで見ながら・・・

var_dumpでみるとlocalIdがUIDぽいからもう少し・・改造できるかも・・

createFirestoreで接続する場合。gRPC PHP extensionの設定等が必要です。

gRPCを使わない・・ライブラリもあるようなので、kreait製じゃないものも選択も・・

あと、Authentication系はadmin、データベース操作系は、Clientといった表記・・


<?php
require __DIR__.'/vendor/autoload.php';
ini_set('display_errors', "On");
use Kreait\Firebase\Factory;
use Kreait\Firebase\ServiceAccount;
use Kreait\Firebase\Firestore;
use Kreait\Firebase\Auth;
	//Firebase アプリケーションを初期化
	$factory = (new Factory)
		->withServiceAccount(__DIR__.'ダウンロードした秘密鍵のjson')
		->withDatabaseUri('自分のDatabaseUriを記述');
var_dump($factory);
	$auth = $factory->createAuth();
	$uid = "コピペしたUID";
	$customToken = $auth->createCustomToken($uid);
var_dump($customToken);
	$token = $customToken->toString();
	echo $token."<br>";
echo "---------------";
exit;

上記PHPでは、アクセスするたびに、新規でトークンが生成されます。

このライブラリーでは、1時間後にトークンが削除されました。

ダンプでは、refreshToken項目で、データは確認できるので・・削除も可能かと・・

システムで、リードだけであれば、現在のままでもよいかな・・

Authenticationするアカウントをリード専用にして、対応するか・・

書き込み用は、書き込み後、すぐに、refreshTokenを削除するとか・・・とか・・

リードとライトアカウント準備して権限設定して、継続か、処理後トークン削除か・・・・

あと、ダウンロードした秘密鍵のjsonをサーバーにアップする場合、自サーバー以外ダウンロードできないようにするか オリジンとか・・HTTP_X_REQUESTED_WITHとか・・

PHP内部に取り込んでしまうか・・・

HTMLとjavascript

上記で。ウェブアプリケーションのscriptタグでコピペしたもので

下記のHTMLでは、トークン固定しているので、該当トークンの有効性が確認できるかと・・

ここで、記載する、apiKey、projectId等は、認証するアカウント等とか必ずログイン機構で認証する前提であれば、

公開されていても問題ないそうです。今回の場合、PHP側にアカウントを埋め込みしているので、とりあえず、隠蔽できているのかね・・


	<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js"></script>
	<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-auth.js"></script>
	<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-firestore.js"></script>
	<script>
		var config = {
			apiKey: "",
			authDomain: "xxxxxxxxfirebaseapp.com",
			databaseURL: "https://xxxxxxxxxx.firebaseio.com",
			projectId: "xxxxxxxx",
			storageBucket: "xxxxxxxx.appspot.com",
			messagingSenderId: "",
			appId: ":web"
		};
		//////////////////////////////////////////////////////////////
		firebase.initializeApp(config);
	</script>

現在は、直書きですが、ここをajax等でphpを呼び出してトークン取得とか・・・

phpは、リファラー等をみて、自サーバーのみ呼び出し可能にするなど・・クロスオリジン確認などなど。

リファラーは、セキュリティソフトなどで禁止されてたりするので工夫は必要・・・


	<script>
	var token = 'phpで取得したカスタムトークン';
	firebase.auth().signInWithCustomToken(token)
		.then(function (result) {
			db = firebase.firestore();
			db.settings({ timestampsInSnapshots: true });
	})
	.catch(function (error) {
	});
	db.collection("自分のコレクション名").get().then(function (querySnapshot) {
		querySnapshot.forEach(function (doc) {
			alert(doc.id);
		});
	});
	</script>

次は・・sumみたいのを・・・・

ああああああ。。。。はまった・・・

どうも、javascript APIもバージョンがあって、V8系とV9では、記述がちがうと・・・・・・

こっちは、下記とはみて・・・・・・

うーむ、途中からはじめると。。。経緯わかんないから・・・・はまるね・・・・

https://firebase.google.com/docs/web/learn-more?hl=ja

どうも、名前空間方式(scriptで呼び出し,import compat)とモジュラー(import compatなしで呼び出し)で新しい機能は・・モジュラー方式でないと

使用ができないということらしい・・・・汗・・

下記のように・・compatがついたものはV8の記述ができるらしい・・あくまでも臨時でなくなる可能性あり・・って・・

importってことは、ES6かな、iphone7,ios10も数パーセントになってきてるから・・・ってこと?!


<script src="https://www.gstatic.com/firebasejs/10.13.1/firebase-app-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/10.13.1/firebase-firestore-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/10.13.1/firebase-auth-compat.js"></script>

<script type="module">
	// Import the functions you need from the SDKs you need
	import { initializeApp } from "https://www.gstatic.com/firebasejs/10.14.0/firebase-app.js";
	import { getFirestore, collection, doc, addDoc , getDoc,getAggregateFromServer, average, count, query ,where,orderBy} from "https://www.gstatic.com/firebasejs/10.14.0/firebase-firestore.js";
	import { getAuth, signInWithCustomToken, signInAnonymously } from "https://www.gstatic.com/firebasejs/10.14.0/firebase-auth.js";
	// TODO: Add SDKs for Firebase products that you want to use
	// https://firebase.google.com/docs/web/setup#available-libraries
	// Your web app's Firebase configuration
	const firebaseConfig = {
		apiKey: "",
		authDomain: "xxxxxxxxfirebaseapp.com",
		databaseURL: "https://xxxxxxxxxx.firebaseio.com",
		projectId: "xxxxxxxx",
		storageBucket: "xxxxxxxx.appspot.com",
		messagingSenderId: "",
		appId: ":web"
	};
	// Initialize Firebase
	const app = initializeApp(firebaseConfig);
	const db = getFirestore(app);
	// Create a reference to the user document
	const Ref = doc(db, "コレクション名", "ドキュメントID");
	// Fetch the document
	const Snap = await getDoc(Ref);
	if (Snap.exists()) {
		console.log("tel:", Snap.data().tel);
		console.log("email:", Snap.data().email);
		console.log("data all:", Snap.data());
	} else {
		console.log("No such document!");
	}
	//追加
	const addRef = collection(db, "rateing");
	// 追加 ID自動生成
	const newRef = await addDoc(addRef, {
		id: id,
		rateing: 3.5,
		createdAt: new Date().toISOString()
	});
	//平均値
	const coll = collection(db, 'rateing');
	const snapshot = await getAggregateFromServer(coll, {
		averagePopulation: average('rateing')
	});
	console.log('average: ', snapshot.data().averagePopulation);
</script>

Whereではまった。。

単一キーと複合キーとあるらしいが・・単一キーは自動で作成されると記載があったので

そのまま、下記のように書くと・・

エラーになる・・ブラウザーの開発環境でエラー内容にURLがあるので、そちらをそのまま、作成

するとエラーは治る・・どうも、whereするものと計算するものを両方いるのかな・・

ただ、whereを使用した場合、下記の場合平均値は、averagePopulationではなく。totalPopulationにはいってくるので

whereを付けた場合、ない場合で戻り値の確認が必要。。。同時にエラーと起きると混乱する・・・


	const coll = collection(db, 'rateing');
	const q = query(coll, where("id", "==", id));
	const snapshot = await getAggregateFromServer(q, {
		averagePopulation: average('rateing')
	});
	console.log('numberOfSales: ', snapshot.data().numberOfSales); 
	console.log('average: ', snapshot.data().averagePopulation);
	console.log('totalPopulation: ', snapshot.data().totalPopulation);
	const rt = snapshot.data().totalPopulation;

ここまでで、気づいたことは、名前空間方式とモジュラー方式では、モジュラー方式の方が、快適に動作する・・・

トークンの処理は・・・ログアウトしておくとか。。

トークン有効時間を、数分にするとか・・


	const auth = getAuth();
	signInWithCustomToken(auth, token).then((userCredential) => {
		//コレクション処理して・・・
		signOut(auth).then(() => {
			// Sign-out successful.
		}).catch((error) => {
			// An error happened.
		});
	})
	.catch((error) => {
	});

jwt版

あとphp側だけど・・やってて気が付いてたけど、jwtでいいような気がしてたのでテストしてみた。

Kreait\Firebaseだと、不要なファイルおおいので・・

composerする


composer require firebase/php-jwt

<?php
require __DIR__.'/vendor/autoload.php';
#ini_set('display_errors', "On");
use Firebase\JWT\JWT;
#	if(!(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest')) {
#		error_msg("許可されていません");
#		return;
#	}
$uid = "コピペしたUID";
$jsonInfo = json_decode(file_get_contents(__DIR__.'ダウンロードした秘密鍵のjson'), true);
$private_key			= $jsonInfo['private_key'];
$service_account_email	= $jsonInfo['client_email'];
$custom_token =  create_custom_token($uid, true);
echo $custom_token;
function create_custom_token($uid, $is_premium_account) {
	global $service_account_email, $private_key;
	$now_seconds = time();
	$payload = array(
		"iss" => $service_account_email,
		"sub" => $service_account_email,
		"aud" => "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
		"iat" => $now_seconds,
		"exp" => $now_seconds+(60*60),	// Maximum expiration time is one hour
		"uid" => $uid,
		"claims" => array(
			"premium_account" => $is_premium_account
		)
	);
	return JWT::encode($payload, $private_key, "RS256");
}

上記で、signInWithCustomTokenでサイインできました。OKぽいですね。

テストがおわったら・・・

下記でテストモードを選択したので下記書き換えを行う・・

Cloud Firestore

設定時は、下記2024, 10, 20まで、読み書きできるみたいな設定です。

テスト環境を伸ばす場合は、単純に期限を変更で問題なさそうですね。


rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // This rule allows anyone with your Firestore database reference to view, edit,
    // and delete all data in your Firestore database. It is useful for getting
    // started, but it is configured to expire after 30 days because it
    // leaves your app open to attackers. At that time, all client
    // requests to your Firestore database will be denied.
    //
    // Make sure to write security rules for your app before that time, or else
    // all client requests to your Firestore database will be denied until you Update
    // your rules
    match /{document=**} {
      allow read, write: if request.time < timestamp.date(2024, 10, 20);
    }
  }
}

https://firebase.google.com/docs/firestore/security/rules-structure?hl=ja

上記をみると・・下記がベースで


service cloud.firestore {
  match /databases/{database}/documents {
//ここに各コレクションごとに制限をかく・・
  }
}

こんな感じでコレクションごとに条件書いていく感じですかね・・・


    match /ターゲットのコレクション名/{document} {
//条件を書く・・・
    }

下記とか


      allow read, write: if request.auth != null

とか?


      allow read, write: if request.auth.uid != null

何となく・・・ってことで。。ここまでで・・・

ここはもう少し。。深堀しないとまずそう・・・・

さいごに

やりながら書いてたので・・下の方が・・、参考になる?!・・

Ver8からVer 10の記述になっていますので注意してね。

途中から、やりはじめると。。知ってて・・当たり前の部分がわからない・・・

あと、セキュリティ周りに気しないと・・・はまる予感がします。

関連記事