diff --git a/README.md b/README.md index 12cbb5b..14c3bce 100755 --- a/README.md +++ b/README.md @@ -5,37 +5,37 @@ The Keen IO API lets developers build analytics features directly into their app [![Build Status](https://travis-ci.org/keenlabs/KeenClient-PHP.png?branch=master)](https://travis-ci.org/keenlabs/KeenClient-PHP) Installation with Composer ------------- +-------------------------- 1. Install composer via via `curl -s http://getcomposer.org/installer | php` (on windows, download http://getcomposer.org/installer and execute it with PHP) - 1. Edit your `composer.json` file with following contents: + 2. Edit your `composer.json` file with following contents: ```json "require": { - "keen-io/keen-io": "dev-master" + "keen-io/keen-io": "~1.1" } ``` 3. Run `php composer.phar install` Usage ---- +----- This client was built using [Guzzle](http://guzzlephp.org/), a PHP HTTP client & framework for building RESTful web service clients. When you first create a new `KeenIOClient` instance you can pass configuration settings like your Project Id and API Keys in an array to the factory method. These are optional and can be later specified through Setter methods. -For certain API Resources, the Master API Key is required and can also be passed to the factory method in the configuration array. +For certain API Resources, the Master API Key is required and can also be passed to the factory method in the configuration array. Please read the [Security Documentation](https://keen.io/docs/security/) regarding this Master API key. For Requests, the `KeenIOClient` will determine what API Key should be passed based on the type of Request and configuration in the [Service Description](/src/KeenIO/Resources/config/keen-io-3_0.json). The API Key is passed in the `Authorization` header of the request. -For a list of required and available parameters for the different API Endpoints, please consult the Keen IO +For a list of required and available parameters for the different API Endpoints, please consult the Keen IO [API Reference Docs](https://keen.io/docs/api/reference/). -####Configuring the Client +#### Configuring the Client The factory method accepts an array of configuration settings for the Keen IO Webservice Client. @@ -47,41 +47,41 @@ Read API Key | `readKey` | The Read API Key - used for access to read only (GET| Write API Key | `writeKey` | The Write API Key - used for write (PUT|POST Requests) operations of the API API Version | `version` | The API Version. Currently used to version the API URL and Service Description -When passing `version` to the factory method or using the `setVersion()` method, the Client will try to load a client Service Description +When passing `version` to the factory method or using the `setVersion()` method, the Client will try to load a client Service Description that matches that version. That Service Description defines the operations available to the Webservice Client. Currently the Keen IO Webservice Client only supports - and automatically defaults - to the current version (`3.0`) of the API. -######Example +###### Example ```php use KeenIO\Client\KeenIOClient; -$client = KeenIOClient::factory([ +$client = KeenIOClient::factory([ 'projectId' => $projectId, - 'writeKey' => $writeKey, - 'readKey' => $readKey + 'writeKey' => $writeKey, + 'readKey' => $readKey ]); ``` -####Configuration can be updated to reuse the same Client: +#### Configuration can be updated to reuse the same Client: You can reconfigure the Keen IO Client configuration options through available getters and setters. You can get and set the following options: `projectId`, `readKey`, `writeKey`, `masterKey`, & `version`. -######Example +###### Example ```php //Get the current Project Id $client->getProjectId(); //Set a new Project Id -$client->setProjectId( $someNewProjectId ); +$client->setProjectId($someNewProjectId); //Get the current Read Key $client->getReadKey(); //Set a new Read Key -$newReadKey = $client->getScopedKey( $masterKey, $filters, $allowed_operations ); -$client->setReadKey( $newReadKey ); +$newReadKey = $client->getScopedKey($masterKey, $filters, $allowedOperations); +$client->setReadKey($newReadKey); ``` @@ -90,108 +90,74 @@ Once you've created a `KeenIOClient`, sending events is simple: ######Example ```php -$event = [ 'purchase' => [ 'item' => 'Golden Elephant' ] ]; +$event = ['purchase' => ['item' => 'Golden Elephant']]; -$client->addEvent( 'purchases', [ 'data' => $event ] ); +$client->addEvent('purchases', ['data' => $event]); ``` -####Send batched events to Keen -You can upload multiple Events to multiple Event Collections at once! +#### Send batched events to Keen +You can upload multiple Events to multiple Event Collections at once! -In the example below, we will create two new purchase events in the `purchases` event collection and a single +In the example below, we will create two new purchase events in the `purchases` event collection and a single new event in the `sign_ups` event collection. Note that the keys of the `data` array specify the `event_collection` where those events should be stored. -######Example +###### Example ```php $purchases = [ - [ 'purchase' => [ 'item' => 'Golden Elephant' ] ], - [ 'purchase' => [ 'item' => 'Magenta Elephant' ] ] + ['purchase' => ['item' => 'Golden Elephant']], + ['purchase' => ['item' => 'Magenta Elephant']] ]; $signUps = [ - [ 'name' => 'foo', 'email' => 'bar@baz.com' ] + ['name' => 'foo', 'email' => 'bar@baz.com'] ]; -$client->addEvents([ 'data' => [ 'purchases' => $purchases, 'sign_ups' => $signUps ] ]); -``` - -####Send batched events in Parallel -Useful for large batch processing jobs. The client will serialize each request and send them all in parallel. - -If an error is encountered during the transfer, then a `KeenIO\Exception\CommandTransferException` is thrown, which allows -you to retrieve a list of commands that succeeded and a list of commands that failed. - -For more information on parallel commands, you can check the [Guzzle docs](http://guzzlephp.org/webservice-client/webservice-client.html#executing-commands-in-parallel). - -######Example: -```php -// Split the events into chunks -$eventChunks = array_chunk( $events, 500 ); - -$commands = []; -foreach( $eventChunks as $eventChunk ) -{ - // Using getCommand will create the command with out immediately executing it - // versus using the magic methods - $commands[] = $this->getCommand( "sendEvents", [ 'data' => [ 'purchases' => $eventChunk ] ] ); -} - -try -{ - // The commands can then be passed to the client's execute method to be run - // in parallel - $result = $this->execute( $commands ); -} -catch( \KeenIO\Exception\CommandTransferException $e ) -{ - // Handle any errored commands... - $failedCommands = $e->getFailedCommands(); -} +$client->addEvents(['data' => ['purchases' => $purchases, 'sign_ups' => $signUps]]); ``` -####Get Analysis on Events +#### Get Analysis on Events All Analysis Endpoints should be supported. See the [API Reference Docs](https://keen.io/docs/api/reference/) for required parameters. You can also check the [Service Description](/src/KeenIO/Resources/config/keen-io-3_0.json) for configured API Endpoints. Below are a few example calls to some of the Analysis methods available. -######Example -```php +###### Example +```php //Count -$totalPurchases = $client->count( 'purchases' ); +$totalPurchases = $client->count('purchases'); //Count Unqiue -$totalItems = $client->countUnique( 'purchases', [ 'target_property' => 'purchase.item' ]); +$totalItems = $client->countUnique('purchases', ['target_property' => 'purchase.item']); //Select Unique -$items = $client->selectUnique( 'purchases', [ 'target_property' => 'purchase.item' ]); +$items = $client->selectUnique('purchases', ['target_property' => 'purchase.item']); //Multi Analysis $analyses = [ - 'clicks' => [ "analysis_type" => "count" ], - 'average price' => [ "analysis_type" => "average", "target_property" => "purchase.price" ] + 'clicks' => ['analysis_type' => 'count'], + 'average price' => ['analysis_type' => 'average', 'target_property' => 'purchase.price'] ]; -$stats = $client->multiAnalysis( 'purchases', [ 'analyses' => $analyses ]); +$stats = $client->multiAnalysis('purchases', ['analyses' => $analyses]); ``` -###Create a Scoped Key +### Create a Scoped Key Scoped keys allow you to secure the requests to the API Endpoints and are especially useful when you are providing -access to multiple clients or applications. You should read the Keen IO docs concerning [Scoped Keys](https://keen.io/docs/security/#scoped-key) +access to multiple clients or applications. You should read the Keen IO docs concerning [Scoped Keys](https://keen.io/docs/security/#scoped-key) for more details. ######Example ```php $filter = [ - 'property_name' => 'user_id', - 'operator' => 'eq', - 'property_value' => '123' + 'property_name' => 'user_id', + 'operator' => 'eq', + 'property_value' => '123' ]; -$filters = [ $filter ]; -$allowed_operations = [ 'read' ]; +$filters = [$filter]; +$allowed_operations = ['read']; -$scopedKey = $client->getScopedKey( $masterKey, $filters, $allowed_operations ); +$scopedKey = $client->getScopedKey($masterKey, $filters, $allowedOperations); ``` diff --git a/src/KeenIO/Client/KeenIOClient.php b/src/KeenIO/Client/KeenIOClient.php index 0eec9f4..a532231 100755 --- a/src/KeenIO/Client/KeenIOClient.php +++ b/src/KeenIO/Client/KeenIOClient.php @@ -10,6 +10,25 @@ * Class KeenIOClient * * @package KeenIO\Client + * + * @method array getResources(array $args = array()) {@command KeenIO getResources} + * @method array getProjects(array $args = array()) {@command KeenIO getProjects} + * @method array getProject(array $args = array()) {@command KeenIO getProject} + * @method array getEventSchemas(array $args = array()) {@command KeenIO getEventSchemas} + * @method array addEvent(array $args = array()) {@command KeenIO addEvent} + * @method array addEvents(array $args = array()) {@command KeenIO addEvents} + * @method array deleteEvents(array $args = array()) {@command KeenIO deleteEvents} + * @method array deleteEventProperties(array $args = array()) {@command KeenIO deleteEventProperties} + * @method array count(array $args = array()) {@command KeenIO count} + * @method array countUnique(array $args = array()) {@command KeenIO countUnique} + * @method array minimum(array $args = array()) {@command KeenIO minimum} + * @method array maximum(array $args = array()) {@command KeenIO maximum} + * @method array average(array $args = array()) {@command KeenIO average} + * @method array sum(array $args = array()) {@command KeenIO sum} + * @method array selectUnique(array $args = array()) {@command KeenIO selectUnique} + * @method array funnel(array $args = array()) {@command KeenIO funnel} + * @method array multiAnalysis(array $args = array()) {@command KeenIO multiAnalysis} + * @method array extraction(array $args = array()) {@command KeenIO extraction} */ class KeenIOClient extends Client { @@ -20,10 +39,10 @@ class KeenIOClient extends Client * * @returns \KeenIO\Client\KeenIOClient */ - public static function factory( $config = array() ) + public static function factory($config = array()) { $default = array( - 'baseUrl' => "https://api.keen.io/{version}/", + 'baseUrl' => 'https://api.keen.io/{version}/', 'version' => '3.0', 'masterKey' => null, 'writeKey' => null, @@ -32,30 +51,30 @@ public static function factory( $config = array() ) ); // Validate the configuration options - self::validateConfig( $config ); + self::validateConfig($config); // Create client configuration - $config = Collection::fromConfig( $config, $default ); + $config = Collection::fromConfig($config, $default); /** * Because each API Resource uses a separate type of API Key, we need to expose them all in * `commands.params`. Doing it this way allows the Service Definitions to set what API Key is used. */ $parameters = array(); - foreach( array( 'masterKey', 'writeKey', 'readKey' ) as $key ) { - $parameters[ $key ] = $config->get( $key ); + foreach(array('masterKey', 'writeKey', 'readKey') as $key) { + $parameters[$key] = $config->get($key); } - $config->set( 'command.params', $parameters ); + $config->set('command.params', $parameters); // Create the new Keen IO Client with our Configuration - $client = new self( $config->get( 'baseUrl' ), $config ); + $client = new self($config->get('baseUrl'), $config); // Set the Service Definition from the versioned file - $file = 'keen-io-' . str_replace( '.', '_', $client->getConfig( 'version' ) ) . '.json'; - $client->setDescription( ServiceDescription::factory( __DIR__ . "/../Resources/config/{$file}" ) ); + $file = 'keen-io-' . str_replace('.', '_', $client->getConfig('version')) . '.json'; + $client->setDescription(ServiceDescription::factory(__DIR__ . "/../Resources/config/{$file}")); // Set the content type header to use "application/json" for all requests - $client->setDefaultOption( 'headers', array( 'Content-Type' => 'application/json' ) ); + $client->setDefaultOption('headers', array('Content-Type' => 'application/json')); return $client; } @@ -69,80 +88,46 @@ public static function factory( $config = array() ) * @param array $args Arguments to pass to the command * * @return mixed Returns the result of the command - * @throws BadMethodCallException when a command is not found */ - public function __call( $method, $args = array() ) + public function __call($method, $args = array()) { - if ( isset( $args[0] ) && is_string( $args[0] ) ) - { - $args[0] = array( 'event_collection' => $args[0] ); + if (isset($args[0]) && is_string($args[0])) { + $args[0] = array('event_collection' => $args[0]); - if ( isset( $args[1] ) && is_array( $args[1] ) ) - $args[0] = array_merge( $args[1], $args[0] ); + if (isset($args[1]) && is_array($args[1])) + $args[0] = array_merge($args[1], $args[0]); } return $this->getCommand($method, isset($args[0]) ? $args[0] : array())->getResult(); } - /** - * Bulk insert events into a single event collection. - * @TODO: Better response & error handling needed before using / documenting... - * - * @param string $collection - * @param array $events - * @param int $size - * - * @return array - */ - public function addBatchedEvents( $collection, $events = array(), $size = 500 ) - { - $commands = array(); - - $eventChunks = array_chunk( $events, $size ); - foreach( $eventChunks as $eventChunk ) { - $commands[ ] = $this->getCommand( "sendEvents", array( 'data' => array( $collection => $eventChunk ) ) ); - } - - try { - $result = $this->execute( $commands ); - } catch( CommandTransferException $e ) { - return array( - 'total' => sizeof( $eventChunks ), - 'succeeded' => sizeof( $e->getSuccessfulCommands() ), - 'failed' => sizeof( $e->getFailedCommands() ) - ); - } - - return array( 'batches' => sizeof( $eventChunks ), 'succeeded' => sizeof( $result ), 'failed' => 0 ); - } - /** * Get a scoped key for an array of filters * - * @param string $apiKey The master API key to use for encryption - * @param array $filters What filters to encode into a scoped key - * @param array $allowed_operations What operations the generated scoped key will allow + * @param string $apiKey The master API key to use for encryption + * @param array $filters What filters to encode into a scoped key + * @param array $allowedOperations What operations the generated scoped key will allow * @param int $source * * @return string */ - public function getScopedKey( $apiKey, $filters, $allowed_operations, $source = MCRYPT_DEV_RANDOM ) + public function getScopedKey( $apiKey, $filters, $allowedOperations, $source = MCRYPT_DEV_RANDOM ) { $options = array( 'filters' => $filters ); - if( $allowed_operations ) { - $options[ 'allowed_operations' ] = $allowed_operations; + if (!empty($allowedOperations)) { + $options['allowed_operations'] = $allowedOperations; } - $optionsJson = $this->padString( json_encode( $options ) ); + $optionsJson = $this->padString(json_encode($options)); - $ivLength = mcrypt_get_iv_size( MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC ); - $iv = mcrypt_create_iv( $ivLength, $source ); + $ivLength = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC); + $iv = mcrypt_create_iv($ivLength, $source); - $encrypted = mcrypt_encrypt( MCRYPT_RIJNDAEL_128, $apiKey, $optionsJson, MCRYPT_MODE_CBC, $iv ); + $encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $apiKey, $optionsJson, MCRYPT_MODE_CBC, $iv); - $ivHex = bin2hex( $iv ); - $encryptedHex = bin2hex( $encrypted ); + $ivHex = bin2hex($iv); + $encryptedHex = bin2hex($encrypted); $scopedKey = $ivHex . $encryptedHex; @@ -157,11 +142,10 @@ public function getScopedKey( $apiKey, $filters, $allowed_operations, $source = * * @return string */ - protected function padString( $string, $blockSize = 32 ) + protected function padString($string, $blockSize = 32) { - - $paddingSize = $blockSize - ( strlen( $string ) % $blockSize ); - $string .= str_repeat( chr( $paddingSize ), $paddingSize ); + $paddingSize = $blockSize - (strlen($string) % $blockSize); + $string .= str_repeat(chr($paddingSize), $paddingSize); return $string; } @@ -171,46 +155,38 @@ protected function padString( $string, $blockSize = 32 ) * * @param string $apiKey The master API key to use for decryption * @param string $scopedKey The scoped Key to decrypt - * * @return mixed */ - public function decryptScopedKey( $apiKey, $scopedKey ) + public function decryptScopedKey($apiKey, $scopedKey) { + $ivLength = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC) * 2; + $ivHex = substr($scopedKey, 0, $ivLength); - $ivLength = mcrypt_get_iv_size( MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC ) * 2; - $ivHex = substr( $scopedKey, 0, $ivLength ); - - $encryptedHex = substr( $scopedKey, $ivLength ); + $encryptedHex = substr($scopedKey, $ivLength); $resultPadded = mcrypt_decrypt( - MCRYPT_RIJNDAEL_128, - $apiKey, - pack( 'H*', $encryptedHex ), - MCRYPT_MODE_CBC, - pack( 'H*', $ivHex ) - ); - - $result = $this->unpadString( $resultPadded ); - - $options = json_decode( $result, true ); + MCRYPT_RIJNDAEL_128, + $apiKey, + pack('H*', $encryptedHex), + MCRYPT_MODE_CBC, + pack('H*', $ivHex) + ); - return $options; + return json_decode($this->unpadString($resultPadded), true); } /** * Remove padding for a PKCS7-padded string * * @param string $string - * * @return string */ - protected function unpadString( $string ) + protected function unpadString($string) { + $len = strlen($string); + $pad = ord($string[$len - 1]); - $len = strlen( $string ); - $pad = ord( $string[ $len - 1 ] ); - - return substr( $string, 0, $len - $pad ); + return substr($string, 0, $len - $pad); } /** @@ -218,17 +194,16 @@ protected function unpadString( $string ) * * @param string $projectId */ - public function setProjectId( $projectId ) + public function setProjectId($projectId) { - self::validateConfig( array( 'projectId' => $projectId ) ); - - $this->getConfig()->set( 'projectId', $projectId ); + self::validateConfig(array('projectId' => $projectId)); + $this->getConfig()->set('projectId', $projectId); } /** * Gets the Project Id being used by the Keen IO Client * - * returns string|null Value of the ProjectId or NULL + * @return string|null Value of the ProjectId or NULL */ public function getProjectId() { @@ -240,23 +215,24 @@ public function getProjectId() * * @param string $writeKey */ - public function setWriteKey( $writeKey ) + public function setWriteKey($writeKey) { - self::validateConfig( array( 'writeKey' => $writeKey ) ); + self::validateConfig(array('writeKey' => $writeKey)); - $this->getConfig()->set( 'writeKey', $writeKey ); + $this->getConfig()->set('writeKey', $writeKey); // Add API Read Key to `command.params` - $params = $this->getConfig( 'command.params' ); + $params = $this->getConfig('command.params'); $params['writeKey'] = $writeKey; - $this->getConfig()->set( 'command.params', $params ); + + $this->getConfig()->set('command.params', $params); } /** * Gets the API Write Key being used by the Keen IO Client * - * returns string|null Value of the WriteKey or NULL + * @return string|null Value of the WriteKey or NULL */ public function getWriteKey() { @@ -268,22 +244,23 @@ public function getWriteKey() * * @param string $readKey */ - public function setReadKey( $readKey ) + public function setReadKey($readKey) { - self::validateConfig( array( 'readKey' => $readKey ) ); + self::validateConfig(array('readKey' => $readKey)); - $this->getConfig()->set( 'readKey', $readKey ); + $this->getConfig()->set('readKey', $readKey); // Add API Read Key to `command.params` - $params = $this->getConfig( 'command.params' ); + $params = $this->getConfig('command.params'); $params['readKey'] = $readKey; - $this->getConfig()->set( 'command.params', $params ); + + $this->getConfig()->set('command.params', $params); } /** * Gets the API Read Key being used by the Keen IO Client * - * returns string|null Value of the ReadKey or NULL + * @return string|null Value of the ReadKey or NULL */ public function getReadKey() { @@ -295,22 +272,23 @@ public function getReadKey() * * @param string $masterKey */ - public function setMasterKey( $masterKey ) + public function setMasterKey($masterKey) { - self::validateConfig( array( 'masterKey' => $masterKey ) ); + self::validateConfig(array('masterKey' => $masterKey)); - $this->getConfig()->set( 'masterKey', $masterKey ); + $this->getConfig()->set('masterKey', $masterKey); // Add API Master Key to `command.params` - $params = $this->getConfig( 'command.params' ); + $params = $this->getConfig('command.params'); $params['masterKey'] = $masterKey; - $this->getConfig()->set( 'command.params', $params ); + + $this->getConfig()->set('command.params', $params); } /** * Gets the API Master Key being used by the Keen IO Client * - * returns string|null Value of the MasterKey or NULL + * @return string|null Value of the MasterKey or NULL */ public function getMasterKey() { @@ -323,21 +301,21 @@ public function getMasterKey() * * @param string $version */ - public function setVersion( $version ) + public function setVersion($version) { - self::validateConfig( array( 'version' => $version ) ); + self::validateConfig(array('version' => $version)); - $this->getConfig()->set( 'version', $version ); + $this->getConfig()->set('version', $version); /* Set the Service Definition from the versioned file */ - $file = 'keen-io-' . str_replace( '.', '_', $this->getConfig( 'version' ) ) . '.json'; - $this->setDescription( ServiceDescription::factory( __DIR__ . "/../Resources/config/{$file}" ) ); + $file = 'keen-io-' . str_replace('.', '_', $this->getConfig('version')) . '.json'; + $this->setDescription(ServiceDescription::factory(__DIR__ . "/../Resources/config/{$file}")); } /** * Gets the Version being used by the Keen IO Client * - * returns string|null Value of the Version or NULL + * @return string|null Value of the Version or NULL */ public function getVersion() { @@ -347,27 +325,32 @@ public function getVersion() /** * Validates the Keen IO Client configuration options * - * @params array $config - * @throws InvalidArgumentException When a config value does not meet its validation criteria + * @params array $config + * @throws \InvalidArgumentException When a config value does not meet its validation criteria */ - static function validateConfig( $config = array() ) + static function validateConfig($config = array()) { - foreach( $config as $option => $value ) + foreach($config as $option => $value) { - if ( $option == 'version' && empty( $config['version'] ) ) + if ($option === 'version' && empty($config['version'])) { throw new \InvalidArgumentException("Version can not be empty"); + } - if ( $option == "readKey" && ! ctype_alnum( $value ) ) + if ($option === 'readKey' && ! ctype_alnum($value)) { throw new \InvalidArgumentException( "Read Key '{$value}' contains invalid characters or spaces." ); + } - if ( $option == "writeKey" && ! ctype_alnum( $value ) ) + if ($option === 'writeKey' && ! ctype_alnum($value)) { throw new \InvalidArgumentException( "Write Key '{$value}' contains invalid characters or spaces." ); + } - if ( $option == "masterKey" && ! ctype_alnum( $value ) ) + if ($option === 'masterKey' && ! ctype_alnum($value)) { throw new \InvalidArgumentException( "Write Key '{$value}' contains invalid characters or spaces." ); + } - if ( $option == "projectId" && ! ctype_alnum( $value ) ) + if ($option === 'projectId' && ! ctype_alnum($value)) { throw new \InvalidArgumentException( "Project ID '{$value}' contains invalid characters or spaces."); + } } } } diff --git a/src/KeenIO/Exception/CommandTransferException.php b/src/KeenIO/Exception/CommandTransferException.php deleted file mode 100755 index 71e4116..0000000 --- a/src/KeenIO/Exception/CommandTransferException.php +++ /dev/null @@ -1,10 +0,0 @@ -