├── src ├── Query │ └── FirestoreQuery.php ├── Attributes │ └── FirestoreDeleteAttribute.php ├── Exceptions │ ├── Client │ │ ├── FieldNotFound.php │ │ ├── NotFound.php │ │ ├── FieldTypeError.php │ │ ├── InvalidPathProvided.php │ │ ├── Forbidden.php │ │ ├── Unauthorized.php │ │ ├── BadRequest.php │ │ └── Conflict.php │ ├── Server │ │ └── InternalServerError.php │ └── UnhandledRequestError.php ├── Contracts │ └── FirestoreDataTypeContract.php ├── Fields │ ├── FirestoreGeoPoint.php │ ├── FirestoreBytes.php │ ├── FirestoreTimestamp.php │ ├── FirestoreReference.php │ ├── FirestoreArray.php │ └── FirestoreObject.php ├── Handlers │ └── RequestErrorHandler.php ├── Helpers │ └── FirestoreHelper.php ├── Authentication │ └── FirestoreAuthentication.php ├── FirestoreDatabaseResource.php ├── FirestoreClient.php └── FirestoreDocument.php ├── CHANGELOG.md ├── LICENSE.md ├── composer.json ├── CONTRIBUTING.md └── README.md /src/Query/FirestoreQuery.php: -------------------------------------------------------------------------------- 1 | setData([$latitude, $longitude]); 14 | } 15 | 16 | public function setData($data) 17 | { 18 | return $this->data = [ 19 | 'latitude' => $data[0], 20 | 'longitude' => $data[1], 21 | ]; 22 | } 23 | 24 | public function getData() 25 | { 26 | return $this->data; 27 | } 28 | 29 | public function parseValue() 30 | { 31 | $value = $this->getData(); 32 | 33 | return $value; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Fields/FirestoreBytes.php: -------------------------------------------------------------------------------- 1 | setData($string); 19 | } 20 | 21 | public function setData($data) 22 | { 23 | return $this->data = $data; 24 | } 25 | 26 | public function getData() 27 | { 28 | return $this->data; 29 | } 30 | 31 | public function parseValue() 32 | { 33 | $value = $this->getData(); 34 | 35 | return FirestoreHelper::base64decode($value); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `Firestore PHP` will be documented in this file. 4 | 5 | ## 2.0.1 - 2019-02-25 6 | - Documentation error and typos fixed. 7 | - Saving last response when Guzzle's `BadResponseException` exception throws. 8 | - Had to use `git mv` to rename files changed in `2.0.0` 9 | - Added `has` method to validate key existence. 10 | 11 | ## 2.0.0 - 2019-02-23 12 | - Added Firebase Authentication 13 | - All files prefixed changed from `FireStore` to `Firestore` (notice the `s` in *store*) 14 | - Added `Bytes` support. 15 | - Exception handling support added. 16 | - Support added to list all documents, batch listing with query parameter. 17 | - Pagination support for bulk and document listing. 18 | - Improved naming convention throughout the package. 19 | - `FireStoreApiClient` changed to `FirestoreClient` 20 | - Documentation updated 21 | 22 | ## 1.0.1 - 2019-01-16 23 | - Add method for casting floating point values 24 | - Document ID flipped on `getDocument` method 25 | 26 | ## 1.0.0 - 2018-04-20 27 | - Initial release 28 | -------------------------------------------------------------------------------- /src/Fields/FirestoreTimestamp.php: -------------------------------------------------------------------------------- 1 | setData($data); 21 | } 22 | 23 | public function setData($data) 24 | { 25 | return $this->data = $data; 26 | } 27 | 28 | public function getData() 29 | { 30 | return $this->data; 31 | } 32 | 33 | public function parseValue() 34 | { 35 | $value = $this->getData(); 36 | 37 | if ( $value instanceof DateTime && method_exists($value, 'format') ) { 38 | return $value->format(self::DEFAULT_FORMAT); 39 | } 40 | 41 | return $value; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Ahsaan Muhammad Yousuf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ahsankhatri/firestore-php", 3 | "description": "Firestore PHP Client", 4 | "keywords": [ 5 | "php", 6 | "firestore", 7 | "firebase", 8 | "google" 9 | ], 10 | "homepage": "https://github.com/ahsankhatri/firestore-php", 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Ahsaan Muhammad Yousuf", 15 | "email": "ahsankhatri1992@gmail.com", 16 | "homepage": "https://ahsaan.me", 17 | "role": "Developer" 18 | } 19 | ], 20 | "require": { 21 | "php": ">=5.6.6", 22 | "guzzlehttp/guzzle": "~6.0|~5.0|~4.0", 23 | "ext-curl": "*", 24 | "ext-json": "*" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "5.7" 28 | }, 29 | "autoload": { 30 | "psr-4": { 31 | "MrShan0\\PHPFirestore\\": "src" 32 | } 33 | }, 34 | "autoload-dev": { 35 | "psr-4": { 36 | "MrShan0\\PHPFirestore\\Tests\\": "tests" 37 | } 38 | }, 39 | "scripts": { 40 | "test": "vendor/bin/phpunit", 41 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage" 42 | 43 | }, 44 | "config": { 45 | "sort-packages": true 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Fields/FirestoreReference.php: -------------------------------------------------------------------------------- 1 | databaseResource = $databaseResource; 17 | 18 | return $this->setData($data); 19 | } 20 | 21 | public function setData($data) 22 | { 23 | $this->data = FirestoreHelper::normalizeCollection($data); 24 | } 25 | 26 | public function getData() 27 | { 28 | return $this->data; 29 | } 30 | 31 | public function parseValue() 32 | { 33 | $value = 34 | 'projects/' . 35 | FirestoreClient::getConfig('projectId') . 36 | '/databases/' . 37 | FirestoreClient::getConfig('database') . 38 | '/documents/' . 39 | $this->getData(); 40 | 41 | return $value; 42 | } 43 | 44 | public function fetch() 45 | { 46 | return $this->databaseResource->getDocument($this->getData()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Fields/FirestoreArray.php: -------------------------------------------------------------------------------- 1 | setData((array) $data); 17 | } 18 | } 19 | 20 | public function add($data) 21 | { 22 | array_push($this->data, $data); 23 | 24 | return $this; 25 | } 26 | 27 | public function setData($data) 28 | { 29 | return $this->data = $data; 30 | } 31 | 32 | public function getData() 33 | { 34 | return $this->data; 35 | } 36 | 37 | public function parseValue() 38 | { 39 | $payload = [ 40 | 'values' => [], 41 | ]; 42 | 43 | foreach ($this->data as $data) { 44 | $document = new FirestoreDocument; 45 | call_user_func_array([$document, 'set'.ucfirst(FirestoreHelper::getType($data))], ['firestore', $data]); 46 | $payload['values'][] = $document->_getRawField('firestore'); 47 | } 48 | 49 | return $payload; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Fields/FirestoreObject.php: -------------------------------------------------------------------------------- 1 | setData((array) $data); 17 | } 18 | } 19 | 20 | public function add($data) 21 | { 22 | array_push($this->data, $data); 23 | 24 | return $this; 25 | } 26 | 27 | public function setData($data) 28 | { 29 | return $this->data = $data; 30 | } 31 | 32 | public function getData() 33 | { 34 | return $this->data; 35 | } 36 | 37 | public function parseValue() 38 | { 39 | $payload = [ 40 | 'fields' => [], 41 | ]; 42 | 43 | foreach ($this->data as $key => $data) { 44 | $document = new FirestoreDocument; 45 | call_user_func_array([$document, 'set'.ucfirst(FirestoreHelper::getType($data))], ['firestore', $data]); 46 | $payload['fields'][$key] = $document->_getRawField('firestore'); 47 | } 48 | 49 | return $payload; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Handlers/RequestErrorHandler.php: -------------------------------------------------------------------------------- 1 | exception = $exception; 21 | $this->body = $exception->getResponse()->getBody(); 22 | } 23 | 24 | public function handleError() 25 | { 26 | $errorCode = $this->exception->getResponse()->getStatusCode(); 27 | 28 | if (method_exists($this, 'handle'.$errorCode)) { 29 | $this->{'handle'.$errorCode}(); 30 | } 31 | 32 | $this->handleUnknown(); 33 | } 34 | 35 | protected function handle400() 36 | { 37 | throw new BadRequest($this->body); 38 | } 39 | 40 | protected function handle401() 41 | { 42 | throw new Unauthorized($this->body); 43 | } 44 | 45 | protected function handle403() 46 | { 47 | throw new Forbidden($this->body); 48 | } 49 | 50 | protected function handle404() 51 | { 52 | throw new NotFound($this->body); 53 | } 54 | 55 | protected function handle409() 56 | { 57 | throw new Conflict($this->body); 58 | } 59 | 60 | protected function handle500() 61 | { 62 | throw new InternalServerError($this->body); 63 | } 64 | 65 | private function handleUnknown() 66 | { 67 | throw new UnhandledRequestError($this->exception->getResponse()->getStatusCode(), $this->body); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | Please read and understand the contribution guide before creating an issue or pull request. 6 | 7 | ## Etiquette 8 | 9 | This project is open source, and as such, the maintainers give their free time to build and maintain the source code 10 | held within. They make the code freely available in the hope that it will be of use to other developers. It would be 11 | extremely unfair for them to suffer abuse or anger for their hard work. 12 | 13 | Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the 14 | world that developers are civilized and selfless people. 15 | 16 | It's the duty of the maintainer to ensure that all submissions to the project are of sufficient 17 | quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used. 18 | 19 | ## Viability 20 | 21 | When requesting or submitting new features, first consider whether it might be useful to others. Open 22 | source projects are used by many developers, who may have entirely different needs to your own. Think about 23 | whether or not your feature is likely to be used by other users of the project. 24 | 25 | ## Procedure 26 | 27 | Before filing an issue: 28 | 29 | - Attempt to replicate the problem, to ensure that it wasn't a coincidental incident. 30 | - Check to make sure your feature suggestion isn't already present within the project. 31 | - Check the pull requests tab to ensure that the bug doesn't have a fix in progress. 32 | - Check the pull requests tab to ensure that the feature isn't already in progress. 33 | 34 | Before submitting a pull request: 35 | 36 | - Check the codebase to ensure that your feature doesn't already exist. 37 | - Check the pull requests to ensure that another person hasn't already submitted the feature or fix. 38 | 39 | ## Requirements 40 | 41 | If the project maintainer has any additional requirements, you will find them listed here. 42 | 43 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 44 | 45 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option. 46 | 47 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 48 | 49 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 50 | 51 | **Happy coding**! 52 | -------------------------------------------------------------------------------- /src/Helpers/FirestoreHelper.php: -------------------------------------------------------------------------------- 1 | client = $client; 30 | } 31 | 32 | /** 33 | * Return absolute url to make request 34 | * 35 | * @param string $resource 36 | * 37 | * @return string 38 | */ 39 | private function constructUrl($resource) 40 | { 41 | return $this->authRoot . $resource . '?key=' . $this->client->getApiKey(); 42 | } 43 | 44 | /** 45 | * You can call this method and set token if you've already generate token 46 | * 47 | * @param string $token 48 | * 49 | * @return array 50 | */ 51 | public function setCustomToken($token) 52 | { 53 | return $this->client->setOption('headers', [ 54 | 'Authorization' => 'Bearer ' . $token, 55 | ]); 56 | } 57 | 58 | /** 59 | * Extract auth token from client 60 | * 61 | * @return null|string 62 | */ 63 | public function getAuthToken() 64 | { 65 | $authHeader = $this->client->getOption('headers'); 66 | 67 | if (!$authHeader) { 68 | return null; 69 | } 70 | 71 | return substr(reset($authHeader), 7); 72 | } 73 | 74 | /** 75 | * Login with email and password into Firebase Authentication 76 | * 77 | * @param string $email 78 | * @param string $password 79 | * @param boolean $setToken 80 | * 81 | * @return object 82 | */ 83 | public function signInEmailPassword($email, $password, $setToken = true) 84 | { 85 | $response = $this->authRequest('POST', 'verifyPassword', [ 86 | 'form_params' => [ 87 | 'email' => $email, 88 | 'password' => $password, 89 | 'returnSecureToken' => 'true', 90 | ] 91 | ]); 92 | 93 | if ($setToken) { 94 | if (!array_key_exists('idToken', $response)) { 95 | throw new BadRequest('idToken not found!'); 96 | } 97 | 98 | $this->setCustomToken($response['idToken']); 99 | } 100 | 101 | return $response; 102 | } 103 | 104 | /** 105 | * Login with anonymously into Firebase Authentication 106 | * 107 | * @param boolean $setToken 108 | * 109 | * @return object 110 | */ 111 | public function signInAnonymously($setToken = true) 112 | { 113 | $response = $this->authRequest('POST', 'signupNewUser', [ 114 | 'form_params' => [ 115 | 'returnSecureToken' => 'true', 116 | ] 117 | ]); 118 | 119 | if ($setToken) { 120 | if (!array_key_exists('idToken', $response)) { 121 | throw new BadRequest('idToken not found!'); 122 | } 123 | 124 | $this->setCustomToken($response['idToken']); 125 | } 126 | 127 | return $response; 128 | } 129 | 130 | /** 131 | * Responsible to make request to firebase authentication mechanism (rest-api) 132 | * 133 | * @param string $method 134 | * @param string $resource 135 | * @param array $options 136 | * 137 | * @return object 138 | */ 139 | private function authRequest($method, $resource, array $options = []) 140 | { 141 | try { 142 | $options = array_merge($this->client->getOptions(), $options); 143 | 144 | // Unset authorization if set mistakenly 145 | if (isset($options['headers']['Authorization'])) { 146 | unset($options['headers']['Authorization']); 147 | } 148 | 149 | $this->client->setLastResponse( 150 | $this->client->getHttpClient()->request($method, $this->constructUrl($resource), $options) 151 | ); 152 | 153 | return FirestoreHelper::decode((string) $this->client->getLastResponse()->getBody()); 154 | } catch (BadResponseException $exception) { 155 | $this->setLastResponse($exception->getResponse()); 156 | $this->handleError($exception); 157 | } 158 | } 159 | 160 | /** 161 | * Throw our own custom handler for errors. 162 | * 163 | * @param BadResponseException $exception 164 | * 165 | * @throws \MrShan0\PHPFirestore\Exceptions\Client\BadRequest 166 | * @throws \MrShan0\PHPFirestore\Exceptions\Client\Unauthorized 167 | * @throws \MrShan0\PHPFirestore\Exceptions\Client\Forbidden 168 | * @throws \MrShan0\PHPFirestore\Exceptions\Client\NotFound 169 | * @throws \MrShan0\PHPFirestore\Exceptions\Client\Conflict 170 | * @throws \MrShan0\PHPFirestore\Exceptions\Server\InternalServerError 171 | * @throws \MrShan0\PHPFirestore\Exceptions\UnhandledRequestError 172 | */ 173 | private function handleError(Exception $exception) 174 | { 175 | $handler = new RequestErrorHandler($exception); 176 | $handler->handleError(); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Firestore Client for PHP 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/ahsankhatri/firestore-php.svg?style=flat-square)](https://packagist.org/packages/ahsankhatri/firestore-php) 4 | [![Total Downloads](https://img.shields.io/packagist/dt/ahsankhatri/firestore-php.svg?style=flat-square)](https://packagist.org/packages/ahsankhatri/firestore-php) 5 | [![License](https://poser.pugx.org/ahsankhatri/firestore-php/license?format=flat-square)](https://packagist.org/packages/ahsankhatri/firestore-php) 6 | 7 | This package is totally based on [Firestore REST API](https://firebase.google.com/docs/firestore/use-rest-api) 8 | 9 | ## Authentication / Generate API Key 10 | 11 | 1) Visit [Google Cloud Firestore API](https://console.cloud.google.com/projectselector/apis/api/firestore.googleapis.com/overview) 12 | 2) Select your desired project. 13 | 3) Select `Credentials` from left menu and select `API Key` from Server key or `Create your own credentials` 14 | 15 | ## Installation 16 | 17 | You can install the package via composer: 18 | 19 | ```bash 20 | composer require ahsankhatri/firestore-php 21 | ``` 22 | 23 | or install it by adding it to `composer.json` then run `composer update` 24 | 25 | ```javascript 26 | "require": { 27 | "ahsankhatri/firestore-php": "^2.0", 28 | } 29 | ``` 30 | 31 | ## Dependencies 32 | 33 | The bindings require the following extensions in order to work properly: 34 | 35 | - [`curl`](https://secure.php.net/manual/en/book.curl.php) 36 | - [`json`](https://secure.php.net/manual/en/book.json.php) 37 | - [`guzzlehttp/guzzle`](https://packagist.org/packages/guzzlehttp/guzzle) 38 | 39 | If you use Composer, these dependencies should be handled automatically. If you install manually, you'll want to make sure that these extensions are available. 40 | 41 | ## Usage 42 | 43 | #### Initialization 44 | 45 | ```php 46 | $firestoreClient = new FirestoreClient('project-id', 'AIzaxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', [ 47 | 'database' => '(default)', 48 | ]); 49 | ``` 50 | 51 | #### Adding a document 52 | 53 | ```php 54 | $firestoreClient->addDocument($collection, [ 55 | 'booleanTrue' => true, 56 | 'booleanFalse' => false, 57 | 'null' => null, 58 | 'string' => 'abc123', 59 | 'integer' => 123456, 60 | 'arrayRaw' => [ 61 | 'string' => 'abc123', 62 | ], 63 | 'bytes' => new FirestoreBytes('bytesdata'), 64 | 'array' => new FirestoreArray([ 65 | 'string' => 'abc123', 66 | ]), 67 | 'reference' => new FirestoreReference('/users/23'), 68 | 'object' => new FirestoreObject(['nested1' => new FirestoreObject(['nested2' => new FirestoreObject(['nested3' => 'test'])])]), 69 | 'timestamp' => new FirestoreTimestamp, 70 | 'geopoint' => new FirestoreGeoPoint(1,1), 71 | ]); 72 | ``` 73 | 74 | **NOTE:** Pass third argument if you want your custom **document id** to set else auto-id will generate it for you. 75 | 76 | Or 77 | 78 | ```php 79 | $document = new FirestoreDocument; 80 | $document->setObject('sdf', new FirestoreObject(['nested1' => new FirestoreObject(['nested2' => new FirestoreObject(['nested3' => 'test'])])])); 81 | $document->setBoolean('booleanTrue', true); 82 | $document->setBoolean('booleanFalse', false); 83 | $document->setNull('null', null); 84 | $document->setString('string', 'abc123'); 85 | $document->setInteger('integer', 123456); 86 | $document->setArray('arrayRaw', ['string'=>'abc123']); 87 | $document->setBytes('bytes', new FirestoreBytes('bytesdata')); 88 | $document->setArray('arrayObject', new FirestoreArray(['string' => 'abc123'])); 89 | $document->setTimestamp('timestamp', new FirestoreTimestamp); 90 | $document->setGeoPoint('geopoint', new FirestoreGeoPoint(1.11,1.11)); 91 | 92 | $firestoreClient->addDocument($collection, $document, 'customDocumentId'); 93 | ``` 94 | 95 | And.. 96 | 97 | ```php 98 | $document->fillValues([ 99 | 'string' => 'abc123', 100 | 'boolean' => true, 101 | ]); 102 | ``` 103 | 104 | #### Inserting/Updating a document 105 | 106 | - Update (Merge) or Insert document 107 | 108 | Following will merge document (if exist) else insert the data. 109 | 110 | ```php 111 | $firestoreClient->updateDocument($documentRoot, [ 112 | 'newFieldToAdd' => new FirestoreTimestamp(new DateTime('2018-04-20 15:00:00')), 113 | 'existingFieldToRemove' => new FirestoreDeleteAttribute 114 | ]); 115 | ``` 116 | 117 | **NOTE:** Passing 3rd argument as a boolean _true_ will force check that document must exist and vice-versa in order to perform update operation. 118 | 119 | For example: If you want to update document only if exist else `MrShan0\PHPFirestore\Exceptions\Client\NotFound` (Exception) will be thrown. 120 | 121 | ```php 122 | $firestoreClient->updateDocument($documentPath, [ 123 | 'newFieldToAdd' => new FirestoreTimestamp(new DateTime('2018-04-20 15:00:00')), 124 | 'existingFieldToRemove' => new FirestoreDeleteAttribute 125 | ], true); 126 | ``` 127 | 128 | - Overwirte or Insert document 129 | 130 | ```php 131 | $firestoreClient->setDocument($collection, $documentId, [ 132 | 'newFieldToAdd' => new FirestoreTimestamp(new DateTime('2018-04-20 15:00:00')), 133 | 'existingFieldToRemove' => new FirestoreDeleteAttribute 134 | ], [ 135 | 'exists' => true, // Indicate document must exist 136 | ]); 137 | ``` 138 | 139 | #### Deleting a document 140 | 141 | ```php 142 | $collection = 'collection/document/innerCollection'; 143 | $firestoreClient->deleteDocument($collection, $documentId); 144 | ``` 145 | 146 | #### List documents with pagination (or custom parameters) 147 | 148 | ```php 149 | $collections = $firestoreClient->listDocuments('users', [ 150 | 'pageSize' => 1, 151 | 'pageToken' => 'nextpagetoken' 152 | ]); 153 | ``` 154 | 155 | **Note:** You can pass custom parameters as supported by [firestore list document](https://firebase.google.com/docs/firestore/reference/rest/v1/projects.databases.documents/list#query-parameters) 156 | 157 | #### Get field from document 158 | 159 | ```php 160 | $document->get('bytes')->parseValue(); // will return bytes decoded value. 161 | 162 | // Catch field that doesn't exist in document 163 | try { 164 | $document->get('allowed_notification'); 165 | } catch (\MrShan0\PHPFirestore\Exceptions\Client\FieldNotFound $e) { 166 | // Set default value 167 | } 168 | ``` 169 | 170 | ### Firebase Authentication 171 | 172 | #### Sign in with Email and Password. 173 | 174 | ```php 175 | $firestoreClient 176 | ->authenticator() 177 | ->signInEmailPassword('testuser@example.com', 'abc123'); 178 | ``` 179 | 180 | #### Sign in Anonymously. 181 | 182 | ```php 183 | $firestoreClient 184 | ->authenticator() 185 | ->signInAnonymously(); 186 | ``` 187 | 188 | ### Retrieve Auth Token 189 | 190 | ```php 191 | $authToken = $firestoreClient->authenticator()->getAuthToken(); 192 | ``` 193 | 194 | ### TODO 195 | - [x] Added delete attribute support. 196 | - [x] Add Support for Object, Boolean, Null, String, Integer, Array, Timestamp, GeoPoint, Bytes 197 | - [x] Add Exception Handling. 198 | - [x] List all documents. 199 | - [ ] List all collections. 200 | - [x] Filters and pagination support. 201 | - [ ] Structured Query support. 202 | - [ ] Transaction support. 203 | - [ ] Indexes support. 204 | - [ ] Entire collection delete support. 205 | 206 | ### Testing 207 | 208 | ``` bash 209 | composer test 210 | ``` 211 | 212 | ### Changelog 213 | 214 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 215 | 216 | ## Contributing 217 | 218 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 219 | 220 | ### Security 221 | 222 | If you discover any security related issues, please email ahsankhatri1992@gmail.com instead of using the issue tracker. 223 | 224 | ## Credits 225 | 226 | - [Ahsaan Muhammad Yousuf](https://ahsaan.me) 227 | - [All Contributors](../../contributors) 228 | 229 | ## License 230 | 231 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 232 | -------------------------------------------------------------------------------- /src/FirestoreDatabaseResource.php: -------------------------------------------------------------------------------- 1 | client = $client; 24 | } 25 | 26 | /** 27 | * To validate parity of slashes in path 28 | * 29 | * @param string $path 30 | * @param boolean $shouldBeOdd 31 | * 32 | * @throws \MrShan0\PHPFirestore\Exceptions\Client\InvalidPathProvided 33 | * 34 | * @return boolean 35 | */ 36 | private function validatePath($path, $shouldBeOdd = true) 37 | { 38 | $slashesCount = substr_count($path, '/'); 39 | $isEvenNumber = $slashesCount % 2 == 0; 40 | 41 | if (($shouldBeOdd && $isEvenNumber) || (!$shouldBeOdd && !$isEvenNumber)) { 42 | throw new InvalidPathProvided($path); 43 | } 44 | 45 | return true; 46 | } 47 | 48 | /** 49 | * List all documents in collection path given 50 | * 51 | * @param string $collection 52 | * @param array $parameters 53 | * @param array $options 54 | * 55 | * @return array 56 | */ 57 | public function listDocuments($collection, array $parameters = [], array $options = []) 58 | { 59 | $this->validatePath($collection, false); 60 | 61 | $response = $this->client->request('GET', 'documents/' . FirestoreHelper::normalizeCollection($collection), $options, $parameters); 62 | 63 | if (isset($response['documents'])) { 64 | $documents = array_map(function($doc) { 65 | return new FirestoreDocument($doc); 66 | }, $response['documents']); 67 | } else { 68 | $documents = []; 69 | } 70 | 71 | return array_merge($response, [ 72 | 'documents' => $documents, 73 | ]); 74 | } 75 | 76 | /** 77 | * Get document object by given path 78 | * 79 | * @param string $documentPath 80 | * @param array $parameters 81 | * @param array $options 82 | * 83 | * @return \MrShan0\PHPFirestore\FirestoreDocument 84 | */ 85 | public function getDocument($documentPath, array $parameters = [], array $options = []) 86 | { 87 | $this->validatePath($documentPath); 88 | 89 | $documentPath = 'documents/' . FirestoreHelper::normalizeCollection($documentPath); 90 | $document = $this->client->request('GET', $documentPath, $options, $parameters); 91 | 92 | if ( FirestoreDocument::isValidDocument($document) ) { 93 | return new FirestoreDocument($document, $this); 94 | } 95 | 96 | throw new NotFound((string) $this->client->getLastResponse()->getBody()); 97 | } 98 | 99 | /** 100 | * Get batch (multiple) documents 101 | * 102 | * @param array $documentsId 103 | * @param array $parameters 104 | * @param array $options 105 | * 106 | * @return array 107 | */ 108 | public function getBatchDocuments(array $documentsId, array $parameters = [], array $options = []) 109 | { 110 | // Validates all documents provided 111 | $documentsPath = array_map(function($doc) { 112 | $this->validatePath($doc); 113 | 114 | return $this->client->getRelativeDatabasePath() . '/documents/' . FirestoreHelper::normalizeCollection($doc); 115 | }, $documentsId); 116 | 117 | $response = $this->client->request('POST', 'documents:batchGet', array_merge($options, [ 118 | 'json' => [ 119 | 'documents' => $documentsPath, 120 | ] 121 | ]), $parameters); 122 | 123 | $documentBasePathLength = strlen($this->client->getRelativeDatabasePath() . '/documents/'); 124 | $results = [ 125 | 'documents' => [], 126 | 'missing' => [], 127 | ]; 128 | 129 | foreach ($response as $doc) { 130 | if (array_key_exists('found', $doc)) { 131 | $results['documents'][] = new FirestoreDocument($doc['found']); 132 | } else { 133 | $results['missing'][] = substr($doc['missing'], $documentBasePathLength); 134 | } 135 | } 136 | 137 | return $results; 138 | } 139 | 140 | /** 141 | * Insert document under collection path given 142 | * 143 | * @param string $collection 144 | * @param array|FirestoreDocument $payload 145 | * @param string|null $documentId 146 | * @param array $parameters 147 | * @param array $options 148 | * 149 | * @return \MrShan0\PHPFirestore\FirestoreDocument 150 | */ 151 | public function addDocument($collection, $payload, $documentId = null, array $parameters = [], array $options = []) 152 | { 153 | $this->validatePath($collection, false); 154 | 155 | if ( !($payload instanceof FirestoreDocument) ) { 156 | $document = new FirestoreDocument(); 157 | } else { 158 | $document = $payload; 159 | } 160 | 161 | // Set custom Document id 162 | if ( $documentId ) { 163 | $parameters['documentId'] = $documentId; 164 | } 165 | 166 | if ( is_array($payload) ) { 167 | $document->fillValues($payload); 168 | } 169 | 170 | $response = $this->client->request('POST', 'documents/' . FirestoreHelper::normalizeCollection($collection), array_merge($options, [ 171 | 'json' => FirestoreHelper::decode($document->toJson()) 172 | ]), $parameters); 173 | 174 | return new FirestoreDocument($response); 175 | } 176 | 177 | /** 178 | * It'll merge document with existing one or insert if it doesn't exist. When you want your update your 179 | * data and don't want to affect existing parameters, use this. 180 | * 181 | * @param string $documentPath 182 | * @param array|FirestoreDocument $payload 183 | * @param boolean $documentExists 184 | * @param array $parameters 185 | * @param array $options 186 | * 187 | * @return \MrShan0\PHPFirestore\FirestoreDocument 188 | */ 189 | public function updateDocument($documentPath, $payload, $documentExists = null, array $parameters = [], array $options = []) 190 | { 191 | $this->options['merge'] = true; 192 | 193 | return $this->setDocument($documentPath, $payload, $documentExists, $parameters, $options); 194 | } 195 | 196 | /** 197 | * To overwrite/insret your document into firestore. 198 | * 199 | * @param string $documentPath 200 | * @param array|FirestoreDocument $payload 201 | * @param boolean $documentExists 202 | * @param array $parameters 203 | * @param array $options 204 | * 205 | * @return \MrShan0\PHPFirestore\FirestoreDocument 206 | */ 207 | public function setDocument($documentPath, $payload, $documentExists = null, array $parameters = [], array $options = []) 208 | { 209 | $this->validatePath($documentPath); 210 | 211 | if ($payload instanceof FirestoreDocument) { 212 | $document = $payload; 213 | } elseif ( is_array($payload) ) { 214 | $document = (new FirestoreDocument)->fillValues($payload); 215 | } 216 | 217 | if ($documentExists !== null) { 218 | $parameters['currentDocument.exists'] = !!$documentExists; 219 | } 220 | 221 | if ( array_key_exists('merge', $this->options) && $this->options['merge'] === true ) { 222 | $parameters['updateMask.fieldPaths'] = array_unique(array_keys($document->toArray())); 223 | } 224 | 225 | $response = $this->client->request('PATCH', 'documents/' . FirestoreHelper::normalizeCollection($documentPath), array_merge($options, [ 226 | 'json' => FirestoreHelper::decode($document->toJson()) 227 | ]), $parameters); 228 | 229 | return new FirestoreDocument($response); 230 | } 231 | 232 | /** 233 | * Delete document from firestore 234 | * 235 | * @param string $document 236 | * @param array $options 237 | * 238 | * @return boolean 239 | */ 240 | public function deleteDocument($document, array $options = []) 241 | { 242 | if ($document instanceof FirestoreDocument) { 243 | $document = FirestoreHelper::normalizeCollection($document->getRelativeName()); 244 | } 245 | 246 | $this->validatePath($document); 247 | 248 | $response = $this->client->request('DELETE', 'documents/' . FirestoreHelper::normalizeCollection($document), $options); 249 | 250 | return count($response) === 0 ? true : false; 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/FirestoreClient.php: -------------------------------------------------------------------------------- 1 | true]; 57 | 58 | /** 59 | * Set last response from API 60 | * 61 | * @var string 62 | */ 63 | private $lastResponse; 64 | 65 | /** 66 | * FirestoreAuthentication object 67 | * 68 | * @var \MrShan0\PHPFirestore\Authentication\FirestoreAuthentication 69 | */ 70 | private $authenticator; 71 | 72 | /** 73 | * FirestoreQuery object 74 | * 75 | * @var FirestoreQuery 76 | */ 77 | private $structuredQuery; 78 | 79 | /** 80 | * Config container for internal purposes 81 | * 82 | * @var array 83 | */ 84 | private static $config; 85 | 86 | /** 87 | * Client constructor. 88 | * 89 | * @param string $projectId 90 | * @param string $apiKey 91 | * @param array $config 92 | * @param array $options 93 | * @param GuzzleClient|null $guzzle 94 | */ 95 | public function __construct($projectId, $apiKey, array $config = [], GuzzleClient $guzzle = null, $options = []) 96 | { 97 | $this->setHttpClient($guzzle ?: new GuzzleClient()); 98 | $this->setApiKey($apiKey); 99 | $this->setOptions($options); 100 | 101 | $this->setConfig(array_merge($config, [ 102 | 'database' => '(default)', 103 | 'projectId' => $projectId, 104 | ])); 105 | } 106 | 107 | /** 108 | * Config setter 109 | * 110 | * @param array $configs 111 | * @return void 112 | */ 113 | public static function setConfig(array $configs) 114 | { 115 | self::$config = $configs; 116 | } 117 | 118 | /** 119 | * Get configs 120 | * 121 | * @param string $key 122 | * @return mixed 123 | */ 124 | public static function getConfig($key) 125 | { 126 | return array_key_exists($key, self::$config) ? self::$config[$key] : null; 127 | } 128 | 129 | /** 130 | * @param GuzzleClient $client 131 | * 132 | * @return void 133 | */ 134 | public function setHttpClient(GuzzleClient $client) 135 | { 136 | $this->guzzle = $client; 137 | } 138 | 139 | /** 140 | * @return mixed 141 | */ 142 | public function getHttpClient() 143 | { 144 | return $this->guzzle; 145 | } 146 | 147 | /** 148 | * Set a key for authentication with checkr. 149 | * 150 | * @param $apiKey 151 | * 152 | * @return void 153 | */ 154 | public function setApiKey($apiKey) 155 | { 156 | $this->apiKey = $apiKey; 157 | } 158 | 159 | /** 160 | * Get the firestore api key. 161 | * 162 | * @return string 163 | */ 164 | public function getApiKey() 165 | { 166 | return $this->apiKey; 167 | } 168 | 169 | /** 170 | * Return firestore relative database path 171 | * 172 | * @return string 173 | */ 174 | public static function getRelativeDatabasePath() 175 | { 176 | return 'projects/' . self::getConfig('projectId') . '/databases/' . self::getConfig('database'); 177 | } 178 | 179 | public function getApiEndPoint() 180 | { 181 | return $this->apiRoot; 182 | } 183 | 184 | /** 185 | * @param bool $status 186 | * 187 | * @return void 188 | */ 189 | public function setLastResponse($response) 190 | { 191 | $this->lastResponse = $response; 192 | } 193 | 194 | /** 195 | * @return Guzzle\Http\Message\Response 196 | */ 197 | public function getLastResponse() 198 | { 199 | return $this->lastResponse; 200 | } 201 | 202 | /** 203 | * Set options for HttpClient. 204 | * 205 | * @param array $options 206 | * 207 | * @return array 208 | */ 209 | public function setOptions(array $options): array 210 | { 211 | return $this->options = $options; 212 | } 213 | 214 | /** 215 | * Set individual option. 216 | * 217 | * @param string $key 218 | * @param mixed $value 219 | * 220 | * @return mixed 221 | */ 222 | public function setOption($key, $value) 223 | { 224 | return $this->options[$key] = $value; 225 | } 226 | 227 | /** 228 | * @return array 229 | */ 230 | public function getOptions(): array 231 | { 232 | return $this->options; 233 | } 234 | 235 | /** 236 | * @param $key 237 | * 238 | * @return bool|mixed 239 | * @return void 240 | */ 241 | public function getOption($key) 242 | { 243 | if (isset($this->options[$key])) { 244 | return $this->options[$key]; 245 | } 246 | 247 | return false; 248 | } 249 | 250 | /** 251 | * Compile final url to hit service. 252 | * 253 | * @return string 254 | */ 255 | private function constructUrl($method, $params=null) 256 | { 257 | $params = is_array($params) ? $params : []; 258 | $builtQuery = (count($params) ? '&' . http_build_query($params) : ''); 259 | 260 | if ( count(preg_grep('~\.fieldPaths~', array_keys($params))) ) { 261 | $builtQuery = preg_replace('/%5B\d+%5D/', '', $builtQuery); 262 | } 263 | 264 | return ( 265 | $this->getApiEndPoint() . $this->getRelativeDatabasePath() . '/' . $method . '?key=' . $this->getApiKey() . $builtQuery 266 | ); 267 | } 268 | 269 | /** 270 | * @return \MrShan0\PHPFirestore\Authentication\FirestoreAuthentication 271 | */ 272 | public function getFirestoreAuth() 273 | { 274 | if ($this->authenticator) { 275 | return $this->authenticator; 276 | } 277 | 278 | $this->authenticator = new FirestoreAuthentication($this); 279 | 280 | return $this->authenticator; 281 | } 282 | 283 | /** 284 | * @return \MrShan0\PHPFirestore\Query\FirestoreQuery 285 | */ 286 | public function getFirestoreQuery() 287 | { 288 | if ($this->structuredQuery) { 289 | return $this->structuredQuery; 290 | } 291 | 292 | $this->structuredQuery = new FirestoreQuery($this); 293 | 294 | return $this->structuredQuery; 295 | } 296 | 297 | /** 298 | * Fetch an API resource to handle the client request. 299 | * 300 | * @param string $name 301 | * @param array $args 302 | * 303 | * @return mixed 304 | */ 305 | private function api($name, $args) 306 | { 307 | $firestoreInstance = new FirestoreDatabaseResource($this); 308 | 309 | return call_user_func_array([$firestoreInstance, $name], $args); 310 | } 311 | 312 | /** 313 | * Call Firestore resource method. 314 | * 315 | * @param $name 316 | * @param $args 317 | * 318 | * @return mixed 319 | */ 320 | public function __call($name, $args) 321 | { 322 | switch ($name) { 323 | case 'authenticator': 324 | return $this->getFirestoreAuth(); 325 | case 'query': 326 | return $this->getFirestoreQuery(); 327 | break; 328 | default: 329 | return $this->api($name, $args); 330 | } 331 | 332 | } 333 | 334 | /** 335 | * Make a request through Guzzle. 336 | * 337 | * @param $method 338 | * @param $path 339 | * @param array $options 340 | * @param array $parameters 341 | * 342 | * @throws \MrShan0\PHPFirestore\Exceptions\Client\BadRequest 343 | * @throws \MrShan0\PHPFirestore\Exceptions\Client\Conflict 344 | * @throws \MrShan0\PHPFirestore\Exceptions\Client\Forbidden 345 | * @throws \MrShan0\PHPFirestore\Exceptions\Client\NotFound 346 | * @throws \MrShan0\PHPFirestore\Exceptions\Client\Unauthorized 347 | * @throws \MrShan0\PHPFirestore\Exceptions\Server\InternalServerError 348 | * @throws \MrShan0\PHPFirestore\Exceptions\UnhandledRequestError 349 | * 350 | * @return mixed 351 | */ 352 | public function request($method, $path, array $options = [], array $parameters = []) 353 | { 354 | $body = ''; 355 | $options = array_merge($this->getOptions(), $options); 356 | 357 | try { 358 | $response = $this->getHttpClient()->request($method, $this->constructUrl($path, $parameters), $options); 359 | $this->setLastResponse($response); 360 | $body = FirestoreHelper::decode((string) $response->getBody()); 361 | } catch (BadResponseException $exception) { 362 | $this->setLastResponse($exception->getResponse()); 363 | $this->handleError($exception); 364 | } 365 | 366 | return $body; 367 | } 368 | 369 | /** 370 | * Throw our own custom handler for errors. 371 | * 372 | * @param BadResponseException $exception 373 | * 374 | * @throws \MrShan0\PHPFirestore\Exceptions\Client\BadRequest 375 | * @throws \MrShan0\PHPFirestore\Exceptions\Client\Conflict 376 | * @throws \MrShan0\PHPFirestore\Exceptions\Client\Forbidden 377 | * @throws \MrShan0\PHPFirestore\Exceptions\Client\NotFound 378 | * @throws \MrShan0\PHPFirestore\Exceptions\Client\Unauthorized 379 | * @throws \MrShan0\PHPFirestore\Exceptions\Server\InternalServerError 380 | * @throws \MrShan0\PHPFirestore\Exceptions\UnhandledRequestError 381 | * 382 | */ 383 | private function handleError(Exception $exception) 384 | { 385 | $handler = new RequestErrorHandler($exception); 386 | $handler->handleError(); 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /src/FirestoreDocument.php: -------------------------------------------------------------------------------- 1 | name = $object['name']; 39 | $this->createTime = isset($object['createTime']) ? $object['createTime'] : null; 40 | $this->updateTime = isset($object['updateTime']) ? $object['updateTime'] : null; 41 | 42 | if (isset($object['fields'])) { 43 | foreach ($object['fields'] as $fieldName => $value) { 44 | $this->fields[ $fieldName ] = $value; 45 | } 46 | } 47 | } 48 | 49 | if (null !== $databaseResource) { 50 | if (!$databaseResource instanceof FirestoreDatabaseResource) { 51 | throw new InvalidArgumentException('Instance passed must be of FirestoreDatabaseResource'); 52 | } 53 | 54 | $this->databaseResource = $databaseResource; 55 | } 56 | } 57 | 58 | /** 59 | * @return string 60 | */ 61 | public function getRelativeName() 62 | { 63 | $rootPath = FirestoreClient::getRelativeDatabasePath() . '/documents'; 64 | 65 | return substr($this->getAbsoluteName(), strlen($rootPath)); 66 | } 67 | 68 | /** 69 | * @return string 70 | */ 71 | public function getAbsoluteName() 72 | { 73 | return $this->name; 74 | } 75 | 76 | /** 77 | * @return DateTime 78 | */ 79 | public function getCreatedTime() 80 | { 81 | if (is_null($this->createTime)) { 82 | return null; 83 | } 84 | 85 | return new DateTime($this->createTime); 86 | } 87 | 88 | /** 89 | * @return DateTime 90 | */ 91 | public function getUpdatedTime() 92 | { 93 | if (is_null($this->updateTime)) { 94 | return null; 95 | } 96 | 97 | return new DateTime($this->updateTime); 98 | } 99 | 100 | /** 101 | * To fill values into Document object appropriately. 102 | * 103 | * @param array $payload 104 | * 105 | * @return \MrShan0\PHPFirestore\FirestoreDocument 106 | */ 107 | public function fillValues(array $payload) 108 | { 109 | foreach ($payload as $key => $value) { 110 | call_user_func_array([$this, 'set'.ucfirst(FirestoreHelper::getType($value))], [$key, $value]); 111 | } 112 | 113 | return $this; 114 | } 115 | 116 | /** 117 | * To determine is valid document when fetched from firestore. 118 | * Won't work with `new FirestoreDocument` 119 | * 120 | * @param object 121 | * 122 | * @return boolean 123 | */ 124 | public static function isValidDocument($object) 125 | { 126 | return ( array_key_exists('name', $object) && array_key_exists('fields', $object) ); 127 | } 128 | 129 | /** 130 | * @return string 131 | */ 132 | public function getName() 133 | { 134 | return $this->name; 135 | } 136 | 137 | /** 138 | * @return void 139 | */ 140 | public function setString($fieldName, $value) 141 | { 142 | $this->fields[$fieldName] = [ 143 | 'stringValue' => $value 144 | ]; 145 | } 146 | 147 | /** 148 | * @param array $value 149 | * 150 | * @return string 151 | */ 152 | public function getString($value) 153 | { 154 | return strval($value['stringValue']); 155 | } 156 | 157 | /** 158 | * @return void 159 | */ 160 | public function setDouble($fieldName, $value) 161 | { 162 | $this->fields[$fieldName] = [ 163 | 'doubleValue' => floatval($value) 164 | ]; 165 | } 166 | 167 | /** 168 | * @param array $value 169 | * 170 | * @return float 171 | */ 172 | public function getDouble($value) 173 | { 174 | return floatval($value['doubleValue']); 175 | } 176 | 177 | /** 178 | * @return void 179 | */ 180 | public function setArray($fieldName, $value) 181 | { 182 | if ( !$value instanceof FirestoreArray ) { 183 | $payload = new FirestoreArray; 184 | foreach ($value as $row) { 185 | $payload->add( $row ); 186 | } 187 | 188 | $value = $payload; 189 | } 190 | 191 | $this->fields[$fieldName] = [ 192 | 'arrayValue' => $value->parseValue() 193 | ]; 194 | } 195 | 196 | /** 197 | * @param array $value 198 | * 199 | * @return array 200 | */ 201 | public function getArray($value) 202 | { 203 | $results = []; 204 | 205 | if (isset($value['arrayValue']['values']) && is_array($value['arrayValue']['values'])) { 206 | foreach ($value['arrayValue']['values'] as $key => $value) { 207 | $results[$key] = $this->castValue($value); 208 | } 209 | } 210 | 211 | return $results; 212 | } 213 | 214 | /** 215 | * @return void 216 | */ 217 | public function setObject($fieldName, $value) 218 | { 219 | if ( !$value instanceof FirestoreObject ) { 220 | $payload = new FirestoreObject; 221 | foreach ($value as $row) { 222 | $payload->add( $row ); 223 | } 224 | 225 | $value = $payload; 226 | } 227 | 228 | $this->fields[$fieldName] = [ 229 | 'mapValue' => $value->parseValue() 230 | ]; 231 | } 232 | 233 | /** 234 | * @param array $value 235 | * 236 | * @return \MrShan0\PHPFirestore\Fields\FirestoreObject 237 | */ 238 | public function getObject($value) 239 | { 240 | $results = []; 241 | 242 | foreach ($value['mapValue']['fields'] as $key => $value) { 243 | $results[$key] = $this->castValue($value); 244 | } 245 | 246 | return new FirestoreObject([$results]); 247 | } 248 | 249 | /** 250 | * @return void 251 | */ 252 | public function setReference($fieldName, FirestoreReference $value) 253 | { 254 | $this->fields[$fieldName] = [ 255 | 'referenceValue' => $value->parseValue() 256 | ]; 257 | } 258 | 259 | /** 260 | * @param array $value 261 | * 262 | * @return \MrShan0\PHPFirestore\Fields\FirestoreReference 263 | */ 264 | public function getReference($value) 265 | { 266 | return new FirestoreReference( substr($value['referenceValue'], strpos($value['referenceValue'], '/documents/')+10), $this->databaseResource ); 267 | } 268 | 269 | /** 270 | * @return void 271 | */ 272 | public function setGeoPoint($fieldName, FirestoreGeoPoint $value) 273 | { 274 | $this->fields[$fieldName] = [ 275 | 'geoPointValue' => $value->parseValue() 276 | ]; 277 | } 278 | 279 | /** 280 | * @param array $value 281 | * 282 | * @return \MrShan0\PHPFirestore\Fields\FirestoreGeoPoint 283 | */ 284 | public function getGeoPoint($value) 285 | { 286 | return new FirestoreGeoPoint($value['geoPointValue']['latitude'], $value['geoPointValue']['longitude']); 287 | } 288 | 289 | /** 290 | * @return void 291 | */ 292 | public function setTimestamp($fieldName, FirestoreTimestamp $value) { 293 | $this->fields[$fieldName] = [ 294 | 'timestampValue' => $value->parseValue() 295 | ]; 296 | } 297 | 298 | /** 299 | * @return \MrShan0\PHPFirestore\Fields\FirestoreTimestamp 300 | */ 301 | public function getTimestamp($value) 302 | { 303 | return new FirestoreTimestamp($value['timestampValue']); 304 | } 305 | 306 | /** 307 | * @return void 308 | */ 309 | public function setBoolean($fieldName, $value) 310 | { 311 | $this->fields[$fieldName] = [ 312 | 'booleanValue' => !!$value 313 | ]; 314 | } 315 | 316 | /** 317 | * @return boolean 318 | */ 319 | public function getBoolean($value) 320 | { 321 | return (bool) $value['booleanValue']; 322 | } 323 | 324 | /** 325 | * @return void 326 | */ 327 | public function setInteger($fieldName, $value) 328 | { 329 | $this->fields[$fieldName] = [ 330 | 'integerValue' => intval($value) 331 | ]; 332 | } 333 | 334 | /** 335 | * @return integer 336 | */ 337 | public function getInteger($value) 338 | { 339 | return intval($value['integerValue']); 340 | } 341 | 342 | /** 343 | * @return void 344 | */ 345 | public function setNull($fieldName) 346 | { 347 | $this->fields[$fieldName] = [ 348 | 'nullValue' => null, 349 | ]; 350 | } 351 | 352 | /** 353 | * @return null 354 | */ 355 | public function getNull($value) 356 | { 357 | return null; 358 | } 359 | 360 | /** 361 | * @return void 362 | */ 363 | public function setBytes($fieldName, FirestoreBytes $value) 364 | { 365 | $this->fields[$fieldName] = [ 366 | 'bytesValue' => FirestoreHelper::base64encode($value->getData()), 367 | ]; 368 | } 369 | 370 | /** 371 | * @param string $value 372 | * 373 | * @return \MrShan0\PHPFirestore\Fields\FirestoreBytes 374 | */ 375 | public function getBytes($value) 376 | { 377 | return new FirestoreBytes($value['bytesValue']); 378 | } 379 | 380 | /** 381 | * A placeholder to delete particular keys from database 382 | * 383 | * @param string $fieldName 384 | * 385 | * @return string 386 | */ 387 | public function setDelete($fieldName) 388 | { 389 | $this->fields[$fieldName] = new FirestoreDeleteAttribute; 390 | } 391 | 392 | /** 393 | * Delete keys in bulk 394 | * 395 | * @param string|array 396 | * 397 | * @return \MrShan0\PHPFirestore\FirestoreDocument 398 | */ 399 | public function deleteFields($fields) 400 | { 401 | is_array($fields) || $fields = [$fields]; 402 | 403 | foreach ($fields as $key) { 404 | $this->setDelete($key); 405 | } 406 | 407 | return $this; 408 | } 409 | 410 | /** 411 | * It will return value that Firestore needs to store. 412 | * 413 | * @throws \MrShan0\PHPFirestore\Exceptions\Client\FieldNotFound 414 | * 415 | * @param string $fieldName 416 | * @return mixed 417 | */ 418 | public function _getRawField($fieldName) 419 | { 420 | if (array_key_exists($fieldName, $this->fields)) { 421 | return reset($this->fields); 422 | } 423 | 424 | throw new FieldNotFound($fieldName); 425 | } 426 | 427 | /** 428 | * Check whether document has such field or not. 429 | * 430 | * @param string $fieldName 431 | * @return boolean 432 | */ 433 | public function has($fieldName) 434 | { 435 | return (array_key_exists($fieldName, $this->fields) && !$this->fields[$fieldName] instanceof FirestoreDeleteAttribute); 436 | } 437 | 438 | /** 439 | * Extract value from object by key 440 | * 441 | * @throws \MrShan0\PHPFirestore\Exceptions\Client\FieldNotFound 442 | * 443 | * @param string $fieldName 444 | * @return mixed 445 | */ 446 | public function get($fieldName) 447 | { 448 | if ($this->has($fieldName)) { 449 | $result = $this->castValue($this->fields[$fieldName]); 450 | 451 | return $result; 452 | } 453 | 454 | throw new FieldNotFound($fieldName); 455 | } 456 | 457 | /** 458 | * @return string 459 | */ 460 | public function toJson() 461 | { 462 | return FirestoreHelper::encode([ 463 | 'fields' => (object) FirestoreHelper::filter($this->fields) 464 | ]); 465 | } 466 | 467 | /** 468 | * @return array 469 | */ 470 | public function toArray() 471 | { 472 | $results = []; 473 | 474 | foreach ($this->fields as $key => $value) 475 | { 476 | $results[ $key ] = $this->castValue($value); 477 | } 478 | 479 | return $results; 480 | } 481 | 482 | /** 483 | * This is our DataMapper, which shapes data into appropriate form 484 | * 485 | * @param array $value 486 | * @return mixed 487 | */ 488 | private function castValue($value) 489 | { 490 | $parsedValue = ''; 491 | 492 | if ( array_key_exists('stringValue', $value ) ) { 493 | $parsedValue = $this->getString($value); 494 | } else if ( array_key_exists('arrayValue', $value ) ) { 495 | $parsedValue = $this->getArray($value); 496 | } else if ( array_key_exists('integerValue', $value ) ) { 497 | $parsedValue = $this->getInteger($value); 498 | } else if ( array_key_exists('doubleValue', $value ) ) { 499 | $parsedValue = $this->getDouble($value); 500 | } else if ( array_key_exists('booleanValue', $value ) ) { 501 | $parsedValue = $this->getBoolean($value); 502 | } else if ( array_key_exists('nullValue', $value ) ) { 503 | $parsedValue = $this->getNull($value); 504 | } else if ( array_key_exists('mapValue', $value ) ) { 505 | $parsedValue = $this->getObject($value); 506 | } else if ( array_key_exists('referenceValue', $value ) ) { 507 | $parsedValue = $this->getReference($value); 508 | } else if ( array_key_exists('geoPointValue', $value ) ) { 509 | $parsedValue = $this->getGeoPoint($value); 510 | } else if ( array_key_exists('timestampValue', $value ) ) { 511 | $parsedValue = $this->getTimestamp($value); 512 | } else if ( array_key_exists('bytesValue', $value ) ) { 513 | $parsedValue = $this->getBytes($value); 514 | } 515 | 516 | return $parsedValue; 517 | } 518 | 519 | } 520 | --------------------------------------------------------------------------------