diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..cd8eb86 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at http://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..bb6265e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# Path-based git attributes +# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html + +# Ignore all test and documentation with "export-ignore". +/.gitattributes export-ignore +/.gitignore export-ignore +/.travis.yml export-ignore +/phpunit.xml.dist export-ignore +/.scrutinizer.yml export-ignore +/tests export-ignore +/.editorconfig export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..808f8c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build +composer.lock +docs +vendor +coverage \ No newline at end of file diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 0000000..df16b68 --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,19 @@ +filter: + excluded_paths: [tests/*] + +checks: + php: + remove_extra_empty_lines: true + remove_php_closing_tag: true + remove_trailing_whitespace: true + fix_use_statements: + remove_unused: true + preserve_multiple: false + preserve_blanklines: true + order_alphabetically: true + fix_php_opening_tag: true + fix_linefeed: true + fix_line_ending: true + fix_identation_4spaces: true + fix_doc_comments: true + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d1ec2c8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +All notable changes to `Firestore PHP` will be documented in this file. + +## 1.0.0 - 2018-04-20 + +- initial release diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..0cee0a6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,51 @@ +# Contributing + +Contributions are **welcome** and will be fully **credited**. + +Please read and understand the contribution guide before creating an issue or pull request. + +## Etiquette + +This project is open source, and as such, the maintainers give their free time to build and maintain the source code +held within. They make the code freely available in the hope that it will be of use to other developers. It would be +extremely unfair for them to suffer abuse or anger for their hard work. + +Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the +world that developers are civilized and selfless people. + +It's the duty of the maintainer to ensure that all submissions to the project are of sufficient +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. + +## Viability + +When requesting or submitting new features, first consider whether it might be useful to others. Open +source projects are used by many developers, who may have entirely different needs to your own. Think about +whether or not your feature is likely to be used by other users of the project. + +## Procedure + +Before filing an issue: + +- Attempt to replicate the problem, to ensure that it wasn't a coincidental incident. +- Check to make sure your feature suggestion isn't already present within the project. +- Check the pull requests tab to ensure that the bug doesn't have a fix in progress. +- Check the pull requests tab to ensure that the feature isn't already in progress. + +Before submitting a pull request: + +- Check the codebase to ensure that your feature doesn't already exist. +- Check the pull requests to ensure that another person hasn't already submitted the feature or fix. + +## Requirements + +If the project maintainer has any additional requirements, you will find them listed here. + +- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. + +- **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option. + +- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. + +- **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. + +**Happy coding**! diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..e1cc8f0 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Ahsaan Muhammad Yousuf + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..67b0765 --- /dev/null +++ b/README.md @@ -0,0 +1,158 @@ +# Firestore Client for PHP + +[![Latest Version on Packagist](https://img.shields.io/packagist/v/ahsankhatri/firestore-php.svg?style=flat-square)](https://packagist.org/packages/ahsankhatri/firestore-php) +[![Total Downloads](https://img.shields.io/packagist/dt/ahsankhatri/firestore-php.svg?style=flat-square)](https://packagist.org/packages/ahsankhatri/firestore-php) +[![License](https://poser.pugx.org/ahsankhatri/firestore-php/license?format=flat-square)](https://packagist.org/packages/ahsankhatri/firestore-php) + +This package is totally based on [Firestore REST API](https://firebase.google.com/docs/firestore/use-rest-api) + +## Authentication / Generate API Key + +1) Visit [Google Cloud Firestore API](https://console.cloud.google.com/projectselector/apis/api/firestore.googleapis.com/overview) +2) Select your desired project. +3) Select `Credentials` from left menu and select `API Key` from Server key or `Create your own credentials` + +## Installation + +You can install the package via composer: + +```bash +composer require ahsankhatri/firestore-php +``` + +## Dependencies + +The bindings require the following extensions in order to work properly: + +- [`curl`](https://secure.php.net/manual/en/book.curl.php) +- [`json`](https://secure.php.net/manual/en/book.json.php) + +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. + +## Usage + +#### Initialization + +```php +$firestoreClient = new FireStoreApiClient('project-id', 'AIzaxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', [ + 'database' => '(default)', +]); +``` + +#### Adding a document + +```php +$firestoreClient->addDocument($collection, [ + 'booleanTrue' => true, + 'booleanFalse' => false, + 'null' => null, + 'string' => 'abc123', + 'integer' => 123456, + 'arrayRaw' => [ + 'string' => 'abc123', + ], + 'array' => new FireStoreArray([ + 'string' => 'abc123', + ]), + 'reference' => new FireStoreReference('/users/23'), + 'object' => new FireStoreObject(['nested1' => new FireStoreObject(['nested2' => new FireStoreObject(['nested3' => 'test'])])]), + 'timestamp' => new FireStoreTimestamp, + 'geopoint' => new FireStoreGeoPoint(1,1), +]); +``` + +**NOTE:** Pass third argument if you want your custom _document id_ to set else auto-id will generate it for you. + +Or + +```php +$document = new FireStoreDocument; +$document->setObject('sdf', new FireStoreObject(['nested1' => new FireStoreObject(['nested2' => new FireStoreObject(['nested3' => 'test'])])])); +$document->setBoolean('booleanTrue', true); +$document->setBoolean('booleanFalse', false); +$document->setNull('null', null); +$document->setString('string', 'abc123'); +$document->setInteger('integer', 123456); +$document->setArray('arrayRaw', ['string'=>'abc123']); +$document->setArray('arrayObject', new FireStoreArray(['string' => 'abc123'])); +$document->setTimestamp('timestamp', new FireStoreTimestamp); +$document->setGeoPoint('geopoint', new FireStoreGeoPoint(1.11,1.11)); +$firestoreClient->addDocument($collection, $document, 'customDocumentId'); +``` + +And.. + +```php +$document->fillValues([ + 'string' => 'abc123', + 'boolean' => true, +]); +``` + +#### Updating a document + +- Update existing document + +```php +$firestoreClient->updateDocument($collection, $documentId, [ + 'newFieldToAdd' => new FireStoreTimestamp(new DateTime('2018-04-20 15:00:00')), + 'existingFieldToRemove' => new FireStoreDeleteAttribute +], true); +``` + +**NOTE:** Passing 3rd argument as a boolean _true_ will indicate that document must exist and vice-versa. + +- Overwrite existing document + +```php +$firestoreClient->setDocument($collection, $documentId, [ + 'newFieldToAdd' => new FireStoreTimestamp(new DateTime('2018-04-20 15:00:00')), + 'existingFieldToRemove' => new FireStoreDeleteAttribute +], [ + 'exists' => true, // Indicate document must exist +]); +``` + +#### Deleting a document + +```php +$collection = 'collection/document/innerCollection'; +$firestoreClient->deleteDocument($collection, $documentId); +``` + +### TODO +- [x] Added delete attribute support. +- [x] Add Support for Object, Boolean, Null, String, Integer, Array, Timestamp, GeoPoint +- [ ] Add Exception Handling. +- [ ] List all documents and collections. +- [ ] Filters and pagination support. +- [ ] Transaction support. +- [ ] Indexes support. +- [ ] Entire collection delete support. + +### Testing + +``` bash +composer test +``` + +### Changelog + +Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. + +## Contributing + +Please see [CONTRIBUTING](CONTRIBUTING.md) for details. + +### Security + +If you discover any security related issues, please email ahsankhatri1992@gmail.com instead of using the issue tracker. + +## Credits + +- [Ahsaan Muhammad Yousuf](https://ahsaan.me) +- [All Contributors](../../contributors) + +## License + +The MIT License (MIT). Please see [License File](LICENSE.md) for more information. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b2c92e7 --- /dev/null +++ b/composer.json @@ -0,0 +1,46 @@ +{ + "name": "ahsankhatri/firestore-php", + "description": "Firestore PHP Client", + "keywords": [ + "php", + "firestore", + "firebase", + "google" + ], + "homepage": "https://github.com/ahsankhatri/firestore-php", + "license": "MIT", + "authors": [ + { + "name": "Ahsaan Muhammad Yousuf", + "email": "ahsankhatri1992@gmail.com", + "homepage": "https://ahsaan.me", + "role": "Developer" + } + ], + "require": { + "php": ">=5.6.6", + "ext-curl": "*", + "ext-json": "*" + }, + "require-dev": { + "phpunit/phpunit": "5.7" + }, + "autoload": { + "psr-4": { + "MrShan0\\PHPFirestore\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "MrShan0\\PHPFirestore\\Tests\\": "tests" + } + }, + "scripts": { + "test": "vendor/bin/phpunit", + "test-coverage": "vendor/bin/phpunit --coverage-html coverage" + + }, + "config": { + "sort-packages": true + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..a9493f3 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + tests + + + + + src/ + + + + + + + + + + diff --git a/src/Attributes/FireStoreDeleteAttribute.php b/src/Attributes/FireStoreDeleteAttribute.php new file mode 100644 index 0000000..af9675e --- /dev/null +++ b/src/Attributes/FireStoreDeleteAttribute.php @@ -0,0 +1,8 @@ +setData((array) $data); + } + } + + public function add($data) + { + array_push($this->data, $data); + + return $this; + } + + public function setData($data) + { + return $this->data = $data; + } + + public function getData() + { + return $this->data; + } + + public function parseValue() + { + $payload = [ + 'values' => [], + ]; + + foreach ($this->data as $data) { + $document = new FireStoreDocument; + call_user_func_array([$document, 'set'.ucfirst(FireStoreHelper::getType($data))], ['string', $data]); + $payload['values'][] = $document->get('string'); + } + + return $payload; + } +} diff --git a/src/Fields/FireStoreGeoPoint.php b/src/Fields/FireStoreGeoPoint.php new file mode 100644 index 0000000..491aef0 --- /dev/null +++ b/src/Fields/FireStoreGeoPoint.php @@ -0,0 +1,36 @@ +setData([$latitude, $longitude]); + } + + public function setData($data) + { + return $this->data = [ + 'latitude' => $data[0], + 'longitude' => $data[1], + ]; + } + + public function getData() + { + return $this->data; + } + + public function parseValue() + { + $value = $this->getData(); + + return $value; + } +} diff --git a/src/Fields/FireStoreObject.php b/src/Fields/FireStoreObject.php new file mode 100644 index 0000000..215c32b --- /dev/null +++ b/src/Fields/FireStoreObject.php @@ -0,0 +1,51 @@ +setData((array) $data); + } + } + + public function add($data) + { + array_push($this->data, $data); + + return $this; + } + + public function setData($data) + { + return $this->data = $data; + } + + public function getData() + { + return $this->data; + } + + public function parseValue() + { + $payload = [ + 'fields' => [], + ]; + + foreach ($this->data as $key => $data) { + $document = new FireStoreDocument; + call_user_func_array([$document, 'set'.ucfirst(FireStoreHelper::getType($data))], ['string', $data]); + $payload['fields'][$key] = $document->get('string'); + } + + return $payload; + } +} diff --git a/src/Fields/FireStoreReference.php b/src/Fields/FireStoreReference.php new file mode 100644 index 0000000..e27e699 --- /dev/null +++ b/src/Fields/FireStoreReference.php @@ -0,0 +1,40 @@ +setData($data); + } + + public function setData($data) + { + $this->data = FireStoreHelper::normalizeCollection($data); + } + + public function getData() + { + return $this->data; + } + + public function parseValue() + { + $value = + 'projects/' . + FireStoreApiClient::getConfig('project') . + '/databases/' . + FireStoreApiClient::getConfig('database') . + '/documents/' . + $this->getData(); + + return $value; + } +} diff --git a/src/Fields/FireStoreTimestamp.php b/src/Fields/FireStoreTimestamp.php new file mode 100644 index 0000000..6ea6bf9 --- /dev/null +++ b/src/Fields/FireStoreTimestamp.php @@ -0,0 +1,43 @@ +setData($data); + } + + public function setData($data) + { + return $this->data = $data; + } + + public function getData() + { + return $this->data; + } + + public function parseValue() + { + $value = $this->getData(); + + if ( $value instanceof DateTime && method_exists($value, 'format') ) { + return $value->format(self::DEFAULT_FORMAT); + } + + return $value; + } +} diff --git a/src/FireStoreApiClient.php b/src/FireStoreApiClient.php new file mode 100644 index 0000000..ed3dbe7 --- /dev/null +++ b/src/FireStoreApiClient.php @@ -0,0 +1,277 @@ +project = $project; + $this->apiKey = $apiKey; + + $this->setConfig(array_merge($config, [ + 'database' => '(default)', + 'project' => $project, + ])); + } + + public static function setConfig($configs) + { + self::$config = $configs; + } + + public static function getConfig($key) + { + return array_key_exists($key, self::$config) ? self::$config[$key] : null; + } + + public function getDocument($collectionName, $documentId=null) + { + $documentPath = 'documents/' . FireStoreHelper::normalizeCollection($collectionName . (null === $documentId ? "/$documentId" : '')); + if ($response = $this->get($documentPath)) { + $object = FireStoreHelper::decode($response); + + if ( FireStoreDocument::isValidDocument($object) ) { + return new FireStoreDocument($object); + } else { + throw new \Exception('Document does not exist.', FireStoreErrorCodes::DOCUMENT_NOT_FOUND); + } + } + + throw new \Exception('Error while parsing response from FireStore', FireStoreErrorCodes::UNABLE_TO_RESOLVE_REQUEST); + } + + public function batchGet(array $documents, $params=[]) + { + $payload = []; + foreach ($documents as $document) { + $payload[] = 'projects/' . $this->project . '/' . 'databases/(default)/documents/' . $document; + } + + $response = $this->post( + 'documents:batchGet', + $params, + FireStoreHelper::encode([ + 'documents' => $payload + ]) + ); + + $result = []; + foreach (FireStoreHelper::decode($response) as $document) { + $result[] = new FireStoreDocument($document['found']); + } + + return $result; + } + + public function addDocument($collectionName, $payload, $newDocumentId=null, $params=[]) + { + + if ( !($payload instanceof FireStoreDocument) ) { + $document = new FireStoreDocument(); + } else { + $document = $payload; + } + + // Set document id + if ( $newDocumentId ) { + $params['documentId'] = $newDocumentId; + } + + if ( is_array($payload) ) { + $document->fillValues($payload); + } + + return $this->post( + "documents/$collectionName", + $params, + $document->toJson() + ); + } + + public function updateDocument($collectionName, $documentId, $payload, $documentExists=null, array $params=[]) + { + $document = new FireStoreDocument(); + + if ($payload instanceof FireStoreDocument) { + $document = $payload; + } elseif ( is_array($payload) ) { + $document->fillValues($payload); + } + + if ($documentExists !== null) { + $params['currentDocument.exists'] = !!$documentExists; + } + + $params['updateMask.fieldPaths'] = array_unique(array_keys($document->toArray())); + + return $this->patch( + "documents/$collectionName/$documentId", + $params, + $document->toJson() + ); + } + + public function setDocument($collectionName, $documentId, $payload, array $params=[]) + { + + $document = new FireStoreDocument(); + + if ($payload instanceof FireStoreDocument) { + $document = $payload; + } elseif ( is_array($payload) ) { + $document->fillValues($payload); + } + + if ( array_key_exists('exists', $params) ) { + $params['currentDocument.exists'] = !!$params['exists']; + unset($params['exists']); + } + + if ( array_key_exists('merge', $params) && $params['merge'] === true ) { + $params['updateMask.fieldPaths'] = array_unique(array_keys($document->toArray())); + unset($params['merge']); + } + + return $this->patch( + "documents/$collectionName/$documentId", + $params, + $document->toJson() + ); + } + + public function deleteDocument($collectionName, $documentId) + { + return $this->delete( + "documents/$collectionName/$documentId", [] + ); + } + + /** + * Compile final url to hit service. + * + * @return string + */ + private function constructUrl($method, $params=null) + { + $params = is_array($params) ? $params : []; + $builtQuery = (count($params) ? '&' . http_build_query($params) : ''); + + if ( array_key_exists('updateMask.fieldPaths', $params) ) { + $builtQuery = preg_replace('/%5B\d%5D/', '', $builtQuery); + } + + return ( + $this->apiRoot . 'projects/' . $this->project . '/' . + 'databases/(default)/' . $method . '?key=' . $this->apiKey . $builtQuery + ); + } + + private function get($method, $params=null) + { + $curl = curl_init(); + curl_setopt_array($curl, array( + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_URL => $this->constructUrl($method, $params), + CURLOPT_USERAGENT => 'cURL' + )); + $response = curl_exec($curl); + curl_close($curl); + return $response; + } + + private function post($method, $params, $postBody) + { + $curl = curl_init(); + curl_setopt_array($curl, array( + CURLOPT_RETURNTRANSFER => true, + CURLOPT_URL => $this->constructUrl($method, $params), + CURLOPT_HTTPHEADER => array('Content-Type: application/json', 'Content-Length: ' . strlen($postBody)), + CURLOPT_USERAGENT => 'cURL', + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $postBody + )); + $response = curl_exec($curl); + curl_close($curl); + return $response; + } + + private function put($method, $params, $postBody) + { + $curl = curl_init(); + curl_setopt_array($curl, array( + CURLOPT_RETURNTRANSFER => true, + CURLOPT_CUSTOMREQUEST => 'PUT', + CURLOPT_HTTPHEADER => array('Content-Type: application/json', 'Content-Length: ' . strlen($postBody)), + CURLOPT_URL => $this->constructUrl($method, $params), + CURLOPT_USERAGENT => 'cURL', + CURLOPT_POSTFIELDS => $postBody + )); + $response = curl_exec($curl); + curl_close($curl); + return $response; + } + + private function patch($method, $params, $postBody) + { + $curl = curl_init(); + curl_setopt_array($curl, array( + CURLOPT_RETURNTRANSFER => true, + CURLOPT_CUSTOMREQUEST => 'PATCH', + CURLOPT_HTTPHEADER => array('Content-Type: application/json', 'Content-Length: ' . strlen($postBody)), + CURLOPT_URL => $this->constructUrl($method, $params), + CURLOPT_USERAGENT => 'cURL', + CURLOPT_POSTFIELDS => $postBody + )); + $response = curl_exec($curl); + curl_close($curl); + return $response; + } + + private function delete($method, $params) + { + $curl = curl_init(); + curl_setopt_array($curl, array( + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_CUSTOMREQUEST => 'DELETE', + CURLOPT_URL => $this->constructUrl($method, $params), + CURLOPT_USERAGENT => 'cURL' + )); + $response = curl_exec($curl); + curl_close($curl); + return $response; + } + +} diff --git a/src/FireStoreDocument.php b/src/FireStoreDocument.php new file mode 100644 index 0000000..41c6c78 --- /dev/null +++ b/src/FireStoreDocument.php @@ -0,0 +1,267 @@ +name = $object['name']; + $this->createTime = $object['createTime']; + $this->updateTime = $object['updateTime']; + + foreach ($object['fields'] as $fieldName => $value) { + $this->fields[ $fieldName ] = $value; + } + } + } + + public function fillValues(array $payload) + { + foreach ($payload as $key => $value) { + call_user_func_array([$this, 'set'.ucfirst(FireStoreHelper::getType($value))], [$key, $value]); + } + + return $this; + } + + /** + * To determine is valid document when fetched from firestore. + * Won't work wiht `new FireStoreDocument` + * + * @param object + * @return boolean + */ + public static function isValidDocument($object) + { + return ( array_key_exists('name', $object) && array_key_exists('fields', $object) ); + } + + public function getName() { + return $this->name; + } + + public function setName($value) { + return $this->name = $value; + } + + public function setString($fieldName, $value) { + $this->fields[$fieldName] = [ + 'stringValue' => $value + ]; + } + + public function getString($value) + { + return strval($value['stringValue']); + } + + public function setDouble($fieldName, $value) { + $this->fields[$fieldName] = [ + 'doubleValue' => floatval($value) + ]; + } + + public function setArray($fieldName, $value) { + + if ( !$value instanceof FireStoreArray ) { + $payload = new FireStoreArray; + foreach ($value as $row) { + $payload->add( $row ); + } + + $value = $payload; + } + + $this->fields[$fieldName] = [ + 'arrayValue' => $value->parseValue() + ]; + } + + public function getArray($value) + { + $results = []; + + foreach ($value['arrayValue']['values'] as $key => $value) { + $results[$key] = $this->castValue($value); + } + + return $results; + } + + public function setObject($fieldName, $value) { + + if ( !$value instanceof FireStoreObject ) { + $payload = new FireStoreObject; + foreach ($value as $row) { + $payload->add( $row ); + } + + $value = $payload; + } + + $this->fields[$fieldName] = [ + 'mapValue' => $value->parseValue() + ]; + } + + public function getObject($value) { + + $results = []; + + foreach ($value['mapValue']['fields'] as $key => $value) { + $results[$key] = $this->castValue($value); + } + + return new FireStoreObject([$results]); + } + + public function setReference($fieldName, FireStoreReference $value) { + $this->fields[$fieldName] = [ + 'referenceValue' => $value->parseValue() + ]; + } + + public function getReference($value) + { + return new FireStoreReference( substr($value['referenceValue'], strpos($value['referenceValue'], '/documents/')+10) ); + } + + public function setGeoPoint($fieldName, FireStoreGeoPoint $value) { + $this->fields[$fieldName] = [ + 'geoPointValue' => $value->parseValue() + ]; + } + + public function getGeoPoint($value) + { + return new FireStoreGeoPoint($value['geoPointValue']['latitude'], $value['geoPointValue']['longitude']); + } + + public function setTimestamp($fieldName, FireStoreTimestamp $value) { + $this->fields[$fieldName] = [ + 'timestampValue' => $value->parseValue() + ]; + } + + public function getTimestamp($value) + { + return new FireStoreTimestamp($value['timestampValue']); + } + + public function setBoolean($fieldName, $value) { + $this->fields[$fieldName] = [ + 'booleanValue' => !!$value + ]; + } + + public function getBoolean($value) { + return (bool) $value['booleanValue']; + } + + public function setInteger($fieldName, $value) { + $this->fields[$fieldName] = [ + 'integerValue' => intval($value) + ]; + } + + public function getInteger($value) { + return intval($value['integerValue']); + } + + public function setNull($fieldName) { + $this->fields[$fieldName] = [ + 'nullValue' => null, + ]; + } + + public function getNull($value) { + return null; + } + + public function setDelete($fieldName, FireStoreDeleteAttribute $value) { + $this->fields[$fieldName] = $value; + } + + /** + * A placeholder to delete particular keys from database + * + * @param string|array + * @return \MrShan0\PHPFirestore\FireStoreDocument + */ + public function deleteFields($fields) + { + is_array($fields) || $fields = [$fields]; + foreach ($fields as $key) { + $this->setDelete($key, new FireStoreDeleteAttribute); + } + + return $this; + } + + public function get($fieldName) { + if (array_key_exists($fieldName, $this->fields)) { + return reset($this->fields); + } + throw new Exception('No such field'); + } + + public function toJson() { + return FireStoreHelper::encode([ + 'fields' => (object) FireStoreHelper::filter($this->fields) + ]); + } + + public function toArray() { + $results = []; + + foreach ($this->fields as $key => $value) { + $results[ $key ] = $this->castValue($value); + } + + return $results; + } + + private function castValue($value) + { + $parsedValue = ''; + + if ( array_key_exists('stringValue', $value ) ) { + $parsedValue = $this->getString($value); + } else if ( array_key_exists('arrayValue', $value ) ) { + $parsedValue = $this->getArray($value); + } else if ( array_key_exists('integerValue', $value ) ) { + $parsedValue = $this->getInteger($value); + } else if ( array_key_exists('booleanValue', $value ) ) { + $parsedValue = $this->getBoolean($value); + } else if ( array_key_exists('nullValue', $value ) ) { + $parsedValue = $this->getNull($value); + } else if ( array_key_exists('mapValue', $value ) ) { + $parsedValue = $this->getObject($value); + } else if ( array_key_exists('referenceValue', $value ) ) { + $parsedValue = $this->getReference($value); + } else if ( array_key_exists('geoPointValue', $value ) ) { + $parsedValue = $this->getGeoPoint($value); + } else if ( array_key_exists('timestampValue', $value ) ) { + $parsedValue = $this->getTimestamp($value); + } + + return $parsedValue; + } + +} diff --git a/src/FireStoreErrorCodes.php b/src/FireStoreErrorCodes.php new file mode 100644 index 0000000..0b1a248 --- /dev/null +++ b/src/FireStoreErrorCodes.php @@ -0,0 +1,9 @@ +assertTrue(true); + } +}