diff --git a/firestore/examples/end2end/README.md b/firestore/examples/end2end/README.md new file mode 100644 index 0000000000..a772c8e289 --- /dev/null +++ b/firestore/examples/end2end/README.md @@ -0,0 +1,46 @@ +# Firestore and PHP + +Detailed documentation on setup, configuration and usage can be found in the [docs](doc/grpc-gcp-php.md). + + +## Run with Docker +To build the Docker image go to directory ```src``` and run +``` +docker build -t rw/grpc-firestore:0.1.0-php . +``` +Display the application usage by running +``` +docker run -ti rw/grpc-firestore:0.1.0-php --help +``` +For authentication a credentials file has to be generated in [https://console.cloud.google.com/apis/credentials] for the appropriate project, which has to be mounted into the container at runtime to file /cred.json : +``` +docker run -ti `-v $PWD/my-credentials-file.json:/cred.json rw/grpc-firestore:0.1.0-php --help +``` + +As stated in the 'usage output', one has to provide projectid, databaseid and (for most calls) collectionid as options. + +The '-ti' flag is mandatory, as it is a interactive program. + +### Examples +Start Interactive mode +``` +docker run -ti \ + -v :/cred.json \ + rw/grpc-firestore:0.1.0-php \ + --interactive true \ + --databaseid "" \ + --projectid \ + --collectionid +``` + +Get Document with DocumentId 'xyz' +``` +docker run -ti \ + -v :/cred.json \ + rw/grpc-firestore:0.1.0-php \ + --api GetDocument \ + -x xyz \ + --databaseid \ + --projectid \ + --collectionid +``` diff --git a/firestore/examples/end2end/doc/assets/image_0.png b/firestore/examples/end2end/doc/assets/image_0.png new file mode 100644 index 0000000000..8f3a6ca0b5 Binary files /dev/null and b/firestore/examples/end2end/doc/assets/image_0.png differ diff --git a/firestore/examples/end2end/doc/assets/image_1.png b/firestore/examples/end2end/doc/assets/image_1.png new file mode 100644 index 0000000000..325908200a Binary files /dev/null and b/firestore/examples/end2end/doc/assets/image_1.png differ diff --git a/firestore/examples/end2end/doc/assets/image_2.png b/firestore/examples/end2end/doc/assets/image_2.png new file mode 100644 index 0000000000..7f0f71c5c3 Binary files /dev/null and b/firestore/examples/end2end/doc/assets/image_2.png differ diff --git a/firestore/examples/end2end/doc/assets/image_3.png b/firestore/examples/end2end/doc/assets/image_3.png new file mode 100644 index 0000000000..179e3a8a26 Binary files /dev/null and b/firestore/examples/end2end/doc/assets/image_3.png differ diff --git a/firestore/examples/end2end/doc/assets/image_4.png b/firestore/examples/end2end/doc/assets/image_4.png new file mode 100644 index 0000000000..bad830b51b Binary files /dev/null and b/firestore/examples/end2end/doc/assets/image_4.png differ diff --git a/firestore/examples/end2end/doc/assets/image_5.png b/firestore/examples/end2end/doc/assets/image_5.png new file mode 100644 index 0000000000..3a1e3039f1 Binary files /dev/null and b/firestore/examples/end2end/doc/assets/image_5.png differ diff --git a/firestore/examples/end2end/doc/assets/image_6.png b/firestore/examples/end2end/doc/assets/image_6.png new file mode 100644 index 0000000000..45a94ebc4a Binary files /dev/null and b/firestore/examples/end2end/doc/assets/image_6.png differ diff --git a/firestore/examples/end2end/doc/grpc-gcp-php.md b/firestore/examples/end2end/doc/grpc-gcp-php.md new file mode 100644 index 0000000000..627e19bde0 --- /dev/null +++ b/firestore/examples/end2end/doc/grpc-gcp-php.md @@ -0,0 +1,212 @@ +How to use firestore in PHP +=========================== + +# Account registration and setup + +Before you start working with firestore you will need to setup your Google Cloud Account, create a project in it and add the fireStore API to the project. If you have already done that then you can skip that chapter and go directly to [Application](#application). + +## Google Cloud Account + +If you don’t have a Google Cloud Account you will need to create one. The Usage of FireStore and other Google Cloud services is not free but you can get an initial free trial period.![image alt text](assets/image_0.png) + +In order to do so you will have to go to [https://console.cloud.google.com/freetrial](https://console.cloud.google.com/freetrial) and follow the steps. After successful registration you will have a trial Google Cloud account that will be enough to get you started. + +## Create a Project + +Login into your Google Cloud Account. After successful login go to that URL [https://console.cloud.google.com/projectcreate?organizationId=0](https://console.cloud.google.com/projectcreate?organizationId=0) and enter the name of the project. For this tutorial we will use "Rogue Wave Project". What you will see is how this name is transferred to a **project id** that we will need further when we start coding with PHP. + +![image alt text](assets/image_1.png) + +## Update Libraries and APIS + +Add fireStore to the list of libraries and API. To do this go to [https://console.cloud.google.com/apis/library?project=rogue-wave-project](https://console.cloud.google.com/apis/library?project=rogue-wave-project). Make sure to replace "rogue-wave-project" with the project id that you had from the previous step, if your project id is different than that one. + +In the search bar enter "firestore" and then add the FireStore API to your library. + +![image alt text](assets/image_2.png) + +## Attach fireStore to your project + +FireStore is Google’s real-time powerful NoSQL database. At the time of this writing FireStore is still in Beta phase. In order to use it in your project and store and manipulate data in it you should go to the following URL [https://console.firebase.google.com](https://console.firebase.google.com) and add your project to it. Choose the "Rogue Wave Project" and specify the region that you are interested in. For better results choose a region that is close to your physical location or you are very well connected to it. ![image alt text](assets/image_3.png) + +## Create your first FireStore database + +Go to [https://console.firebase.google.com/project/rogue-wave-project/database](https://console.firebase.google.com/project/rogue-wave-project/database). This will open an web UI that can help you create your first FireStore database and collections in that database. Make sure also that you set the permissions to "Test Mode" as given below. + +![image alt text](assets/image_4.png) + +Collections are similar in concept to MongoDB collections and SQL tables. For the purposes of this tutorial you should create a collection with the name "users". + +Once you have created the collection add documents in it. For the sample applications we will need a document with an id "peter" and fields “name” and “age”. The fields should be having string and number types respectively. Add also a new document that has the name fields have a value of “john”. + +After adding several records you should end up with a similar picture. + +![image alt text](assets/image_5.png) + +## Creating a service credentials JSON file + +The final and most important part is to create a service account credentials file. This one should be in JSON and our PHP application will use it to read the connection details, setup SSL, and basically provide everything needed to do a normal connection from your local application to the remote Firebase server. + +In order to get your JSON file you should go to: [https://console.cloud.google.com/apis/credentials/serviceaccountkey?project=rogue-wave-project](https://console.cloud.google.com/apis/credentials/serviceaccountkey?project=rogue-wave-project) + +When you create the JSON file make sure to add a service account name with a "Cloud Datastore" role. As shown below. + +![image alt text](assets/image_6.png) + +# Why gRPC + +// TBD + +## Overview + +// TBD + +## Advantages of gRPC + +Faster and less CPU intensive + +## Short information about proto buffers. + +// TBD + +## gRPC and PHP + +// TBD + +## What is FireStore + +FireStore is Google’s real-time NoSQL database. It is still in beta and can be tested from the following website: [https://console.firebase.google.com/project/rogue-wave-project/database](https://console.firebase.google.com/project/rogue-wave-project/database) + +## How things work + +// TBD + +# Application + +## Installation + +At time of this writing official installation instructions for PHP are missing. + +### Credentials Location + +Use the following URL [https://cloud.google.com/firestore/docs/quickstart](https://cloud.google.com/firestore/docs/quickstart) to see how to setup one very important environment variable which should point to the exact location of the JSON file that was created in ["Creating a service credentials JSON file"](#creating-a-service-credentials-json-file). + +In your working environment you should setup the environment variable **GOOGLE_APPLICATION_CREDENTIALS** before running the PHP application(s) that need to connect to Firebase/Firestore. You can either do it in the current terminal +``` +export GOOGLE_APPLICATION_CREDENTIALS="path/to/your/keyfile.json" +``` + +Or you can set it in your PHP script using the following code + +``` +putenv('GOOGLE_APPLICATION_CREDENTIALS='.__DIR__.'/service_credentials.json'); +``` + +**Security notice:** + +Please, not that if you put the JSON file in your project it MUST be protected so that only your application has access to it but the external users cannot access it. If a malicious user has access to your JSON file he can do everything that you are also able to do + +### gRPC module installation + +Follow the documentation from that URL [https://grpc.io/docs/quickstart/php.html](https://grpc.io/docs/quickstart/php.html) to install the grpc PHP module. Also make sure to install the PHP protobuffer module, if you are looking for speed. + +### Source code and dependencies + +The source code of the application is located in the ```src``` directory. + +Once you have the source code locally you need to install the application dependencies. This can be done with the help of **composer.** Composer is the PHP package manager. If you don’t have it already installed on your system, run + +``` +$ curl -sS https://getcomposer.org/installer | php +$ [sudo] mv composer.phar /usr/local/bin/composer +``` + +The commands that needs to be run in order to fetch all dependencies are + +``` +cd +composer install +``` + +## Configuration + +The location of the JSON service key file is specified in the ```config/application.config.php``` file. In it you will find the following line: + +``` +const CREDENTIALS_FILENAME = __DIR__.'/service_credentials.json'; + +putenv('GOOGLE_APPLICATION_CREDENTIALS='.CREDENTIALS_FILENAME); +``` + +The code above expects the JSON file to be present in the ```config/``` folder under the name ```service_credentials.json.``` If that is not the case you have to modify the source code to match the location and name of your JSON file. + +### config.php +Please note, that this paragraph is only relevant if you want to run the example application in interactive mode and if it's not started with ```firestore.php```as stated in paragraph [Usage](#usage) below. + +In file ```config.php``` are also configuration settings related to the name of the project, database and collection to use. They are defined in the lines: + +``` +return [ + 'firestore' => [ + 'options'=> [], // options that will be used for the creation of the FirestoreClient + 'projectId' => 'rogue-wave-project', + 'collectionId' => 'GrpcTestData1', + 'database' => '(default)', + ] +]; +``` + +Initial ```config.php```can be copied from ```config.php.dist```. + +Please modify the configuration according to your settings. + +## Usage + +There is an interactive and a non-interactive mode available. The entyrpoint for both is running ```firestore.php``` from the root directory. Please find examples below. + +``` +Usage: + firestore --interactive true --databaseid "" --projectid --collectionid // Interactive mode is started + firestore --api BatchGetDocuments -x --databaseid "" --projectid --collectionid + firestore --api BeginTransaction --databaseid "" --projectid + firestore --api Commit --databaseid "" --projectid --collectionid + firestore --api CreateDocument -x --databaseid --projectid --collectionid + firestore --api DeleteDocument -x --databaseid --projectid --collectionid + firestore --api GetDocument -x --databaseid --projectid --collectionid + firestore --api ListCollectionIds --databaseid --projectid + firestore --api ListDocuments --databaseid "" --projectid --collectionid + firestore --api Rollback --databaseid "" --projectid + firestore --api RunQuery --select --field= --value= --databaseid "" --projectid --collectionid + firestore --api UpdateDocument -x --field= --value= --databaseid "" --projectid --collectionid + +Options: + -a, --api=API Available Api Calls are: + BatchGetDocuments + BeginTransaction + Commit + CreateDocument + DeleteDocument + GetDocument + ListCollectionIds + ListDocuments + Rollback + RunQuery + UpdateDocument + -p, --projectid=PROJECTID Project Id + -d, --databaseid=DATABASEID Database Id, e.g. "(default)" + -c, --collectionid=COLLECTIONID Collection Id + -f, --field=FIELD Field + -s, --select=SELECT Select Field, only for RunQuery API + -i, --interactive[=INTERACTIVE] Run interactive mode + -h, --help Display this help message + -val, --value=VALUE Value + -x|doc, --documentid=DOCUMENTID DocumentId +``` + +The interactive mode can also be started by calling ```php bin/interactive.php```. + +## Further development + +[https://googlecloudplatform.github.io/google-cloud-php/#/docs/cloud-firestore/master/firestore/v1beta1/firestoreclient](https://googlecloudplatform.github.io/google-cloud-php/#/docs/cloud-firestore/master/firestore/v1beta1/firestoreclient). + +Good luck and enjoy working with FireStore! diff --git a/firestore/examples/end2end/src/.extra/google-grp-service-composer.tgz b/firestore/examples/end2end/src/.extra/google-grp-service-composer.tgz new file mode 100644 index 0000000000..3ed2442034 Binary files /dev/null and b/firestore/examples/end2end/src/.extra/google-grp-service-composer.tgz differ diff --git a/firestore/examples/end2end/src/.gitignore b/firestore/examples/end2end/src/.gitignore index e69de29bb2..a5e5e144a9 100644 --- a/firestore/examples/end2end/src/.gitignore +++ b/firestore/examples/end2end/src/.gitignore @@ -0,0 +1,2 @@ +vendor/* +config/config.php \ No newline at end of file diff --git a/firestore/examples/end2end/src/Dockerfile b/firestore/examples/end2end/src/Dockerfile new file mode 100644 index 0000000000..69aaa7b66d --- /dev/null +++ b/firestore/examples/end2end/src/Dockerfile @@ -0,0 +1,30 @@ +FROM php:7.2.0-cli-alpine3.7 as builder + +RUN apk --no-cache add $PHPIZE_DEPS zip unzip git zlib-dev + +RUN pecl install grpc +RUN docker-php-ext-enable grpc + +WORKDIR / +RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \ + php -r "if (hash_file('SHA384', 'composer-setup.php') === '544e09ee996cdf60ece3804abc52599c22b1f40f4323403c44d44fdfdd586475ca9813a858088ffbc1f233e9b180f061') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" && \ + php composer-setup.php && \ + php -r "unlink('composer-setup.php');" + +COPY . /app + +WORKDIR /app + +RUN php /composer.phar install + +FROM php:7.2.0-cli-alpine3.7 + +COPY --from=builder /app /app +COPY --from=builder /app/config/config.php.dist /app/config/config.php +COPY --from=builder /usr/local/lib/php/extensions/no-debug-non-zts-20170718/grpc.so /usr/local/lib/php/extensions/no-debug-non-zts-20170718/grpc.so + +RUN docker-php-ext-enable grpc + +ENV GOOGLE_APPLICATION_CREDENTIALS=/cred.json + +ENTRYPOINT ["/app/firestore.php"] \ No newline at end of file diff --git a/firestore/examples/end2end/src/bin/interactive.php b/firestore/examples/end2end/src/bin/interactive.php new file mode 100644 index 0000000000..0fb954a7df --- /dev/null +++ b/firestore/examples/end2end/src/bin/interactive.php @@ -0,0 +1,37 @@ +usage(); + $option = readline("> Enter an Option ('quit' to exit): "); + if ($option == 'quit' || $option == 'q') { + break; + } + try{ + $commands->run($option); + } + catch (\Google\ApiCore\ApiException $ex){ + echo "\n*******************************\n"; + echo " Got exception: ".$ex->getMessage()."\n"; + echo "\n*******************************\n"; + readline("Press a key to continue"); + } + echo "\n====================================\n"; +} \ No newline at end of file diff --git a/firestore/examples/end2end/src/composer.json b/firestore/examples/end2end/src/composer.json new file mode 100644 index 0000000000..403e045da3 --- /dev/null +++ b/firestore/examples/end2end/src/composer.json @@ -0,0 +1,16 @@ +{ + "name" : "grpc/grpc-demo", + "description" : "gRPC example for PHP", + "require" : { + "grpc/grpc" : "^v1.3.0", + "symfony/console" : "~4.0", + "google/auth" : "^1.2", + "google/protobuf" : "v3.5.1.1", + "google/grpc-service: "1.0.0" + }, + "autoload" : { + "psr-4" : { + "FireStore\\" : "src/" + } + } +} diff --git a/firestore/examples/end2end/src/composer.lock b/firestore/examples/end2end/src/composer.lock new file mode 100644 index 0000000000..fa89a980e2 --- /dev/null +++ b/firestore/examples/end2end/src/composer.lock @@ -0,0 +1,597 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "cab2395564c829101bb16ec28551fc9e", + "packages": [ + { + "name": "firebase/php-jwt", + "version": "v5.0.0", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "9984a4d3a32ae7673d6971ea00bae9d0a1abba0e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/9984a4d3a32ae7673d6971ea00bae9d0a1abba0e", + "reference": "9984a4d3a32ae7673d6971ea00bae9d0a1abba0e", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": " 4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "time": "2017-06-27T22:17:23+00:00" + }, + { + "name": "google/auth", + "version": "v1.2.1", + "source": { + "type": "git", + "url": "https://github.com/google/google-auth-library-php.git", + "reference": "da0062d279c9459350808a4fb63dbc08b90d6b90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/google/google-auth-library-php/zipball/da0062d279c9459350808a4fb63dbc08b90d6b90", + "reference": "da0062d279c9459350808a4fb63dbc08b90d6b90", + "shasum": "" + }, + "require": { + "firebase/php-jwt": "~2.0|~3.0|~4.0|~5.0", + "guzzlehttp/guzzle": "~5.3.1|~6.0", + "guzzlehttp/psr7": "~1.2", + "php": ">=5.4", + "psr/cache": "^1.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^1.11", + "guzzlehttp/promises": "0.1.1|^1.3", + "phpunit/phpunit": "^4.8.36|^5.7", + "sebastian/comparator": ">=1.2.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Google\\Auth\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Google Auth Library for PHP", + "homepage": "http://github.com/google/google-auth-library-php", + "keywords": [ + "Authentication", + "google", + "oauth2" + ], + "time": "2018-01-24T18:28:42+00:00" + }, + { + "name": "google/protobuf", + "version": "v3.5.1.1", + "source": { + "type": "git", + "url": "https://github.com/google/protobuf.git", + "reference": "860bd12fec5c69e6529565165532b3d5108a7d97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/google/protobuf/zipball/860bd12fec5c69e6529565165532b3d5108a7d97", + "reference": "860bd12fec5c69e6529565165532b3d5108a7d97", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": ">=4.8.0" + }, + "suggest": { + "ext-bcmath": "Need to support JSON deserialization" + }, + "type": "library", + "autoload": { + "psr-4": { + "Google\\Protobuf\\": "php/src/Google/Protobuf", + "GPBMetadata\\Google\\Protobuf\\": "php/src/GPBMetadata/Google/Protobuf" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "proto library for PHP", + "homepage": "https://developers.google.com/protocol-buffers/", + "keywords": [ + "proto" + ], + "time": "2018-01-05T21:42:10+00:00" + }, + { + "name": "grpc/grpc", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/grpc/grpc-php.git", + "reference": "8d190d91ddb9d980f685d9caf79bca62d7edc1e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/grpc/grpc-php/zipball/8d190d91ddb9d980f685d9caf79bca62d7edc1e6", + "reference": "8d190d91ddb9d980f685d9caf79bca62d7edc1e6", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "google/auth": "v0.9" + }, + "suggest": { + "ext-protobuf": "For better performance, install the protobuf C extension.", + "google/protobuf": "To get started using grpc quickly, install the native protobuf library." + }, + "type": "library", + "autoload": { + "psr-4": { + "Grpc\\": "src/lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "gRPC library for PHP", + "homepage": "https://grpc.io", + "keywords": [ + "rpc" + ], + "time": "2017-09-11T20:50:39+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f4db5a78a5ea468d4831de7f0bf9d9415e348699", + "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.0 || ^5.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.2-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2017-06-22T18:50:49+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20T10:07:11+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2017-03-20T17:10:46+00:00" + }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "time": "2016-08-06T20:24:11+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "symfony/console", + "version": "v4.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "36d5b41e7d4e1ccf0370f6babe966c08ef0a1488" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/36d5b41e7d4e1ccf0370f6babe966c08ef0a1488", + "reference": "36d5b41e7d4e1ccf0370f6babe966c08ef0a1488", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2018-01-29T09:06:29+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b", + "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2018-01-30T19:27:44+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/firestore/examples/end2end/src/config/.gitignore b/firestore/examples/end2end/src/config/.gitignore new file mode 100644 index 0000000000..5003b4259f --- /dev/null +++ b/firestore/examples/end2end/src/config/.gitignore @@ -0,0 +1,2 @@ +*.json +/config.php diff --git a/firestore/examples/end2end/src/config/application.config.php b/firestore/examples/end2end/src/config/application.config.php new file mode 100644 index 0000000000..1c62fea6ff --- /dev/null +++ b/firestore/examples/end2end/src/config/application.config.php @@ -0,0 +1,41 @@ + [ + 'options'=> [], // options that will be used for the creation of the FirestoreClient + 'projectId' => 'myProjectId', + 'collectionId' => 'GrpcTestData', + 'database' => '(default)', + ] +]; \ No newline at end of file diff --git a/firestore/examples/end2end/src/firestore.php b/firestore/examples/end2end/src/firestore.php new file mode 100755 index 0000000000..19c6dedea5 --- /dev/null +++ b/firestore/examples/end2end/src/firestore.php @@ -0,0 +1,263 @@ +#!/usr/bin/env php + +doRun($input, $output); + } + } +}) + ->register('firestore') + + ->addOption('api', 'a', InputOption::VALUE_REQUIRED, "Available Api Calls are:\n" . join("\n", $apis)) + ->addOption('projectid', 'p', InputOption::VALUE_REQUIRED, "Project Id") + ->addOption('databaseid', 'd', InputOption::VALUE_REQUIRED, 'Database Id, e.g. "(default)"') + ->addOption('collectionid', 'c', InputOption::VALUE_REQUIRED, "Collection Id") + ->addOption('field', 'f', InputOption::VALUE_REQUIRED, "Field") + ->addOption('value', 'val', InputOption::VALUE_REQUIRED, "Value") + ->addOption('select', 's', InputOption::VALUE_REQUIRED, "Select Field, only for RunQuery API") + ->addOption('documentid', ['x', 'doc'], InputOption::VALUE_REQUIRED, "DocumentId") + ->addOption('interactive', 'i', InputOption::VALUE_OPTIONAL, "Run interactive mode") + + ->addUsage('--interactive true --databaseid "" --projectid --collectionid // Interactive mode is started') + ->addUsage('--api BatchGetDocuments -x --databaseid "" --projectid --collectionid ') + ->addUsage('--api BeginTransaction --databaseid "" --projectid ') + ->addUsage('--api Commit --databaseid "" --projectid --collectionid ') + ->addUsage('--api CreateDocument -x --databaseid --projectid --collectionid ') + ->addUsage('--api DeleteDocument -x --databaseid --projectid --collectionid ') + ->addUsage('--api GetDocument -x --databaseid --projectid --collectionid ') + ->addUsage('--api ListCollectionIds --databaseid --projectid ') + ->addUsage('--api ListDocuments --databaseid "" --projectid --collectionid ') + ->addUsage('--api Rollback --databaseid "" --projectid ') + ->addUsage('--api RunQuery --select --field= --value= --databaseid "" --projectid --collectionid ') + ->addUsage('--api UpdateDocument -x --field= --value= --databaseid "" --projectid --collectionid ') + + ->setCode(function (InputInterface $input, OutputInterface $output) use ($apis) { + $api = $input->getOption('api'); + + $projectid = $input->getOption('projectid'); + if (!$projectid) { + throw new \InvalidArgumentException('projectid is mandatory'); + } + $databaseId = $input->getOption('databaseid'); + if (!$databaseId) { + throw new \InvalidArgumentException('databaseid is mandatory'); + } + $collectionid = $input->getOption('collectionid'); + if (!$collectionid && !in_array($api, ['BeginTransaction', 'ListCollectionIds', 'Rollback'])) { + throw new \InvalidArgumentException('collectionid is mandatory'); + } + + if ($input->getOption('interactive') == 'true') { + define('PROJECT_ID', $input->getOption('projectid')); + define('DATABASE_ID', $input->getOption('databaseid')); + define('COLLECTION_ID', $input->getOption('collectionid')); + require 'bin/interactive.php'; + return; + } + + if (!in_array($api, $apis)) { + throw new \BadFunctionCallException("Api $api not available"); + } + + $host = "firestore.googleapis.com"; + $credentials = \Grpc\ChannelCredentials::createSsl(); + + // WARNING: the environment variable "GOOGLE_APPLICATION_CREDENTIALS" needs to be set + $auth = ApplicationDefaultCredentials::getCredentials(); + $opts = [ + 'credentials' => $credentials, + 'update_metadata' => $auth->getUpdateMetadataFunc(), + ]; + + $firestoreClient = new FirestoreClient($host, $opts); + + if ($collectionid) { + $documentNameBuilder = new DocumentNameBuilder( + $projectid, + $databaseId, + $collectionid + ); + } + + $databaseRootNameBuilder = new DatabaseRootNameBuilder( + $firestoreClient, + $projectid, + $databaseId + ); + + $parentResourceNameBuilder = new ParentResourceNameBuilder( + $projectid, + $databaseId + ); + + switch ($api) { + case 'BatchGetDocuments': + (new BatchGetDocuments)( + $firestoreClient, + $databaseRootNameBuilder, + $documentNameBuilder, + array_map('trim', explode(',', $input->getOption('documentid'))) + ); + break; + + case 'BeginTransaction': + (new BeginTransaction)( + $firestoreClient, + $databaseRootNameBuilder + ); + break; + + case 'Commit': + $transaction = (new BeginTransaction)( + $firestoreClient, + $databaseRootNameBuilder, + true + ); + + (new Commit)( + $firestoreClient, + $databaseRootNameBuilder, + $documentNameBuilder, + $transaction + ); + break; + + case 'CreateDocument': + (new CreateDocument)( + $firestoreClient, + $parentResourceNameBuilder, + $collectionid, + $input->getOption('documentid') + ); + break; + + case 'DeleteDocument': + (new DeleteDocument)( + $firestoreClient, + $documentNameBuilder, + $input->getOption('documentid') + ); + break; + + case 'GetDocument': + (new GetDocument)( + $firestoreClient, + $documentNameBuilder, + $input->getOption('documentid') + ); + break; + + case 'ListCollectionIds': + (new ListCollectionIds)( + $firestoreClient, + $parentResourceNameBuilder + ); + break; + + case 'ListDocuments': + (new ListDocuments)( + $firestoreClient, + $parentResourceNameBuilder, + $collectionid + ); + break; + + case 'Rollback': + $transaction = (new BeginTransaction)( + $firestoreClient, + $databaseRootNameBuilder, + true + ); + + (new Rollback)( + $firestoreClient, + $databaseRootNameBuilder, + $transaction + ); + break; + + case 'RunQuery': + $select = [$input->getOption('select')]; + $where = [$input->getOption('field') => $input->getOption('value')]; + + $queryBuilder = new StructuredQueryBuilder($collectionid, $select, $where); + + (new RunQuery)( + $firestoreClient, + $parentResourceNameBuilder, + $queryBuilder->build() + ); + break; + + case 'UpdateDocument': + (new UpdateDocument)( + $firestoreClient, + $documentNameBuilder, + $input->getOption('documentid'), + $input->getOption('field'), + $input->getOption('value') + ); + break; + + default: + throw new \Exception("Api call $api not available"); + } + }) + ->getApplication() + ->setDefaultCommand('firestore', true) + ->run(); diff --git a/firestore/examples/end2end/src/src/ApiMethods/Admin/CreateIndex.php b/firestore/examples/end2end/src/src/ApiMethods/Admin/CreateIndex.php new file mode 100644 index 0000000000..2357d660a4 --- /dev/null +++ b/firestore/examples/end2end/src/src/ApiMethods/Admin/CreateIndex.php @@ -0,0 +1,46 @@ +setFieldPath($field1); + $ageField->setMode(IndexField_Mode::ASCENDING); + + $nameField = new IndexField(); + $nameField->setFieldPath($field2); + $nameField->setMode(IndexField_Mode::ASCENDING); + + $index = new Index(); + $index->setName($indexName); + $index->setCollectionId($collectionId); + $index->setFields([$ageField, $nameField]); + + $argument = new CreateIndexRequest(); + $argument->setParent($databaseRootNameBuilder->build()); + $argument->setIndex($index); + + list($index, $error) = $client->CreateIndex($argument)->wait(); + if($error->code) { + echo "Failed creating index. Details: ".$error->details."\n"; + } + else { + echo "Index was succesfully created.\n"; + } + } +} diff --git a/firestore/examples/end2end/src/src/ApiMethods/Admin/DeleteIndex.php b/firestore/examples/end2end/src/src/ApiMethods/Admin/DeleteIndex.php new file mode 100644 index 0000000000..35ddcbb102 --- /dev/null +++ b/firestore/examples/end2end/src/src/ApiMethods/Admin/DeleteIndex.php @@ -0,0 +1,28 @@ +setName($databaseRootNameBuilder->build().'/indexes/'.$indexName); + + list($reply, $error) = $client->DeleteIndex($argument)->wait(); + if($error->code) { + echo "Failed deleting index: ".$error->details."\n"; + } + else { + echo "Successfully deleted index\n"; + } + } +} diff --git a/firestore/examples/end2end/src/src/ApiMethods/Admin/GetIndex.php b/firestore/examples/end2end/src/src/ApiMethods/Admin/GetIndex.php new file mode 100644 index 0000000000..5be50e567a --- /dev/null +++ b/firestore/examples/end2end/src/src/ApiMethods/Admin/GetIndex.php @@ -0,0 +1,29 @@ +setName($databaseRootNameBuilder->build().'/indexes/'.$indexName); + + list($index, $error) = $client->GetIndex($argument)->wait(); + if($error->code) { + echo "Failed getting index. Details: ".$error->details."\n"; + } + else { + echo "Index was successfully fetched."; + echo "Index Full Name:".$index->getName()."\n"; + } + } +} diff --git a/firestore/examples/end2end/src/src/ApiMethods/Admin/ListIndexes.php b/firestore/examples/end2end/src/src/ApiMethods/Admin/ListIndexes.php new file mode 100644 index 0000000000..a6e3e6fb30 --- /dev/null +++ b/firestore/examples/end2end/src/src/ApiMethods/Admin/ListIndexes.php @@ -0,0 +1,29 @@ +setParent($databaseRootNameBuilder->build()); + + list($reply, $error) = $client->ListIndexes($argument)->wait(); + if($error->code) { + echo "Failed listing indexes. Details: ".$error->details."\n"; + return; + } + + foreach($reply->getIndexes() as $index) { + echo "Name:".$index->getName()."\n"; + echo "Collection:".$index->getCollectionId()."\n"; + } + } +} diff --git a/firestore/examples/end2end/src/src/ApiMethods/BatchGetDocuments.php b/firestore/examples/end2end/src/src/ApiMethods/BatchGetDocuments.php new file mode 100644 index 0000000000..cd02b4623c --- /dev/null +++ b/firestore/examples/end2end/src/src/ApiMethods/BatchGetDocuments.php @@ -0,0 +1,43 @@ +setDocumentId($docId); + $documents[] = $documentNameBuilder->build(); + } + + $argument = new BatchGetDocumentsRequest(); + $argument->setDocuments($documents); + $argument->setDatabase($databaseRootNameBuilder->build()); + + $stream = $client->BatchGetDocuments($argument); + + foreach ($stream->responses() as $element) { + $result = $element->getResult(); + + if ($element->getFound()) { + $docName = $element->getFound()->getName(); + echo "Document was $result: $docName\n"; + } else { + $docName = $element->getMissing(); + echo "Document is $result: $docName\n"; + } + + $transaction = $element->getTransaction(); + } + } +} diff --git a/firestore/examples/end2end/src/src/ApiMethods/BeginTransaction.php b/firestore/examples/end2end/src/src/ApiMethods/BeginTransaction.php new file mode 100644 index 0000000000..170fbdf440 --- /dev/null +++ b/firestore/examples/end2end/src/src/ApiMethods/BeginTransaction.php @@ -0,0 +1,53 @@ +setSeconds($now->getTimestamp()); + // $timestamp->setNanos($now->format('u')); // using precision higher than microseconds was rejected + + if (!$readWrite) { + $readOnlyOption = new TransactionOptions_ReadOnly(); + $readOnlyOption->setReadTime($timestamp); + + $options = new TransactionOptions(); + $options->setReadOnly($readOnlyOption); + + echo "Read "; + } else { + $readWriteOption = new TransactionOptions_ReadWrite(); + + $options = new TransactionOptions(); + $options->setReadWrite($readWriteOption); + + echo "Read/Write "; + } + echo "Transaction started\n"; + + $argument = new BeginTransactionRequest(); + $argument->setDatabase($databaseRootNameBuilder->build()); + $argument->setOptions($options); + + list($transaction, $error) = $client->BeginTransaction($argument)->wait(); + + return $transaction; + } +} diff --git a/firestore/examples/end2end/src/src/ApiMethods/Commit.php b/firestore/examples/end2end/src/src/ApiMethods/Commit.php new file mode 100644 index 0000000000..a602580fff --- /dev/null +++ b/firestore/examples/end2end/src/src/ApiMethods/Commit.php @@ -0,0 +1,82 @@ +setSeconds($now->getTimestamp()); + + $mask = new DocumentMask(); + $mask->setFieldPaths(['name','age']); + + $document = new Document(); + $docId = 'GeneratedByCommit_' . uniqid(); + echo "Generate document with document Id $docId\n"; + + $documentNameBuilder->setDocumentId($docId); + $documentName = $documentNameBuilder->build(); + $document->setName($documentName); + $document->setCreateTime($timestamp); + $document->setUpdateTime($timestamp); + + // Add different fields + $fields = new MapField(GPBType::STRING, GPBType::MESSAGE, Value::class); + + $value = new Value(); + $name = array_rand(array_flip(['john', 'jim', 'james', 'jeremy', 'joe'])); + $value->setStringValue($name); + $fields['name'] = $value; + + $value = new Value(); + $age = rand(20, 80); + $value->setIntegerValue($age); + $fields['age'] = $value; + $document->setFields($fields); + + echo "Fields to write:\n"; + var_dump(['name' => $name, 'age' => $age]); + + $writes = []; + $write = new Write(); + $write->setUpdate($document); + $write->setUpdateMask($mask); + $writes[] = $write; + + $argument = new CommitRequest(); + $argument->setWrites($writes); + $argument->setDatabase($databaseRootNameBuilder->build()); + $argument->setTransaction($transaction->getTransaction()); + + list($reply, $error) = $client->Commit($argument)->wait(); + if(!$error->code) { + echo "Comitted\n"; + } + else { + echo "!Failed to commit: '.$error->details.'!\n"; + } + } +} diff --git a/firestore/examples/end2end/src/src/ApiMethods/CreateDocument.php b/firestore/examples/end2end/src/src/ApiMethods/CreateDocument.php new file mode 100644 index 0000000000..3e689e3b7e --- /dev/null +++ b/firestore/examples/end2end/src/src/ApiMethods/CreateDocument.php @@ -0,0 +1,36 @@ +setParent($parentResourceNameBuilder->build()); + $argument->setCollectionId($collectionId); + $argument->setDocumentId($docId); + $argument->setDocument($document); + + list ($document, $error) = $client->CreateDocument($argument)->wait(); + if(!$error->code) { + echo "Successfully created new document!\n"; + echo "Name: {$document->getName()}\n"; + } + else { + echo "!Failed to create document: '.$error->details.'!\n"; + } + } +} diff --git a/firestore/examples/end2end/src/src/ApiMethods/DeleteDocument.php b/firestore/examples/end2end/src/src/ApiMethods/DeleteDocument.php new file mode 100644 index 0000000000..921754819f --- /dev/null +++ b/firestore/examples/end2end/src/src/ApiMethods/DeleteDocument.php @@ -0,0 +1,33 @@ +setDocumentId($docId); + + $argument = new DeleteDocumentRequest(); + $argument->setName($documentNameBuilder->build()); + + list ($document, $error) = $client->DeleteDocument($argument)->wait(); + if(!$error->code) { + echo "Successfully deleted document!\n"; + } + else { + echo "!Failed to delete document: '.$error->details.'!\n"; + } + } +} diff --git a/firestore/examples/end2end/src/src/ApiMethods/GetDocument.php b/firestore/examples/end2end/src/src/ApiMethods/GetDocument.php new file mode 100644 index 0000000000..dc0929d33d --- /dev/null +++ b/firestore/examples/end2end/src/src/ApiMethods/GetDocument.php @@ -0,0 +1,47 @@ +setDocumentId($docId); + + $documentName = $documentNameBuilder->build(); + + echo str_pad('-', 79, '-') . "\n"; + + $argument = new GetDocumentRequest(); + $argument->setName($documentName); + + list($document, $error) = $client->GetDocument($argument)->wait(); + if($error->code) { + echo "!Failed fetching document: '.$error->details.'!\n"; + return; + } + + echo "Document Name: $documentName"; + echo "\nCreated: " . date(DATE_RFC822, $document->getCreateTime()->getSeconds()); + echo "\nUpdated: " . date(DATE_RFC822, $document->getUpdateTime()->getSeconds()); + + $fields = $document->getFields(); + $i = 0; + echo "\n\nDocument Fields\n"; + foreach ($fields as $name => $value) { + $i++; + echo "Field $i: "; + switch ($value->getValueType()) { + case 'integer_value': + echo "$name => ".$value->getIntegerValue()."\n"; + break; + default: + echo "$name => ".$value->getStringValue()."\n"; + } + } + } +} diff --git a/firestore/examples/end2end/src/src/ApiMethods/ListCollectionIds.php b/firestore/examples/end2end/src/src/ApiMethods/ListCollectionIds.php new file mode 100644 index 0000000000..92622e8765 --- /dev/null +++ b/firestore/examples/end2end/src/src/ApiMethods/ListCollectionIds.php @@ -0,0 +1,26 @@ +setParent($parentResourceNameBuilder->build()); + + list($reply, $error) = $client->ListCollectionIds($argument)->wait(); + if(!$error->code) { + $collections = $reply->getCollectionIds(); + foreach($collections as $collection) { + echo "$collection\n"; + } + } + } +} diff --git a/firestore/examples/end2end/src/src/ApiMethods/ListDocuments.php b/firestore/examples/end2end/src/src/ApiMethods/ListDocuments.php new file mode 100644 index 0000000000..8e75ee1ffc --- /dev/null +++ b/firestore/examples/end2end/src/src/ApiMethods/ListDocuments.php @@ -0,0 +1,40 @@ +setParent($parentResourceNameBuilder->build()); + $argument->setCollectionId($collectionId); + + list($pagedResponse, $error) = $client->ListDocuments($argument)->wait(); + if($error->code) { + echo "!Failed listing document: '.$error->details.'!\n"; + return; + } + + // we have successful request + $documents = $pagedResponse->getDocuments(); + $index = 0; + foreach($documents as $document) { + $index++; + $name = $document->getName(); + echo "=> Document $index: $name\n"; + $fields = $document->getFields(); + foreach ($fields as $name => $value) { + echo "$name => ".$value->getStringValue()."\n"; + } + } + } +} diff --git a/firestore/examples/end2end/src/src/ApiMethods/Rollback.php b/firestore/examples/end2end/src/src/ApiMethods/Rollback.php new file mode 100644 index 0000000000..f0b8b4ac33 --- /dev/null +++ b/firestore/examples/end2end/src/src/ApiMethods/Rollback.php @@ -0,0 +1,30 @@ +setTransaction($transaction->getTransaction()); + $argument->setDatabase( $databaseRootNameBuilder->build()); + list($transaction, $error) = $client->Rollback($argument)->wait(); + if(!$error->code) { + echo "Rollback done\n"; + } + else { + echo "!Failed to rollback transaction: '.$error->details.'!\n"; + } + } +} diff --git a/firestore/examples/end2end/src/src/ApiMethods/RunQuery.php b/firestore/examples/end2end/src/src/ApiMethods/RunQuery.php new file mode 100644 index 0000000000..f79cdbc147 --- /dev/null +++ b/firestore/examples/end2end/src/src/ApiMethods/RunQuery.php @@ -0,0 +1,46 @@ +setParent($parentResourceNameBuilder->build()); + $argument->setStructuredQuery($query); + + $stream = $client->RunQuery($argument); + + $index = 0; + foreach ($stream->responses() as $response) { + $document = $response->getDocument(); + if (null == $document) { + continue; + } + $index++; + + $name = $document->getName(); + echo "=> Document $index: $name\n"; + + $fields = $document->getFields(); + foreach ($fields as $name => $value) { + $type = $value->getValueType(); + + $method = 'get'. ucfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $type)))); + $data = $value->$method(); + + echo "$name => $data\n"; + } + } + } +} diff --git a/firestore/examples/end2end/src/src/ApiMethods/UpdateDocument.php b/firestore/examples/end2end/src/src/ApiMethods/UpdateDocument.php new file mode 100644 index 0000000000..15f0a18ba3 --- /dev/null +++ b/firestore/examples/end2end/src/src/ApiMethods/UpdateDocument.php @@ -0,0 +1,63 @@ +setDocumentId($docId); + + $documentName = $documentNameBuilder->build(); + + echo str_pad('-', 79, '-') . "\n"; + + $argument = new GetDocumentRequest(); + $argument->setName($documentName); + + list($document, $error) = $client->GetDocument($argument)->wait(); + if($error->code) { + echo "!Failed fetching document: '.$error->details.'!\n"; + return; + } + + $updateMask = new DocumentMask(); + $updateMask->setFieldPaths([$fieldName]); + + // this is how we can add different fields to a document + $field = new MapField(GPBType::STRING, GPBType::MESSAGE, Value::class); + $value = new Value(); + $value->setStringValue($newValue); + $field[$fieldName] = $value; + + $document->setFields($field); + + $argument = new UpdateDocumentRequest(); + $argument->setMask($updateMask); + $argument->setDocument($document); + + list ($document, $error) = $client->UpdateDocument($argument)->wait(); + if(!$error->code) { + echo "Update document was successful."; + } + else { + echo "Failed updating document. Error was: ".$error->details; + } + + return $document; + } +} diff --git a/firestore/examples/end2end/src/src/Commands.php b/firestore/examples/end2end/src/src/Commands.php new file mode 100644 index 0000000000..7982d7f755 --- /dev/null +++ b/firestore/examples/end2end/src/src/Commands.php @@ -0,0 +1,535 @@ +config = include __DIR__.'/../config/application.config.php'; + + /* + * This is how you can originally create a connection to the remote firebase DB. + * The options parameters is usually empty. + * + * Notice: There is one very important details here which is not mentioned in the documentation + * In order to create an instance of the FirestoreClient the classes need to find the + * service account credentials JSON file. The location of that file has to be either + * under a specific location in the user's home directory or it needs to be + * specified in the environment with the key GOOGLE_APPLICATION_CREDENTIALS. + * See the config/application.config.php file for details + * + */ + $host = "firestore.googleapis.com"; + $credentials = \Grpc\ChannelCredentials::createSsl(); + + // WARNING: the environment variable "GOOGLE_APPLICATION_CREDENTIALS" needs to be set + $auth = ApplicationDefaultCredentials::getCredentials(); + $opts = [ + 'credentials' => $credentials, + 'update_metadata' => $auth->getUpdateMetadataFunc(), + ]; + + $this->firestoreClient = new FirestoreClient($host, $opts); + + $this->firestoreAdminClient = new FirestoreAdminClient($host, $opts); + + $this->documentNameBuilder = new DocumentNameBuilder( + $this->config['firestore']['projectId'], + $this->config['firestore']['database'], + $this->config['firestore']['collectionId'] + ); + + $this->databaseRootNameBuilder = new DatabaseRootNameBuilder( + $this->config['firestore']['projectId'], + $this->config['firestore']['database'] + ); + } + + // [Normal Functions] + /** + * + * @grpc + */ + private function batchGetDocuments($docIds = []) + { + if (count($docIds) == 0) { + echo "\n :: Batch Retreive Documents ::\n"; + echo "------------------------------------------\n\n"; + } + + if (end($docIds) == '') { + array_pop($docIds); + } + + $docId = readline("> Enter Document Id (blank when finished): "); + + if (trim($docId) || count($docIds) == 0) { + $docIds[] = $docId; + return $this->batchGetDocuments($docIds); + } + + (new ApiMethods\BatchGetDocuments)( + $this->firestoreClient, + $this->databaseRootNameBuilder, + $this->documentNameBuilder, + $docIds + ); + } + + /** + * + * @grpc + */ + private function beginTransaction($readWrite = false) + { + return (new ApiMethods\BeginTransaction)( + $this->firestoreClient, + $this->databaseRootNameBuilder, + $readWrite + ); + } + + /** + * + * @grpc + */ + private function commit() + { + $transaction = $this->beginTransaction(true); + + (new ApiMethods\Commit)( + $this->firestoreClient, + $this->databaseRootNameBuilder, + $this->documentNameBuilder, + $transaction + ); + } + + /** + * + * @grpc + */ + private function createDocument($docId = null) + { + if (!$docId) { + echo "\n :: Creating a new Document ::\n"; + echo "------------------------------------------\n\n"; + $docId = readline("> Enter Document Id: "); + return $this->createDocument($docId); + } + + $parentResourceNameBuilder = new ParentResourceNameBuilder( + $this->config['firestore']['projectId'], + $this->config['firestore']['database'] + ); + + $collectionId = $this->config['firestore']['collectionId']; + + (new ApiMethods\CreateDocument)( + $this->firestoreClient, + $parentResourceNameBuilder, + $collectionId, + $docId + ); + } + + /** + * + * @grpc + */ + private function deleteDocument($docId = null) + { + if (!$docId) { + echo "\n :: Deleting a Document ::\n"; + echo "------------------------------------------\n\n"; + $docId = readline("> Enter Document Id: "); + return $this->deleteDocument($docId); + } + + (new ApiMethods\DeleteDocument)( + $this->firestoreClient, + $this->documentNameBuilder, + $docId + ); + } + + + /** + * + * @grpc + */ + private function getDocument($docId = null) + { + if (!$docId) { + echo "\n :: Fetch a Specific Document... ::\n"; + echo "------------------------------------------\n\n"; + $docId = readline("> Enter Document Id: "); + return $this->getDocument($docId); + } + + (new ApiMethods\GetDocument)($this->firestoreClient, $this->documentNameBuilder, $docId); + } + + /** + * + * @grpc + */ + private function listCollectionIds() + { + $parentResourceNameBuilder = new ParentResourceNameBuilder( + $this->config['firestore']['projectId'], + $this->config['firestore']['database'] + ); + + (new ApiMethods\ListCollectionIds)( + $this->firestoreClient, + $parentResourceNameBuilder + ); + } + + + /** + * + * @grpc + */ + private function listDocuments() + { + $collectionId = $this->config['firestore']['collectionId']; + + $parentResourceNameBuilder = new ParentResourceNameBuilder( + $this->config['firestore']['projectId'], + $this->config['firestore']['database'] + ); + + (new ApiMethods\ListDocuments)( + $this->firestoreClient, + $parentResourceNameBuilder, + $collectionId + ); + } + + /** + * + * @grpc + */ + private function rollback() + { + $transaction = $this->beginTransaction(); + + (new ApiMethods\Rollback)( + $this->firestoreClient, + $this->databaseRootNameBuilder, + $transaction + ); + } + + /** + * + * @grpc + */ + private function runQuery() + { + echo "Run Query in the format of 'SELECT name, age FROM users WHERE age=20 AND name=\"john\"'\n\n"; + $prompt = <<<'EOF' +select;Select Fields (multiple fields seperated by comma): +where;Where clause: +EOF; + + $userInput = []; + foreach ($this->getInputParser($prompt)() as $key => $value) { + $userInput[$key] = $value; + } + + // build arrays for SELECT and WHERE from user input + $select = array_map('trim', explode(',', $userInput['select'])); + + parse_str(str_ireplace([' and ', ' '], ['&', ''], $userInput['where']), $where); + $where = array_map( + function ($value) { + $value = trim($value, ' \'"'); + if (is_numeric($value)) { + return $value + 0; + } + return $value; + }, + $where + ); + + $collectionId = $this->config['firestore']['collectionId']; + $queryBuilder = new StructuredQueryBuilder($collectionId, $select, $where); + + $parentResourceNameBuilder = new ParentResourceNameBuilder( + $this->config['firestore']['projectId'], + $this->config['firestore']['database'] + ); + + (new ApiMethods\RunQuery)( + $this->firestoreClient, + $parentResourceNameBuilder, + $queryBuilder->build() + ); + } + + /** + * + * @grpc + */ + private function updateDocument() + { + echo "\n :: Update a Document... ::\n"; + echo "------------------------------------------\n\n"; + + $prompt = <<<'EOF' +docId;Enter Document Id: +fieldToUpdate;Enter Field Name (String field, new or existing): +newValue;Enter Field Value: +EOF; + + $userInput = []; + foreach ($this->getInputParser($prompt)() as $key => $value) { + $userInput[$key] = $value; + } + + $document = (new ApiMethods\UpdateDocument)( + $this->firestoreClient, + $this->documentNameBuilder, + $userInput['docId'], + $userInput['fieldToUpdate'], + $userInput['newValue'] + ); + + $this->getDocument($userInput['docId']); + } + + // [ Admin Functions ] + /** + * @grpc + * @admin + */ + private function createIndex() + { + echo "\n :: Create Index... ::\n"; + echo "------------------------------------------\n\n"; + + $prompt = <<<'EOF' +collectionId;Collection Name: +name;Enter Index Name: +field1;Enter Field 1 Name (should be existing field): +field2;Enter Field 2 Name (should be existing field): +EOF; + + $userInput = []; + foreach ($this->getInputParser($prompt)() as $key => $value) { + $userInput[$key] = $value; + } + + (new ApiMethods\Admin\CreateIndex)( + $this->firestoreAdminClient, + $this->databaseRootNameBuilder, + $userInput['collectionId'], + $userInput['name'], + $userInput['field1'], + $userInput['field2'] + ); + } + + /** + * @grpc + * @admin + */ + private function deleteIndex() + { + echo "\n :: Delete Index... ::\n"; + echo "------------------------------------------\n\n"; + + $prompt = <<<'EOF' +name;Enter Index Name: +EOF; + + $userInput = []; + foreach ($this->getInputParser($prompt)() as $key => $value) { + $userInput[$key] = $value; + } + + (new ApiMethods\Admin\DeleteIndex)( + $this->firestoreAdminClient, + $this->databaseRootNameBuilder, + $userInput['name'] + ); + } + + /** + * @grpc + * @admin + */ + private function getIndex() + { + echo "\n :: Get Index... ::\n"; + echo "------------------------------------------\n\n"; + + $prompt = <<<'EOF' +name;Enter Index Name: +EOF; + + $userInput = []; + foreach ($this->getInputParser($prompt)() as $key => $value) { + $userInput[$key] = $value; + } + + (new ApiMethods\Admin\GetIndex)( + $this->firestoreAdminClient, + $this->databaseRootNameBuilder, + $userInput['name'] + ); + } + + /** + * @grpc + * @admin + */ + private function listIndexes() + { + echo "\n :: Listing Index... ::\n"; + echo "------------------------------------------\n\n"; + + (new ApiMethods\Admin\ListIndexes)( + $this->firestoreAdminClient, + $this->databaseRootNameBuilder + ); + } + + /** + * @grpc + * @admin + */ + private function progress() + { + // Unable to find PHP documentation for those methods + throw new \Exception("PHP classes are not existing for this functionality"); + } + + // Utility methods + + public function usage() + { + print(" Google Firestore RPC Menu \n\n"); + printf("%s\n", str_pad('-', 79, "-")); + + $options = $this->getOptions(); + + foreach ($options as $idx => $option) { + printf( + "%'. 2d | %s %s\n", + $idx + 1, + str_pad(strtolower($option), 25, "."), + ucfirst($option) + ); + } + + $offset = count($options); + + print("\n Firestore Admin RPC's \n\n"); + printf("%s\n", str_pad('-', 79, "-")); + $options = $this->getOptions(true); + + foreach ($options as $idx => $option) { + printf( + "%'. 2d | %s %s\n", + $idx + $offset + 1, + str_pad(strtolower($option), 25, "."), + ucfirst($option) + ); + } + + print("\n"); + } + + private function getInputParser($prompt) + { + return function () use ($prompt) { + foreach (explode("\n", $prompt) as $line) { + list($key, $text) = explode(';', $line); + $value = readline("> $text"); + echo "\n"; + yield $key => $value; + } + }; + } + + public function run($idx) + { + $allOptions = array_merge($this->getOptions(), $this->getOptions(true)); + if (!isset($allOptions[$idx - 1])) { + return $this->usage(); + } + + return call_user_func(array($this, $allOptions[$idx - 1])); + } + + private function getOptions($admin = false) + { + $options = []; + + $reflection = new \ReflectionClass($this); + $methods = $reflection->getMethods(); + foreach ($methods as $method) { + $comment = $method->getDocComment(); + if (!preg_match('/@grpc\s*/i', $comment)) { + continue; + } + + if ($admin != preg_match('/@admin\s*/i', $comment)) { + continue; + } + + $options[] = $method->getName(); + } + + + return $options; + } + + public function __destruct() + { + $this->firestoreClient->close(); + } +} diff --git a/firestore/examples/end2end/src/src/DatabaseRootNameBuilder.php b/firestore/examples/end2end/src/src/DatabaseRootNameBuilder.php new file mode 100644 index 0000000000..bf81e38b81 --- /dev/null +++ b/firestore/examples/end2end/src/src/DatabaseRootNameBuilder.php @@ -0,0 +1,29 @@ +projectId = $projectId; + $this->database = $database; + } + + public function build() + { + return 'projects/'.$this->projectId.'/databases/'.$this->database; + } +} diff --git a/firestore/examples/end2end/src/src/DocumentNameBuilder.php b/firestore/examples/end2end/src/src/DocumentNameBuilder.php new file mode 100644 index 0000000000..6dff7d1bb6 --- /dev/null +++ b/firestore/examples/end2end/src/src/DocumentNameBuilder.php @@ -0,0 +1,55 @@ +projectId = $projectId; + $this->database = $database; + $this->collectionId = $collectionId; + } + + public function build() + { + return sprintf( + self::DOCUMENT_NAME_FORMAT, + $this->projectId, + $this->database, + $this->collectionId, + $this->docId + ); + } + + public function setDocumentId($docId) + { + $this->docId = $docId; + } +} diff --git a/firestore/examples/end2end/src/src/ParentResourceNameBuilder.php b/firestore/examples/end2end/src/src/ParentResourceNameBuilder.php new file mode 100644 index 0000000000..383d97a0bf --- /dev/null +++ b/firestore/examples/end2end/src/src/ParentResourceNameBuilder.php @@ -0,0 +1,35 @@ +projectId = $projectId; + $this->database = $database; + } + + public function build() + { + return sprintf( + self::PARENT_RESOURCE_NAME_FORMAT, + $this->projectId, + $this->database + ); + } +} diff --git a/firestore/examples/end2end/src/src/StructuredQueryBuilder.php b/firestore/examples/end2end/src/src/StructuredQueryBuilder.php new file mode 100644 index 0000000000..e5cc8b9f4d --- /dev/null +++ b/firestore/examples/end2end/src/src/StructuredQueryBuilder.php @@ -0,0 +1,126 @@ + 'DaisyRidley', + * 'age' => 25, + * ]; + * + * @var array + */ + private $where; + + /** + * + * @var string + */ + private $collectionId; + + /** + * + * @var string + */ + private $docId; + + public function __construct($collectionId, $select = [], $where = []) + { + $this->select = $select; + $this->where = $where; + $this->collectionId = $collectionId; + } + + /** + * + * @return \Google\Cloud\Firestore\V1beta1\StructuredQuery + */ + public function build() + { + $query = new StructuredQuery(); + // Example: SELECT name, age FROM users WHERE age=20 AND name="john" + + // SELECT name, age + $fields = []; + $projection = new StructuredQuery_Projection(); + foreach ($this->select as $fieldName) { + $field = new StructuredQuery_FieldReference(); + $field->setFieldPath($fieldName); + $fields[] = $field; + } + + + $projection->setFields($fields); + $query->setSelect($projection); + + + // FROM + $collection = new StructuredQuery_CollectionSelector(); + $collection->setCollectionId($this->collectionId); + $query->setFrom([$collection]); + + // WHERE + $fieldTypes = []; + foreach ($this->where as $fieldName => $value) { + $fieldTypes[$fieldName] = (is_numeric($value)) ? 'integer_value' : 'string_value'; + } + + $filters = []; + foreach ($this->where as $name => $value) { + $filter = new StructuredQuery_FieldFilter(); + $nameField = new StructuredQuery_FieldReference(); + $nameField->setFieldPath($name); + + $filter->setField($nameField); + + $filter->setOp(StructuredQuery_FieldFilter_Operator::EQUAL); + + $valueField = new Value(); + $type = $fieldTypes[$name]; + $method = 'set'. ucfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $type)))); + if (empty($method)) { + $method = "setStringValue"; + } + $valueField->$method($value); + $filter->setValue($valueField); + + $f = new StructuredQuery_Filter(); + $f->setFieldFilter($filter); + + $filters[] = $f; + } + + $compositeFilter = new StructuredQuery_CompositeFilter(); + $compositeFilter->setOp(StructuredQuery_CompositeFilter_Operator::PBAND); + $compositeFilter->setFilters($filters); + $whereFilter = new StructuredQuery_Filter(); + $whereFilter->setCompositeFilter($compositeFilter); + + $query->setWhere($whereFilter); + + return $query; + } +}