├── src ├── Exception │ └── MissingEncoderException.php ├── Integration │ └── Laravel │ │ ├── Firebase.php │ │ └── FirebaseServiceProvider.php ├── Normalizer │ ├── NormalizerInterface.php │ ├── StringNormalizer.php │ ├── AssocArrayNormalizer.php │ ├── AbstractNormalizer.php │ ├── ObjectNormalizer.php │ └── SimpleArrayNormalizer.php ├── FirebaseMethods.php ├── Event │ └── RequestsBatchedEvent.php ├── Configurable.php ├── Criteria.php ├── Autoloader.php ├── Auth │ └── TokenGenerator.php └── Firebase.php ├── examples ├── basic.php ├── debug.php ├── complex_query.php ├── normalizer.php ├── token.php ├── injection.php ├── pooling.php └── nocomposer.php ├── .gitignore ├── tests ├── stubs │ ├── DummyConfigurable.php │ ├── DummyNormalizer.php │ └── DummyEncoder.php ├── traits │ └── ProtectedCaller.php ├── ConfigurableTest.php ├── TokenGeneratorTest.php └── FirebaseTest.php ├── .travis.yml ├── composer.json ├── LICENSE ├── phpunit.xml.dist ├── .scrutinizer.yml └── README.md /src/Exception/MissingEncoderException.php: -------------------------------------------------------------------------------- 1 | get($argv[3])); 10 | -------------------------------------------------------------------------------- /examples/debug.php: -------------------------------------------------------------------------------- 1 | true]); 8 | 9 | print_r($fb->get($argv[3])); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | vendor/ 3 | .idea/ 4 | coverage.clover 5 | # Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file 6 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file 7 | # composer.lock 8 | -------------------------------------------------------------------------------- /examples/complex_query.php: -------------------------------------------------------------------------------- 1 | get($argv[3], new Criteria('$key', ['equalTo' => $argv[4]]))); -------------------------------------------------------------------------------- /src/Integration/Laravel/Firebase.php: -------------------------------------------------------------------------------- 1 | normalize(new StringNormalizer())->get($argv[3])); -------------------------------------------------------------------------------- /src/Normalizer/NormalizerInterface.php: -------------------------------------------------------------------------------- 1 | config['a'] = 'b'; 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /tests/stubs/DummyNormalizer.php: -------------------------------------------------------------------------------- 1 | getBody(); 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /src/Normalizer/AssocArrayNormalizer.php: -------------------------------------------------------------------------------- 1 | json(); 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /tests/stubs/DummyEncoder.php: -------------------------------------------------------------------------------- 1 | name; 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/Normalizer/ObjectNormalizer.php: -------------------------------------------------------------------------------- 1 | json(array('object' => true)); 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /src/Normalizer/SimpleArrayNormalizer.php: -------------------------------------------------------------------------------- 1 | json()); 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /examples/token.php: -------------------------------------------------------------------------------- 1 | generateToken(array(), array('admin' => true))); 13 | 14 | print_r($fb->get($argv[3])); -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 5.5 6 | - 5.4 7 | - hhvm 8 | 9 | before_script: 10 | - composer self-update 11 | - composer install --prefer-source --no-interaction --dev 12 | 13 | script: phpunit 14 | 15 | matrix: 16 | fast_finish: true 17 | allow_failures: 18 | - php: hhvm 19 | 20 | after_script: 21 | - wget https://scrutinizer-ci.com/ocular.phar 22 | - php ocular.phar code-coverage:upload --format=php-clover coverage.clover 23 | -------------------------------------------------------------------------------- /examples/injection.php: -------------------------------------------------------------------------------- 1 | $options['base_url'] 11 | ]); 12 | }); 13 | 14 | $fb = Firebase::initialize($argv[2], $argv[1]); 15 | 16 | print_r($fb->get($argv[3])); 17 | -------------------------------------------------------------------------------- /src/Event/RequestsBatchedEvent.php: -------------------------------------------------------------------------------- 1 | requests = $requests; 13 | } 14 | 15 | public function getRequests() 16 | { 17 | return $this->requests; 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /tests/traits/ProtectedCaller.php: -------------------------------------------------------------------------------- 1 | setAccessible(true); 16 | return $reflectionMethod->invokeArgs($object, $args); 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /examples/pooling.php: -------------------------------------------------------------------------------- 1 | batch(function ($fb) { 11 | 12 | /** @var Firebase $fb */ 13 | for($i = 0; $i < 100; $i++) { 14 | $fb->push('list', $i); 15 | } 16 | 17 | }); 18 | 19 | //pooling the requests and executing async 20 | $pool = new Pool($fb->getClient(), $requests); 21 | $pool->wait(); 22 | 23 | //the pool accepts an optional array as third argument 24 | //for more info have a look at: http://docs.guzzlephp.org/en/latest/clients.html?highlight=pool -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eelkevdbos/firebase-php", 3 | "description": "Firebase php wrapper for REST API", 4 | "license": "MIT", 5 | "keywords": ["firebase", "REST", "wrapper"], 6 | "authors": [{ 7 | "name": "Eelke van den Bos", 8 | "email": "eelkevdbos@gmail.com" 9 | }], 10 | "require": { 11 | "php": ">=5.4", 12 | "firebase/php-jwt": "~2.0", 13 | "guzzlehttp/guzzle": "5.*", 14 | "illuminate/support": ">=4.0" 15 | }, 16 | "require-dev": { 17 | "mockery/mockery": "0.9.1", 18 | "phpunit/phpunit": "4.1.1" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "Firebase\\": "src" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Integration/Laravel/FirebaseServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->bind('firebase', function ($app) { 11 | 12 | /** @var \Illuminate\Contracts\Config\Repository $config */ 13 | $config = $app['config']; 14 | 15 | /** @var \Illuminate\Contracts\Foundation\Application $app */ 16 | return $app->make('Firebase\Firebase', [ 17 | $config->get('services.firebase'), 18 | $app->make('GuzzleHttp\Client') 19 | ]); 20 | 21 | }, true); 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Eelke van den Bos 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 all 13 | 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 THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /examples/nocomposer.php: -------------------------------------------------------------------------------- 1 | register(); 14 | 15 | //load guzzle dependency 16 | $loader->addNamespace('GuzzleHttp\\Stream\\', $vendor . '/guzzlehttp/streams/src'); 17 | $loader->addNamespace('GuzzleHttp\\Ring\\', $vendor . '/guzzlehttp/ringphp/src'); 18 | $loader->addNamespace('GuzzleHttp\\', $vendor . '/guzzlehttp/guzzle/src'); 19 | 20 | //load php-jwt, not namespaced 21 | require $vendor . '/firebase/php-jwt/Firebase/PHP-JWT/Authentication/JWT.php'; 22 | 23 | //load react dependencies 24 | $loader->addNamespace('React\\Promise\\', $vendor . '/react/promise/src'); 25 | require $vendor . '/react/promise/src/functions.php'; 26 | 27 | //load firebase repository 28 | $loader->addNamespace('Firebase\\', dirname(__DIR__) . '/src'); 29 | 30 | $fb = Firebase::initialize($argv[2], $argv[1]); 31 | 32 | print_r($fb->get($argv[3])); 33 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | 18 | 19 | ./tests/ 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ./src 28 | 29 | ./vendor 30 | ./tests 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/ConfigurableTest.php: -------------------------------------------------------------------------------- 1 | configurable = new DummyConfigurable(); 12 | } 13 | 14 | public function testGetOption() 15 | { 16 | $this->assertEquals('b', $this->configurable->getOption('a')); 17 | } 18 | 19 | public function testSetOption() 20 | { 21 | $this->configurable->setOption('a', 'c'); 22 | $this->assertEquals('c', $this->configurable->getOption('a')); 23 | } 24 | 25 | public function testSetOptions() 26 | { 27 | $this->configurable->setOptions(['c' => 'd']); 28 | $this->assertEquals('d', $this->configurable->getOption('c')); 29 | } 30 | 31 | public function testGetOptions() 32 | { 33 | $input = array('c' => 'd', 'e' => 'f'); 34 | $this->configurable->setOptions($input); 35 | $this->assertEquals($input, $this->configurable->getOptions()); 36 | } 37 | 38 | public function testMergeOptions() 39 | { 40 | $input = array('c' => 'd', 'e' => 'f'); 41 | $this->configurable->mergeOptions($input); 42 | $output = $input + array('a' => 'b'); 43 | $this->assertEquals($output, $this->configurable->getOptions()); 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/Configurable.php: -------------------------------------------------------------------------------- 1 | config = $options; 24 | return $this; 25 | } 26 | 27 | /** 28 | * Merge all configuration options at once 29 | * @param array $options 30 | * @return $this 31 | */ 32 | public function mergeOptions($options) 33 | { 34 | $this->config = array_merge($this->config, $options); 35 | return $this; 36 | } 37 | 38 | /** 39 | * Get all configuration options at once 40 | * @return array 41 | */ 42 | public function getOptions() 43 | { 44 | return $this->config; 45 | } 46 | 47 | /** 48 | * Setter for individual configuration option 49 | * @param string $key 50 | * @param mixed $value 51 | * @return $this 52 | */ 53 | public function setOption($key, $value) 54 | { 55 | $this->config[$key] = $value; 56 | return $this; 57 | } 58 | 59 | /** 60 | * Getter for individual configuration option 61 | * @param string $key 62 | * @param mixed $defaultValue 63 | * @return mixed 64 | */ 65 | public function getOption($key, $defaultValue = null) 66 | { 67 | if(!isset($this->config[$key])) { 68 | return $defaultValue; 69 | } 70 | return $this->config[$key]; 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /src/Criteria.php: -------------------------------------------------------------------------------- 1 | orderBy = static::addDoubleQuotes($orderBy); 34 | $this->params = $params; 35 | } 36 | 37 | /** 38 | * @param array $params 39 | * @return static 40 | */ 41 | public static function orderByKey($params = []) 42 | { 43 | return new static('$key', $params); 44 | } 45 | 46 | /** 47 | * @param array $params 48 | * @return static 49 | */ 50 | public static function orderByValue($params = []) 51 | { 52 | return new static('$value', $params); 53 | } 54 | 55 | /** 56 | * @param array $params 57 | * @return static 58 | */ 59 | public static function orderByPriority($params = []) 60 | { 61 | return new static('$priority', $params); 62 | } 63 | 64 | /** 65 | * @return string 66 | */ 67 | public function getOrderBy() 68 | { 69 | return $this->orderBy; 70 | } 71 | 72 | /** 73 | * @return array 74 | */ 75 | public function getParams() 76 | { 77 | return $this->params; 78 | } 79 | 80 | /** 81 | * Adds double quotes where necessary 82 | * @param $input 83 | * @return string 84 | */ 85 | public static function addDoubleQuotes($input) 86 | { 87 | $output = $input; 88 | 89 | if (substr($input, 0, 1) !== '"') { 90 | $output = '"' . $output; 91 | } 92 | 93 | if (substr(strrev($input), 0, 1) !== '"') { 94 | $output = $output . '"'; 95 | } 96 | 97 | return $output; 98 | } 99 | 100 | } -------------------------------------------------------------------------------- /tests/TokenGeneratorTest.php: -------------------------------------------------------------------------------- 1 | generator = new \Firebase\Auth\TokenGenerator($this->secret, new DummyEncoder()); 26 | } 27 | 28 | public function testGenerateToken() 29 | { 30 | $token = $this->generator->generateToken(array(), array('issuedAt' => 1)); 31 | 32 | $this->assertTrue(is_string($token)); 33 | 34 | //ensure the same holds for the resolver 35 | \Firebase\Auth\TokenGenerator::$encoderResolver = function () { 36 | return 'JWT'; 37 | }; 38 | 39 | $tokenViaResolver = $this->generator->generateToken(array(), array('issuedAt' => 1)); 40 | 41 | $this->assertTrue(is_string($tokenViaResolver)); 42 | } 43 | 44 | public function testInvalidResolver() 45 | { 46 | $this->setExpectedException('Firebase\Exception\MissingEncoderException'); 47 | 48 | //instantiate class without encode member function 49 | \Firebase\Auth\TokenGenerator::$encoderResolver = function () { 50 | return new StdClass; 51 | }; 52 | 53 | $this->generator->generateToken(array(), array('issuedAt' => 1)); 54 | } 55 | 56 | /** 57 | * Expect claims to be build by their respective builders 58 | */ 59 | public function testBuildClaim() 60 | { 61 | $version = self::callProtected($this->generator, 'buildClaim', array('version', 1)); 62 | $this->assertEquals($version, array('v', 1)); 63 | 64 | $issuedAt = self::callProtected($this->generator, 'buildClaim', array('issuedAt', 1)); 65 | $this->assertEquals($issuedAt, array('iat', 1)); 66 | 67 | $data = self::callProtected($this->generator, 'buildClaim', array('data', 1)); 68 | $this->assertEquals($data, array('d', 1)); 69 | 70 | $notBefore = self::callProtected($this->generator, 'buildClaim', array('notBefore', 1)); 71 | $this->assertEquals($notBefore, array('nbf', 1)); 72 | 73 | $expires = self::callProtected($this->generator, 'buildClaim', array('expires', 1)); 74 | $this->assertEquals($expires, array('exp', 1)); 75 | 76 | $admin = self::callProtected($this->generator, 'buildClaim', array('admin', 1)); 77 | $this->assertEquals($admin, array('admin', true)); 78 | 79 | $debug = self::callProtected($this->generator, 'buildClaim', array('debug', 0)); 80 | $this->assertEquals($debug, array('debug', false)); 81 | } 82 | 83 | public function testBuildDataClaim() 84 | { 85 | $data = self::callProtected($this->generator, 'buildClaim', array('data', array('data' => true))); 86 | $this->assertEquals($data, array('d', array('data' => true))); 87 | 88 | $this->setExpectedException('UnexpectedValueException'); 89 | self::callProtected($this->generator, 'buildClaim', array('data', "\xB1\x31")); 90 | } 91 | 92 | public function testGetValidTimestamp() 93 | { 94 | $dateTime = new DateTime(); 95 | 96 | $validDateTime = self::callProtected($this->generator, 'getValidTimestamp', array($dateTime)); 97 | $this->assertEquals($validDateTime, $dateTime->getTimestamp()); 98 | 99 | $validUnix = self::callProtected($this->generator, 'getValidTimestamp', array(127000)); 100 | $this->assertEquals(127000, $validUnix); 101 | 102 | $this->setExpectedException('UnexpectedValueException'); 103 | $invalidTime = self::callProtected($this->generator, 'getValidTimestamp', array(new stdClass())); 104 | } 105 | 106 | /** 107 | * Expect invalid options not to be built into a claim 108 | */ 109 | public function testInvalidClaim() 110 | { 111 | $result = self::callProtected($this->generator, 'buildClaims', array(array(), array('unknown'))); 112 | $this->assertEquals(3, count($result)); 113 | } 114 | 115 | } -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | tools: 2 | external_code_coverage: true 3 | 4 | checks: 5 | php: 6 | argument_type_checks: true 7 | assignment_of_null_return: true 8 | avoid_aliased_php_functions: true 9 | avoid_closing_tag: true 10 | avoid_conflicting_incrementers: true 11 | avoid_corrupting_byteorder_marks: true 12 | avoid_duplicate_types: true 13 | avoid_fixme_comments: true 14 | avoid_perl_style_comments: true 15 | avoid_superglobals: true 16 | avoid_todo_comments: true 17 | avoid_unnecessary_concatenation: true 18 | avoid_usage_of_logical_operators: true 19 | avoid_useless_overridden_methods: true 20 | blank_line_after_namespace_declaration: true 21 | catch_class_exists: true 22 | classes_in_camel_caps: true 23 | closure_use_modifiable: true 24 | closure_use_not_conflicting: true 25 | code_rating: true 26 | deadlock_detection_in_loops: true 27 | deprecated_code_usage: true 28 | duplication: true 29 | encourage_shallow_comparison: true 30 | encourage_single_quotes: true 31 | ensure_lower_case_builtin_functions: true 32 | fix_identation_4spaces: true 33 | fix_line_ending: true 34 | fix_linefeed: true 35 | fix_php_opening_tag: true 36 | fix_use_statements: true 37 | foreach_traversable: true 38 | function_body_start_on_new_line: true 39 | function_in_camel_caps: true 40 | instanceof_class_exists: true 41 | lowercase_basic_constants: true 42 | lowercase_php_keywords: true 43 | method_calls_on_non_object: true 44 | newline_at_end_of_file: true 45 | no_commented_out_code: true 46 | no_debug_code: true 47 | no_duplicate_arguments: true 48 | no_else_if_statements: true 49 | no_empty_statements: true 50 | no_error_suppression: true 51 | no_eval: true 52 | no_exit: true 53 | no_global_keyword: true 54 | no_goto: true 55 | no_mixed_inline_html: true 56 | no_non_implemented_abstract_methods: true 57 | no_property_on_interface: true 58 | no_short_open_tag: true 59 | no_space_around_object_operator: true 60 | no_space_before_semicolon: true 61 | no_space_inside_cast_operator: true 62 | no_trailing_whitespace: true 63 | no_underscore_prefix_in_methods: true 64 | no_underscore_prefix_in_properties: true 65 | no_unnecessary_final_modifier: true 66 | no_unnecessary_function_call_in_for_loop: true 67 | no_unnecessary_if: true 68 | non_commented_empty_catch_block: true 69 | one_class_per_file: true 70 | optional_parameters_at_the_end: true 71 | overriding_private_members: true 72 | param_doc_comment_if_not_inferrable: true 73 | parameter_doc_comments: true 74 | parameter_non_unique: true 75 | parameters_in_camelcaps: true 76 | php5_style_constructor: true 77 | phpunit_assertions: true 78 | precedence_in_conditions: true 79 | precedence_mistakes: true 80 | prefer_unix_line_ending: true 81 | properties_in_camelcaps: true 82 | property_assignments: true 83 | psr2_class_declaration: true 84 | psr2_control_structure_declaration: true 85 | psr2_switch_declaration: true 86 | remove_php_closing_tag: true 87 | remove_trailing_whitespace: true 88 | require_php_tag_first: true 89 | require_scope_for_methods: true 90 | require_scope_for_properties: true 91 | return_doc_comment_if_not_inferrable: true 92 | return_doc_comments: true 93 | security_vulnerabilities: true 94 | side_effects_or_types: true 95 | simplify_boolean_return: true 96 | single_namespace_per_use: true 97 | switch_fallthrough_commented: true 98 | too_many_arguments: true 99 | unreachable_code: true 100 | unused_methods: true 101 | unused_parameters: true 102 | unused_properties: true 103 | unused_variables: true 104 | uppercase_constants: true 105 | use_self_instead_of_fqcn: true 106 | use_statement_alias_conflict: true 107 | useless_calls: true 108 | variable_existence: true 109 | verify_access_scope_valid: true 110 | verify_argument_usable_as_reference: true 111 | verify_property_names: true 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | firebase-php 2 | ============ 3 | 4 | [![Build Status](https://travis-ci.org/eelkevdbos/firebase-php.svg?branch=master)](https://travis-ci.org/eelkevdbos/firebase-php) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/eelkevdbos/firebase-php/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/eelkevdbos/firebase-php/?branch=master)[![Code Coverage](https://scrutinizer-ci.com/g/eelkevdbos/firebase-php/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/eelkevdbos/firebase-php/?branch=master) 5 | 6 | Firebase php wrapper for REST API 7 | 8 | ##Prerequisites 9 | - PHP >= 5.4 10 | - Firebase >= 1.1.1 11 | - Composer (recommended, not required) 12 | 13 | ## Installation using composer (recommended) 14 | Set your projects minimum stability to `dev` in `composer.json`. This is caused by the PHP-JWT dependency. After updating the composer.json file, simply execute: `composer require eelkevdbos/firebase-php dev-master` 15 | 16 | ##Installation without composer 17 | For a vanilla install, the following dependencies should be downloaded: 18 | - firebase/php-jwt [github](https://github.com/firebase/php-jwt/releases/tag/v1.0.0) 19 | - guzzlehttp/guzzle [github](https://github.com/guzzle/guzzle/releases/tag/5.0.3) 20 | 21 | Loading the dependencies can be achieved by using any [PSR-4 autoloader](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader-examples.md). 22 | 23 | ## Basic Usage 24 | By setting your firebase secret as token, you gain superuser access to firebase. 25 | 26 | ```php 27 | 28 | use Firebase\Firebase; 29 | 30 | $fb = Firebase::initialize(YOUR_FIREBASE_URL, YOUR_FIREBASE_SECRET); 31 | 32 | //or set your own implementation of the ClientInterface as second parameter of the regular constructor 33 | $fb = new Firebase([ 'base_url' => YOUR_FIREBASE_BASE_URL, 'token' => YOUR_FIREBASE_SECRET ], new GuzzleHttp\Client()); 34 | 35 | //retrieve a node 36 | $nodeGetContent = $fb->get('/node/path'); 37 | 38 | //set the content of a node 39 | $nodeSetContent = $fb->set('/node/path', array('data' => 'toset')); 40 | 41 | //update the content of a node 42 | $nodeUpdateContent = $fb->update('/node/path', array('data' => 'toupdate')); 43 | 44 | //delete a node 45 | $nodeDeleteContent = $fb->delete('/node/path'); 46 | 47 | //push a new item to a node 48 | $nodePushContent = $fb->push('/node/path', array('name' => 'item on list')); 49 | 50 | ``` 51 | 52 | ## Advanced Usage 53 | For more finegrained authentication, have a look at the [security rules](https://www.firebase.com/docs/security/security-rules.html). Using the token generator allows you to make use of the authentication services supplied by Firebase. 54 | 55 | ```php 56 | 57 | use Firebase\Firebase; 58 | use Firebase\Auth\TokenGenerator; 59 | 60 | $tokenGenerator = new TokenGenerator(YOUR_FIREBASE_SECRET); 61 | 62 | $token = $tokenGenerator->generateToken(['email' => 'test@example.com']) 63 | 64 | $fb = Firebase::initialize(YOUR_FIREBASE_BASE_URL, $token); 65 | ``` 66 | 67 | The above snippet of php interacts with the following security rules: 68 | 69 | ``` 70 | { 71 | "rules": { 72 | ".read": "auth.email == 'test@example.com'" 73 | ".write": "auth.email == 'admin@example.com'" 74 | } 75 | } 76 | ``` 77 | And will allow the snippet read-access to all of the nodes, but not write-access. 78 | 79 | ##Concurrent requests 80 | Execution of concurrent requests can be achieved with the same syntax as regular requests. Simply wrap them in a Closure and call the closure via the `batch` method and you are all set. 81 | 82 | ```php 83 | 84 | use Firebase\Firebase; 85 | 86 | $fb = Firebase::initialize(YOUR_FIREBASE_BASE_URL, YOUR_FIREBASE_SECRET); 87 | 88 | $requests = $fb->batch(function ($client) { 89 | for($i = 0; $i < 100; $i++) { 90 | $client->push('list', $i); 91 | } 92 | }); 93 | 94 | $pool = new GuzzleHttp\Pool($fb->getClient(), $requests); 95 | $pool->wait(); 96 | 97 | ``` 98 | 99 | ## Integration 100 | At the moment of writing, integration for Laravel 4.* is supported. A service provider and a facade class are supplied. Installation is done in 2 simple steps after the general installation steps: 101 | 102 | 1. edit `app/config/app.php` to add the service provider and the facade class 103 | ``` 104 | 'providers' => array( 105 | ... 106 | 'Firebase\Integration\Laravel\FirebaseServiceProvider' 107 | ) 108 | 109 | 'aliases' => array( 110 | ... 111 | 'Firebase' => 'Firebase\Integration\Laravel\Firebase' 112 | ) 113 | ``` 114 | 2. edit `app/config/services.php` (supplied by default from L4.2) to add `token` and `base_url` settings 115 | ``` 116 | 'firebase' => array( 117 | 'base_url' => YOUR_FIREBASE_BASE_URL, 118 | 'token' => YOUR_FIREBASE_SECRET 119 | ) 120 | ``` 121 | 122 | ##Eventing 123 | 124 | The library supports the EventEmitter pattern. The event-emitter is attached to the Firebase class. Events currently available: 125 | - RequestsBatchedEvent 126 | -------------------------------------------------------------------------------- /src/Autoloader.php: -------------------------------------------------------------------------------- 1 | register(); 30 | * 31 | * // register the base directories for the namespace prefix 32 | * $loader->addNamespace('Foo\Bar', '/path/to/packages/foo-bar/src'); 33 | * $loader->addNamespace('Foo\Bar', '/path/to/packages/foo-bar/tests'); 34 | * 35 | * The following line would cause the autoloader to attempt to load the 36 | * \Foo\Bar\Qux\Quux class from /path/to/packages/foo-bar/src/Qux/Quux.php: 37 | * 38 | * prefixes[$prefix]) === false) { 88 | $this->prefixes[$prefix] = array(); 89 | } 90 | 91 | // retain the base directory for the namespace prefix 92 | if ($prepend) { 93 | array_unshift($this->prefixes[$prefix], $base_dir); 94 | } else { 95 | array_push($this->prefixes[$prefix], $base_dir); 96 | } 97 | } 98 | 99 | /** 100 | * Loads the class file for a given class name. 101 | * 102 | * @param string $class The fully-qualified class name. 103 | * @return mixed The mapped file name on success, or boolean false on 104 | * failure. 105 | */ 106 | public function loadClass($class) 107 | { 108 | // the current namespace prefix 109 | $prefix = $class; 110 | 111 | // work backwards through the namespace names of the fully-qualified 112 | // class name to find a mapped file name 113 | while (false !== $pos = strrpos($prefix, '\\')) { 114 | 115 | // retain the trailing namespace separator in the prefix 116 | $prefix = substr($class, 0, $pos + 1); 117 | 118 | // the rest is the relative class name 119 | $relative_class = substr($class, $pos + 1); 120 | 121 | // try to load a mapped file for the prefix and relative class 122 | $mapped_file = $this->loadMappedFile($prefix, $relative_class); 123 | if ($mapped_file) { 124 | return $mapped_file; 125 | } 126 | 127 | // remove the trailing namespace separator for the next iteration 128 | // of strrpos() 129 | $prefix = rtrim($prefix, '\\'); 130 | } 131 | 132 | // never found a mapped file 133 | return false; 134 | } 135 | 136 | /** 137 | * Load the mapped file for a namespace prefix and relative class. 138 | * 139 | * @param string $prefix The namespace prefix. 140 | * @param string $relative_class The relative class name. 141 | * @return mixed Boolean false if no mapped file can be loaded, or the 142 | * name of the mapped file that was loaded. 143 | */ 144 | protected function loadMappedFile($prefix, $relative_class) 145 | { 146 | // are there any base directories for this namespace prefix? 147 | if (isset($this->prefixes[$prefix]) === false) { 148 | return false; 149 | } 150 | 151 | // look through base directories for this namespace prefix 152 | foreach ($this->prefixes[$prefix] as $base_dir) { 153 | 154 | // replace the namespace prefix with the base directory, 155 | // replace namespace separators with directory separators 156 | // in the relative class name, append with .php 157 | $file = $base_dir 158 | . str_replace('\\', '/', $relative_class) 159 | . '.php'; 160 | 161 | // if the mapped file exists, require it 162 | if ($this->requireFile($file)) { 163 | // yes, we're done 164 | return $file; 165 | } 166 | } 167 | 168 | // never found it 169 | return false; 170 | } 171 | 172 | /** 173 | * If a file exists, require it from the file system. 174 | * 175 | * @param string $file The file to require. 176 | * @return bool True if the file exists, false if not. 177 | */ 178 | protected function requireFile($file) 179 | { 180 | if (file_exists($file)) { 181 | require $file; 182 | return true; 183 | } 184 | return false; 185 | } 186 | } -------------------------------------------------------------------------------- /src/Auth/TokenGenerator.php: -------------------------------------------------------------------------------- 1 | secret = $secret; 50 | } 51 | 52 | /** 53 | * Generate a JWT token by specifying data and options 54 | * @param array|null $data 55 | * @param array $options 56 | * @return string 57 | */ 58 | public function generateToken($data, $options = array()) 59 | { 60 | return $this->encodeToken( 61 | $this->buildClaims($options + array('data' => $data)), 62 | $this->secret 63 | ); 64 | } 65 | 66 | /** 67 | * Encodes token using a JWT encoder 68 | * @param array|object $claims 69 | * @param string $secret 70 | * @param string $hashMethod 71 | * @return string 72 | * @throws MissingEncoderException 73 | */ 74 | protected function encodeToken($claims, $secret, $hashMethod = 'HS256') 75 | { 76 | if (method_exists($encoder = $this->resolveEncoder(), 'encode')) { 77 | return call_user_func_array(array($encoder, 'encode'), array($claims, $secret, $hashMethod)); 78 | } 79 | 80 | throw new MissingEncoderException('No JSON Web Token encoder could be found'); 81 | } 82 | 83 | 84 | /** 85 | * Resolve JWT encoder via static variable 86 | * @param string $default 87 | * @return mixed|string 88 | */ 89 | protected function resolveEncoder($default = 'JWT') 90 | { 91 | if (isset(static::$encoderResolver)) { 92 | 93 | return call_user_func(static::$encoderResolver); 94 | 95 | } 96 | 97 | return $default; 98 | } 99 | 100 | /** 101 | * Build optional and required claims array 102 | * @param array $options 103 | * @return array 104 | */ 105 | protected function buildClaims($options) 106 | { 107 | $claims = array(); 108 | 109 | foreach ($this->requiredClaims as $claimKey) { 110 | list($key, $claim) = $this->buildClaim($claimKey); 111 | $claims[$key] = $claim; 112 | } 113 | 114 | foreach (array_intersect($this->availableClaims, array_keys($options)) as $claimKey) { 115 | list($key, $claim) = $this->buildClaim($claimKey, $options[$claimKey]); 116 | $claims[$key] = $claim; 117 | } 118 | 119 | return $claims; 120 | } 121 | 122 | /** 123 | * Constructs builder method and executes with arguments 124 | * @param string $key 125 | * @param mixed|null $arg 126 | * @return mixed 127 | */ 128 | protected function buildClaim($key, $arg = null) 129 | { 130 | $claimBuilder = sprintf('build%sClaim', ucfirst($key)); 131 | return $this->{$claimBuilder}($arg); 132 | } 133 | 134 | /** 135 | * Validity of token not before value supplied 136 | * @param \DateTime|integer $value 137 | * @return array 138 | */ 139 | protected function buildNotBeforeClaim($value) 140 | { 141 | return array('nbf', $this->getValidTimestamp($value)); 142 | } 143 | 144 | /** 145 | * Expires parameter, determines expiry-date other than default: IssuedAt + 24 hrs 146 | * @param \DateTime|integer $value 147 | * @return array 148 | */ 149 | protected function buildExpiresClaim($value) 150 | { 151 | return array('exp', $this->getValidTimestamp($value)); 152 | } 153 | 154 | /** 155 | * Debug parameter, if set to true, gives Auth debug information in response header 156 | * @param $value 157 | * @return array 158 | */ 159 | protected function buildDebugClaim($value) 160 | { 161 | return array('debug', (bool)$value); 162 | } 163 | 164 | /** 165 | * Admin parameter, if set to true, grants read and write access 166 | * @param $value 167 | * @return array 168 | */ 169 | protected function buildAdminClaim($value) 170 | { 171 | return array('admin', (bool)$value); 172 | } 173 | 174 | /** 175 | * Version parameter, mandatory for JWT encoder 176 | * @param integer|null $value 177 | * @return array 178 | */ 179 | protected function buildVersionClaim($value = null) 180 | { 181 | return array('v', $value ?: $this->version); 182 | } 183 | 184 | /** 185 | * IssuedAt parameter, timestamp to determine expiry-date (24hrs) of token 186 | * @param \DateTime|integer|null $value 187 | * @return array 188 | */ 189 | protected function buildIssuedAtClaim($value = null) 190 | { 191 | return array('iat', $value ?: time()); 192 | } 193 | 194 | /** 195 | * Make sure the tmiestamp is formatted in seconds after epoch 196 | * @param \DateTime|integer $value 197 | * @return int 198 | * @throws \UnexpectedValueException 199 | */ 200 | protected function getValidTimestamp($value) 201 | { 202 | if (gettype($value) == 'integer') { 203 | return $value; 204 | } 205 | 206 | if ($value instanceof DateTime) { 207 | return $value->getTimestamp(); 208 | } 209 | 210 | throw new UnexpectedValueException('Instance of DateTime required for a valid timestamp'); 211 | } 212 | 213 | /** 214 | * Tests if data supplied is JSONifiable 215 | * @param $value 216 | * @return array 217 | * @throws UnexpectedValueException 218 | */ 219 | protected function buildDataClaim($value) 220 | { 221 | @json_encode($value); 222 | 223 | if (($errorCode = json_last_error()) !== JSON_ERROR_NONE) { 224 | 225 | throw new UnexpectedValueException($this->jsonErrorMessage($errorCode)); 226 | } 227 | 228 | return array('d', $value); 229 | } 230 | 231 | /** 232 | * Returns an error message 233 | * @param integer $errorNumber 234 | * @return string 235 | */ 236 | protected function jsonErrorMessage($errorNumber) 237 | { 238 | $messages = array( 239 | JSON_ERROR_NONE => 'No error has occured', 240 | JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded', 241 | JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON', 242 | JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded', 243 | JSON_ERROR_SYNTAX => 'Syntax error', 244 | JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded' 245 | ); 246 | return isset($messages[$errorNumber]) ? $messages[$errorNumber] : 'Unknown JSON encoding error'; 247 | } 248 | } -------------------------------------------------------------------------------- /tests/FirebaseTest.php: -------------------------------------------------------------------------------- 1 | token = 'aabbcc'; 47 | $this->request = Mockery::mock('GuzzleHttp\Message\RequestInterface'); 48 | $this->response = Mockery::mock('GuzzleHttp\Message\ResponseInterface')->shouldIgnoreMissing(); 49 | $this->emitter = Mockery::mock('GuzzleHttp\Event\EmitterInterface')->shouldIgnoreMissing(); 50 | $this->client = Mockery::mock('GuzzleHttp\ClientInterface'); 51 | 52 | $this->firebaseConfig = array( 53 | 'base_url' => 'http://baseurl', 54 | 'token' => $this->token, 55 | 'timeout' => 30 56 | ); 57 | 58 | $this->firebase = new Firebase\Firebase( 59 | $this->firebaseConfig, 60 | $this->client 61 | ); 62 | } 63 | 64 | public function testOptionBuilder() 65 | { 66 | //test with token and data options 67 | $result = self::callProtected($this->firebase, 'buildOptions', array(array('test' => 'success'))); 68 | $this->assertTrue(isset($result['query']) && isset($result['json'])); 69 | 70 | //test without data 71 | $result = self::callProtected($this->firebase, 'buildOptions'); 72 | $this->assertTrue(!isset($result['json'])); 73 | 74 | } 75 | 76 | public function testUrlBuilder() 77 | { 78 | //test fully specified 79 | $pathWithJson = '/test.json'; 80 | $result = self::callProtected($this->firebase, 'buildUrl', array($pathWithJson)); 81 | $this->assertEquals($this->firebaseConfig['base_url'] . $pathWithJson, $result); 82 | 83 | //test appending .json by url builder 84 | $pathWithoutJson = '/test'; 85 | 86 | $result = self::callProtected($this->firebase, 'buildUrl', array($pathWithoutJson)); 87 | $this->assertEquals($this->firebaseConfig['base_url'] . $pathWithJson, $result); 88 | } 89 | 90 | public function testQueryBuilder() 91 | { 92 | //testing query building with tokenk supplied 93 | $result = self::callProtected($this->firebase, 'buildQuery'); 94 | $this->assertEquals(array('auth' => $this->token), $result); 95 | 96 | //testing query building without token 97 | $this->firebase->setOption('token', null); 98 | $result = self::callProtected($this->firebase, 'buildQuery'); 99 | $this->assertEquals(array(), $result); 100 | } 101 | 102 | public function testRequestMethods() 103 | { 104 | $guzzle = $this->firebase->getClient(); 105 | 106 | $guzzle->shouldReceive(array( 107 | 'createRequest' => $this->request, 108 | 'send' => $this->response 109 | ))->times(5); 110 | 111 | $this->firebase->get('/test.json'); 112 | $this->firebase->push('/test.json', 'a'); 113 | $this->firebase->update('/test.json', 'c'); 114 | $this->firebase->set('/test.json', 'b'); 115 | $this->firebase->delete('/test.json'); 116 | } 117 | 118 | public function testEmptyGetter() 119 | { 120 | $guzzle = $this->firebase->getClient(); 121 | 122 | $guzzle->shouldReceive(array( 123 | 'createRequest' => $this->request, 124 | 'send' => $this->response 125 | ))->once(); 126 | 127 | $this->firebase->get(); 128 | } 129 | 130 | public function testNamedNormalizer() 131 | { 132 | //test multiple normalizers 133 | $this->firebase->setNormalizers(array(new DummyNormalizer())); 134 | $this->firebase->normalize('dummy'); 135 | } 136 | 137 | public function testDuckNormalizer() 138 | { 139 | $this->firebase->normalize(new \DummyNormalizer()); 140 | $this->assertEquals($this->callProtected($this->firebase, 'normalizeResponse', array($this->response)), $this->response); 141 | } 142 | 143 | public function testBatchRequests() 144 | { 145 | $this->firebase->getClient() 146 | ->shouldReceive('createRequest') 147 | ->twice() 148 | ->andReturn($this->request); 149 | 150 | $this->firebase->getClient() 151 | ->shouldReceive('getEmitter') 152 | ->once() 153 | ->andReturn($this->emitter); 154 | 155 | $requests = $this->firebase->batch(function ($fb) { 156 | $fb->get('/test/1'); 157 | $fb->get('/test/2'); 158 | }); 159 | 160 | $this->assertCount(2, $requests); 161 | } 162 | 163 | public function testBatchEvents() 164 | { 165 | $this->firebase->getClient() 166 | ->shouldReceive('createRequest') 167 | ->once() 168 | ->andReturn($this->request); 169 | 170 | $this->firebase->getClient() 171 | ->shouldReceive('getEmitter') 172 | ->once() 173 | ->andReturn($emitter = new \GuzzleHttp\Event\Emitter()); 174 | 175 | $emitter->on('requests.batched', function ($event) { 176 | $this->assertCount(1, $event->getRequests()); 177 | }); 178 | 179 | $this->firebase->batch(function ($fb) { 180 | $fb->get('/test/1'); 181 | }); 182 | } 183 | 184 | public function testAlternativeInjection() 185 | { 186 | $optionsRef = array(); 187 | 188 | \Firebase\Firebase::setClientResolver(function ($options) use (&$optionsRef) { 189 | $optionsRef = $options; 190 | 191 | return Mockery::mock('GuzzleHttp\ClientInterface'); 192 | }); 193 | 194 | $firebase = new \Firebase\Firebase(array('injected_option' => true)); 195 | 196 | $this->assertInstanceOf('GuzzleHttp\ClientInterface', $firebase->getClient()); 197 | $this->assertArrayHasKey('injected_option', $optionsRef); 198 | } 199 | 200 | public function testEvaluatePathValueArguments() 201 | { 202 | 203 | $firebase = new \Firebase\Firebase(array('injected_option' => true)); 204 | $pathAndValue = $this->callProtected($firebase, 'evaluatePathValueArguments', [['a', 'b']]); 205 | 206 | $this->assertEquals($pathAndValue, ['a', 'b']); 207 | 208 | $pathNullValue = $this->callProtected($firebase, 'evaluatePathValueArguments', [['a', \Firebase\Firebase::NULL_ARGUMENT]]); 209 | $this->assertEquals($pathNullValue, ['', 'a']); 210 | } 211 | 212 | public function testDefaultStaticConstructor() 213 | { 214 | $firebase = \Firebase\Firebase::initialize($this->firebaseConfig['base_url'], $this->token); 215 | $this->assertEquals($firebase->getOption('base_url'), $this->firebaseConfig['base_url']); 216 | $this->assertEquals($firebase->getOption('token'), $this->token); 217 | 218 | //unset client resolver and check for default guzzle client implementation 219 | \Firebase\Firebase::$clientResolver = null; 220 | $firebase = \Firebase\Firebase::initialize($this->firebaseConfig['base_url'], $this->token); 221 | $this->assertInstanceOf('GuzzleHttp\Client', $firebase->getClient()); 222 | } 223 | 224 | public function testCriteriaParsing() 225 | { 226 | $query = $this->callProtected($this->firebase, 'buildQuery', [new \Firebase\Criteria('$key', ['equalTo' => 'A'])]); 227 | 228 | $this->assertArrayHasKey('orderBy', $query); 229 | $this->assertArrayHasKey('equalTo', $query); 230 | } 231 | 232 | } -------------------------------------------------------------------------------- /src/Firebase.php: -------------------------------------------------------------------------------- 1 | $url, 'token' => $token]), null, $normalizers); 69 | } 70 | 71 | /** 72 | * @param array $options 73 | * @param null|ClientInterface $client 74 | * @param array $normalizers 75 | */ 76 | public function __construct($options = array(), ClientInterface $client = null, $normalizers = array()) 77 | { 78 | $this->setOptions($options); 79 | $this->setNormalizers($normalizers); 80 | is_null($client) ? $this->resolveClient() : $this->setClient($client); 81 | } 82 | 83 | /** 84 | * @param Closure $resolver 85 | */ 86 | public static function setClientResolver(Closure $resolver) 87 | { 88 | static::$clientResolver = $resolver; 89 | } 90 | 91 | /** 92 | * Read data from path 93 | * @param $path 94 | * @return mixed 95 | */ 96 | public function get($path = '', Criteria $criteria = null) 97 | { 98 | $request = $this->createRequest('GET', $path, $criteria); 99 | return $this->handleRequest($request); 100 | } 101 | 102 | /** 103 | * Set data in path 104 | * @param $path 105 | * @param $value 106 | * @return mixed 107 | */ 108 | public function set($path, $value = self::NULL_ARGUMENT) 109 | { 110 | $request = $this->createRequest('PUT', $path, $value); 111 | return $this->handleRequest($request); 112 | } 113 | 114 | /** 115 | * Update exising data in path 116 | * @param $path 117 | * @param $value 118 | * @return mixed 119 | */ 120 | public function update($path, $value = self::NULL_ARGUMENT) 121 | { 122 | $request = $this->createRequest('PATCH', $path, $value); 123 | return $this->handleRequest($request); 124 | } 125 | 126 | /** 127 | * Delete item in path 128 | * @param $path 129 | * @return mixed 130 | */ 131 | public function delete($path = '') 132 | { 133 | $request = $this->createRequest('DELETE', $path); 134 | return $this->handleRequest($request); 135 | } 136 | 137 | /** 138 | * Push item to path 139 | * @param $path 140 | * @param $value 141 | * @return mixed 142 | */ 143 | public function push($path, $value = self::NULL_ARGUMENT) 144 | { 145 | $request = $this->createRequest('POST', $path, $value); 146 | return $this->handleRequest($request); 147 | } 148 | 149 | /** 150 | * Create a Request object 151 | * @param string $method 152 | * @param string $path 153 | * @param mixed $value 154 | * @return RequestInterface 155 | */ 156 | protected function createRequest($method, $path, $value = null) 157 | { 158 | list($path, $value) = $this->evaluatePathValueArguments(array($path, $value)); 159 | return $this->client->createRequest($method, $this->buildUrl($path), $this->buildOptions($value)); 160 | } 161 | 162 | /** 163 | * Stores requests when batching, sends request 164 | * @param RequestInterface $request 165 | * @return mixed 166 | */ 167 | protected function handleRequest(RequestInterface $request) 168 | { 169 | if (!$this->getOption('batch', false)) { 170 | $response = $this->client->send($request); 171 | return $this->normalizeResponse($response); 172 | } 173 | $this->requests[] = $request; 174 | } 175 | 176 | /** 177 | * Set a normalizer by string or a normalizer instance 178 | * @param string|NormalizerInterface $normalizer 179 | * @return $this 180 | */ 181 | public function normalize($normalizer) 182 | { 183 | if (is_string($normalizer) && isset($this->normalizers[$normalizer])) { 184 | 185 | $this->normalizer = $this->normalizers[$normalizer]; 186 | 187 | } else if ($normalizer instanceof NormalizerInterface) { 188 | 189 | $this->normalizer = $normalizer; 190 | 191 | } 192 | 193 | return $this; 194 | } 195 | 196 | /** 197 | * Normalizes the HTTP Request Client response 198 | * @param ResponseInterface $response 199 | * @return mixed 200 | */ 201 | protected function normalizeResponse(ResponseInterface $response) 202 | { 203 | if (!is_null($this->normalizer)) { 204 | return $this->normalizer->normalize($response); 205 | } 206 | 207 | //default responsen is decoded json 208 | return $response->json(); 209 | } 210 | 211 | /** 212 | * Set normalizers in an associative array 213 | * @param $normalizers 214 | * @return $this 215 | */ 216 | public function setNormalizers($normalizers) 217 | { 218 | foreach ($normalizers as $normalizer) { 219 | $this->normalizers[$normalizer->getName()] = $normalizer; 220 | } 221 | return $this; 222 | } 223 | 224 | /** 225 | * @param ClientInterface $client 226 | * @return $this 227 | */ 228 | public function setClient(ClientInterface $client) 229 | { 230 | $this->client = $client; 231 | return $this; 232 | } 233 | 234 | /** 235 | * @return ClientInterface 236 | */ 237 | public function getClient() 238 | { 239 | return $this->client; 240 | } 241 | 242 | /** 243 | * Prefix url with a base_url if present 244 | * @param string $path 245 | * @return string 246 | */ 247 | protected function buildUrl($path) 248 | { 249 | $baseUrl = $this->getOption('base_url'); 250 | 251 | //add trailing slash to the url if not supplied in the base_url setting nor supplied in path #6 252 | $url = $baseUrl . ((substr($baseUrl, -1) == '/' || substr($path, 0, 1) == '/') ? '' : '/') . $path; 253 | 254 | if (strpos($url, '.json') === false) { 255 | $url .= '.json'; 256 | } 257 | 258 | return $url; 259 | } 260 | 261 | /** 262 | * Build Query parameters for HTTP Request Client 263 | * @param $data 264 | * @return array 265 | */ 266 | protected function buildQuery($data = null) 267 | { 268 | $params = array(); 269 | 270 | if ($data instanceof Criteria) { 271 | $params = array_merge($params, $data->getParams()); 272 | $params['orderBy'] = $data->getOrderBy(); 273 | } 274 | 275 | if ($token = $this->getOption('token', false)) { 276 | $params['auth'] = $token; 277 | } 278 | 279 | return $params; 280 | } 281 | 282 | /** 283 | * Build options array for HTTP Request Client 284 | * @param mixed $data 285 | * @return array 286 | */ 287 | protected function buildOptions($data = null) 288 | { 289 | $options = array( 290 | 'query' => $this->buildQuery($data), 291 | 'debug' => $this->getOption('debug', false), 292 | 'timeout' => $this->getOption('timeout', 0) 293 | ); 294 | 295 | if (!is_null($data) && !($data instanceof Criteria)) { 296 | $options['json'] = $data; 297 | } 298 | 299 | return $options; 300 | } 301 | 302 | 303 | public function batch($callable) 304 | { 305 | //enable batching in the config 306 | $this->setOption('batch', true); 307 | 308 | //gather requests 309 | call_user_func_array($callable, array($this)); 310 | 311 | $requests = $this->requests; 312 | 313 | $emitter = $this->client->getEmitter(); 314 | $emitter->emit('requests.batched', new RequestsBatchedEvent($requests)); 315 | 316 | //reset the requests for the next batch 317 | $this->requests = []; 318 | 319 | return $requests; 320 | } 321 | 322 | /** 323 | * Handle single argument calls to set/update/push methods #7 324 | * @param $args 325 | * @return array 326 | */ 327 | protected function evaluatePathValueArguments($args) 328 | { 329 | $hasSecondArgument = $args[1] !== self::NULL_ARGUMENT; 330 | return array(!$hasSecondArgument ? '' : $args[0], !$hasSecondArgument ? $args[0] : $args[1]); 331 | } 332 | 333 | /** 334 | * Inject client dependency 335 | */ 336 | protected function resolveClient() 337 | { 338 | if (!isset($this->client)) { 339 | $this->setClient(call_user_func(static::$clientResolver, $this->getOptions())); 340 | } 341 | } 342 | 343 | } --------------------------------------------------------------------------------