diff --git a/.gitignore b/.gitignore index 20328172b..f69e4693c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ composer.phar .vagrant integration-tests/vendor -integration-tests/composer.lock +composer.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e548727c..bb727de79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,74 @@ # Change log -All notable changes to the LaunchDarkly Java SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). +All notable changes to the LaunchDarkly PHP SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). + +## [2.4.0] - 2018-01-04 +### Added +- Support for [private user attributes](https://docs.launchdarkly.com/docs/private-user-attributes). + +### Changed +- Stop retrying HTTP requests if the API key has been invalidated. +- User bucketing supports integer attributes. Thanks @mlund01! +- Source code complies with the PSR-2 standard. Thanks @valerianpereira! + +## [2.3.0] - 2017-10-06 +### Added +- New `flush` method forces events to be published to the LaunchDarkly service. This can be useful if `LDClient` is not automatically destroyed at the end of a request. Thanks @foxted! + +## [2.2.0] - 2017-06-06 +### Added +- Support for [publishing events via ld-relay](README.md#using-ld-relay) +- Allow `EventPublisher` to be injected into the client. +- `GuzzleEventPublisher` as a synchronous, in-process alternative to publishing events via background processes. +- Allow the `curl` path used by `CurlEventPublisher` to be customized via the `curl` option to `LDClient`. Thanks @abacaphiliac! + +## [2.1.2] - 2017-04-27 +### Changed +- Added package suggestions in `composer.json`. + +### Fixed +- Better handling of possibly null variations. Thanks @idirouhab! + +## [2.1.1] - 2017-04-11 +### Changed +- Better handling of possibly null targets. Thanks @idirouhab! +- Better handling of possibly null rules. + +## [2.1.0] - 2017-03-23 +### Changed +- Allow FeatureRequester to be injected into the client. Thanks @abacaphiliac! +- Allow Predis\Client to be overriden in LDDFeatureRequester. Thanks @abacaphiliac! +- Use logger interface method instead of Monolog method. Thanks @abacaphiliac! +- Improve type hinting for default. Thanks @jdrieghe! + +## [2.0.7] - 2017-03-03 +### Changed +- Removed warning when calling `allFlags` via `LDDFeatureRequester` +- Removed warning when a feature flag's prerequisites happen to be null + +## [2.0.6] - 2017-02-07 +### Changed +- Use minimum versions in composer.json + +## [2.0.5] - 2017-02-03 +### Changed +- Made Monolog dependency version less strict + +## [2.0.4] - 2017-01-26 +### Changed +- Made Composer requirements less strict + +## [2.0.3] - 2017-01-05 +### Changed +- Fixed botched 2.0.2 release: Better handling of null vs false when evaluating. + +## [2.0.2] - 2017-01-04 +### Changed +- Better handling of null vs false when evaluating. + +## [2.0.1] - 2016-11-09 +### Added +- Monolog is now a required dependency ## [2.0.0] - 2016-08-08 ### Added @@ -15,4 +83,4 @@ All notable changes to the LaunchDarkly Java SDK will be documented in this file - The `toggle` call has been deprecated in favor of `variation`. ### Removed -- The `getFlag` function has been removed. \ No newline at end of file +- The `getFlag` function has been removed. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 37e1be0ce..66a657168 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,4 +7,4 @@ We encourage pull-requests and other contributions from the community. We've als This can help with getting php 5.3 on your mac: https://github.com/Homebrew/homebrew-php To see composer dependency graph: -`php composer.phar show -i -t` \ No newline at end of file +`php composer.phar show -i -t` diff --git a/README.md b/README.md index ed88128c0..8df0f1fa0 100644 --- a/README.md +++ b/README.md @@ -5,20 +5,23 @@ LaunchDarkly SDK for PHP [![Circle CI](https://circleci.com/gh/launchdarkly/php-client.svg?style=svg)](https://circleci.com/gh/launchdarkly/php-client) +Requirements +------------ +1. PHP 5.3 or higher. + Quick setup ----------- -0. Install the PHP SDK with [Composer](https://getcomposer.org/) +1. Install the PHP SDK and monolog for logging with [Composer](https://getcomposer.org/) php composer.phar require launchdarkly/launchdarkly-php - php composer.phar require "guzzlehttp/guzzle:6.2.1" - php composer.phar require "kevinrob/guzzle-cache-middleware": "1.4.1" + php composer.phar require "guzzle/guzzle:dev-master#ecb935d2d0ecd8cddae4dcfb90515cac5e2cb023" 1. After installing, require Composer's autoloader: require 'vendor/autoload.php'; -2. Create a new LDClient with your SDK key: +1. Create a new LDClient with your SDK key: $client = new LaunchDarkly\LDClient("your_sdk_key"); @@ -39,42 +42,66 @@ Your first feature flag Fetching flags -------------- -There are two approaches to fetching the flag rules from LaunchDarkly: +There are two distinct methods of integrating LaunchDarkly in a PHP environment. + +* [Guzzle](https://github.com/launchdarkly/guzzle3) with its [cache plugin](https://github.com/launchdarkly/guzzle3/blob/master/docs/plugins/cache-plugin.rst) to request and cache HTTP responses in an in-memory array (default) -- note, this is a forked older version of Guzzle for PHP 5.3 compatibility +* [ld-relay](https://github.com/launchdarkly/ld-relay) to retrieve and store flags in Redis (recommended) -* Making HTTP requests (using Guzzle) -* Setting up the [ld-daemon](https://github.com/launchdarkly/ld-daemon) to store the flags in Redis +We strongly recommend using the ld-relay. Per-flag caching (Guzzle method) is only intended for low-throughput environments. Using Guzzle ============ -To use Guzzle it must be required as a dependency: +Require Guzzle as a dependency: - php composer.phar require "guzzlehttp/guzzle:6.2.1" - php composer.phar require "kevinrob/guzzle-cache-middleware": "1.4.1" + php composer.phar require "guzzle/guzzle:dev-master#ecb935d2d0ecd8cddae4dcfb90515cac5e2cb023" It will then be used as the default way of fetching flags. -Using Redis -=========== +With Guzzle, you could persist your cache somewhere other than the default in-memory store, like Memcached or Redis. You could then specify your cache when initializing the client with the [cache option](https://github.com/launchdarkly/php-client/blob/master/src/LaunchDarkly/LDClient.php#L44). -1. Require Predis as a dependency: + $client = new LaunchDarkly\LDClient("YOUR_SDK_KEY", array("cache_storage" => $cacheStorage)); - php composer.phar require "predis/predis:1.0.*" +Using LD-Relay +============== -2. Create the LDClient with the Redis feature requester as an option: +The LaunchDarkly Relay Proxy ([ld-relay](https://github.com/launchdarkly/ld-relay)) consumes the LaunchDarkly streaming API and can update +a Redis cache operating in your production environment. The ld-relay offers many benefits such as performance and feature flag consistency. With PHP applications, we strongly recommend setting up ld-relay with a Redis store. - $client = new LaunchDarkly\LDClient("your_sdk_key", ['feature_requester_class' => 'LaunchDarkly\LDDFeatureRequester']); +1. Set up ld-relay in [daemon-mode](https://github.com/launchdarkly/ld-relay#redis-storage-and-daemon-mode) with Redis -Learn more ------------ +2. Require Predis as a dependency: -Check out our [documentation](http://docs.launchdarkly.com) for in-depth instructions on configuring and using LaunchDarkly. You can also head straight to the [complete reference guide for this SDK](http://docs.launchdarkly.com/docs/php-sdk-reference). + php composer.phar require "predis/predis:1.0.*" + +3. Create the LDClient with the Redis feature requester as an option: + + $client = new LaunchDarkly\LDClient("your_sdk_key", [ + 'feature_requester_class' => 'LaunchDarkly\LDDFeatureRequester', + 'redis_host' => 'your.redis.host', + 'redis_port' => 6379 + ]); + +4. If ld-relay is configured for [event forwarding](https://github.com/launchdarkly/ld-relay#event-forwarding), you can configure the LDClient to publish events to ld-relay instead of directly to `events.launchdarkly.com`. Using `GuzzleEventPublisher` with ld-relay event forwarding can be an efficient alternative to the default `curl`-based event publishing. + + $client = new LaunchDarkly\LDClient("your_sdk_key", [ + 'event_publisher_class' => 'LaunchDarkly\GuzzleEventPublisher', + 'events_uri' => 'http://your-ldrelay-host:8030', + 'feature_requester_class' => 'LaunchDarkly\LDDFeatureRequester', + 'redis_host' => 'your.redis.host', + 'redis_port' => 6379 + ]); Testing ------- We run integration tests for all our SDKs using a centralized test harness. This approach gives us the ability to test for consistency across SDKs, as well as test networking behavior in a long-running application. These tests cover each method in the SDK, and verify that event sending, flag evaluation, stream reconnection, and other aspects of the SDK all behave correctly. +Learn more +----------- + +Check out our [documentation](http://docs.launchdarkly.com) for in-depth instructions on configuring and using LaunchDarkly. You can also head straight to the [complete reference guide for this SDK](http://docs.launchdarkly.com/docs/php-sdk-reference). + Contributing ------------ @@ -90,19 +117,19 @@ About LaunchDarkly * Grant access to certain features based on user attributes, like payment plan (eg: users on the ‘gold’ plan get access to more features than users in the ‘silver’ plan). Disable parts of your application to facilitate maintenance, without taking everything offline. * LaunchDarkly provides feature flag SDKs for * [Java](http://docs.launchdarkly.com/docs/java-sdk-reference "Java SDK") - * [JavaScript] (http://docs.launchdarkly.com/docs/js-sdk-reference "LaunchDarkly JavaScript SDK") - * [PHP] (http://docs.launchdarkly.com/docs/php-sdk-reference "LaunchDarkly PHP SDK") - * [Python] (http://docs.launchdarkly.com/docs/python-sdk-reference "LaunchDarkly Python SDK") - * [Go] (http://docs.launchdarkly.com/docs/go-sdk-reference "LaunchDarkly Go SDK") - * [Node.JS] (http://docs.launchdarkly.com/docs/node-sdk-reference "LaunchDarkly Node SDK") - * [.NET] (http://docs.launchdarkly.com/docs/dotnet-sdk-reference "LaunchDarkly .Net SDK") - * [Ruby] (http://docs.launchdarkly.com/docs/ruby-sdk-reference "LaunchDarkly Ruby SDK") - * [Python Twisted] (http://docs.launchdarkly.com/docs/python-twisted "LaunchDarkly Python Twisted SDK") + * [JavaScript](http://docs.launchdarkly.com/docs/js-sdk-reference "LaunchDarkly JavaScript SDK") + * [PHP](http://docs.launchdarkly.com/docs/php-sdk-reference "LaunchDarkly PHP SDK") + * [Python](http://docs.launchdarkly.com/docs/python-sdk-reference "LaunchDarkly Python SDK") + * [Python Twisted](http://docs.launchdarkly.com/docs/python-twisted-sdk-reference "LaunchDarkly Python Twisted SDK") + * [Go](http://docs.launchdarkly.com/docs/go-sdk-reference "LaunchDarkly Go SDK") + * [Node.JS](http://docs.launchdarkly.com/docs/node-sdk-reference "LaunchDarkly Node SDK") + * [.NET](http://docs.launchdarkly.com/docs/dotnet-sdk-reference "LaunchDarkly .Net SDK") + * [Ruby](http://docs.launchdarkly.com/docs/ruby-sdk-reference "LaunchDarkly Ruby SDK") + * [iOS](http://docs.launchdarkly.com/docs/ios-sdk-reference "LaunchDarkly iOS SDK") + * [Android](http://docs.launchdarkly.com/docs/android-sdk-reference "LaunchDarkly Android SDK") * Explore LaunchDarkly - * [www.launchdarkly.com] (http://www.launchdarkly.com/ "LaunchDarkly Main Website") for more information - * [docs.launchdarkly.com] (http://docs.launchdarkly.com/ "LaunchDarkly Documentation") for our documentation and SDKs - * [apidocs.launchdarkly.com] (http://apidocs.launchdarkly.com/ "LaunchDarkly API Documentation") for our API documentation - * [blog.launchdarkly.com] (http://blog.launchdarkly.com/ "LaunchDarkly Blog Documentation") for the latest product updates - - - + * [launchdarkly.com](http://www.launchdarkly.com/ "LaunchDarkly Main Website") for more information + * [docs.launchdarkly.com](http://docs.launchdarkly.com/ "LaunchDarkly Documentation") for our documentation and SDKs + * [apidocs.launchdarkly.com](http://apidocs.launchdarkly.com/ "LaunchDarkly API Documentation") for our API documentation + * [blog.launchdarkly.com](http://blog.launchdarkly.com/ "LaunchDarkly Blog Documentation") for the latest product updates + * [Feature Flagging Guide](https://github.com/launchdarkly/featureflags/ "Feature Flagging Guide") for best practices and strategies diff --git a/VERSION b/VERSION index 359a5b952..197c4d5c2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.0.0 \ No newline at end of file +2.4.0 diff --git a/circle.yml b/circle.yml index ad6dd4f54..a81ace970 100644 --- a/circle.yml +++ b/circle.yml @@ -1,10 +1,15 @@ machine: php: version: 5.3.25 + services: + - redis + - docker test: override: # syntax check php files - find ./src -name "*.php" -type f -print0 | xargs -0 -I {} php -l {} - find ./tests -name "*.php" -type f -print0 | xargs -0 -I {} php -l {} - - vendor/bin/phpunit tests + + - vendor/bin/phpunit tests --coverage-text + - vendor/bin/phpunit integration-tests/LDDFeatureRequesterTest.php diff --git a/composer.json b/composer.json index 621414dce..8e56a5722 100644 --- a/composer.json +++ b/composer.json @@ -20,20 +20,20 @@ } ], "require": { + "monolog/monolog": "^1.6", "php": "~5.3", - "guzzle/guzzle": "dev-master#ecb935d2d0ecd8cddae4dcfb90515cac5e2cb023", - "psr/log": "1.0.0", + "psr/log": "^1.0", "justinrainbow/json-schema": "v1.6.0" }, "require-dev": { - "phpunit/phpunit": "4.8.26", - "phpdocumentor/phpdocumentor": "2.*", - "predis/predis": "1.0.*", - "zendframework/zend-serializer": "2.4.*" + "guzzle/guzzle": "dev-master#ecb935d2d0ecd8cddae4dcfb90515cac5e2cb023", + "phpunit/phpunit": ">=4.8.26 <5.0", + "phpdocumentor/phpdocumentor": "^2.0", + "predis/predis": "^1.0" }, "suggested": { - "monolog/monolog": "1.21.0", - "predis/predis": "1.0.*" + "guzzle/guzzle": "(^dev-master#ecb935d2d0ecd8cddae4dcfb90515cac5e2cb023) Required when using GuzzleEventPublisher or the default FeatureRequester", + "predis/predis": "(^1.0) Required when using LDDFeatureRequester" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 46414fb6e..00fc772cd 100644 --- a/composer.lock +++ b/composer.lock @@ -4,102 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "30633a946a2f7c18c7cd3bd7492dadc4", - "content-hash": "3a1f29c4c3c6857e353d5e518bc9db53", + "content-hash": "ff69af1388803e7a7ad3f373098efd0f", "packages": [ - { - "name": "guzzle/guzzle", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/launchdarkly/guzzle3.git", - "reference": "ecb935d2d0ecd8cddae4dcfb90515cac5e2cb023" - }, - "require": { - "ext-curl": "*", - "php": ">=5.3.3", - "symfony/event-dispatcher": "~2.1" - }, - "replace": { - "guzzle/batch": "self.version", - "guzzle/cache": "self.version", - "guzzle/common": "self.version", - "guzzle/http": "self.version", - "guzzle/inflection": "self.version", - "guzzle/iterator": "self.version", - "guzzle/log": "self.version", - "guzzle/parser": "self.version", - "guzzle/plugin": "self.version", - "guzzle/plugin-async": "self.version", - "guzzle/plugin-backoff": "self.version", - "guzzle/plugin-cache": "self.version", - "guzzle/plugin-cookie": "self.version", - "guzzle/plugin-curlauth": "self.version", - "guzzle/plugin-error-response": "self.version", - "guzzle/plugin-history": "self.version", - "guzzle/plugin-log": "self.version", - "guzzle/plugin-md5": "self.version", - "guzzle/plugin-mock": "self.version", - "guzzle/plugin-oauth": "self.version", - "guzzle/service": "self.version", - "guzzle/stream": "self.version" - }, - "require-dev": { - "doctrine/cache": "~1.3", - "monolog/monolog": "~1.0", - "phpunit/phpunit": "3.7.*", - "psr/log": "~1.0", - "symfony/class-loader": "~2.1", - "zendframework/zend-cache": "2.*,<2.3", - "zendframework/zend-log": "2.*,<2.3" - }, - "suggest": { - "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.9-dev" - } - }, - "autoload": { - "psr-0": { - "Guzzle": "src/", - "Guzzle\\Tests": "tests/" - } - }, - "scripts": { - "test": [ - "phpunit" - ] - }, - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Guzzle Community", - "homepage": "https://github.com/guzzle/guzzle/contributors" - } - ], - "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", - "homepage": "http://guzzlephp.org/", - "keywords": [ - "HTTP client", - "client", - "curl", - "framework", - "http", - "rest", - "web service" - ], - "time": "2015-12-08 23:21:31" - }, { "name": "justinrainbow/json-schema", "version": "v1.6.0", @@ -164,26 +70,64 @@ "json", "schema" ], - "time": "2016-01-06 14:37:04" + "time": "2016-01-06T14:37:04+00:00" }, { - "name": "psr/log", - "version": "1.0.0", + "name": "monolog/monolog", + "version": "1.23.0", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" + "url": "https://github.com/Seldaek/monolog.git", + "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", - "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4", + "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4", "shasum": "" }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "2.3.0", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "^5.3|^6.0" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" + }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, "autoload": { - "psr-0": { - "Psr\\Log\\": "" + "psr-4": { + "Monolog\\": "src/Monolog" } }, "notification-url": "https://packagist.org/downloads/", @@ -192,59 +136,47 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" } ], - "description": "Common interface for logging libraries", + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", "keywords": [ "log", - "psr", + "logging", "psr-3" ], - "time": "2012-12-21 11:40:51" + "time": "2017-06-19T01:22:40+00:00" }, { - "name": "symfony/event-dispatcher", - "version": "v2.8.14", + "name": "psr/log", + "version": "1.0.2", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "25c576abd4e0f212e678fe8b2bd9a9a98c7ea934" + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/25c576abd4e0f212e678fe8b2bd9a9a98c7ea934", - "reference": "25c576abd4e0f212e678fe8b2bd9a9a98c7ea934", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", "shasum": "" }, "require": { - "php": ">=5.3.9" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~2.0,>=2.0.5|~3.0.0", - "symfony/dependency-injection": "~2.6|~3.0.0", - "symfony/expression-language": "~2.6|~3.0.0", - "symfony/stopwatch": "~2.3|~3.0.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" + "php": ">=5.3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Psr\\Log\\": "Psr/Log/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -252,17 +184,18 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" } ], - "description": "Symfony EventDispatcher Component", - "homepage": "https://symfony.com", - "time": "2016-10-13 01:43:15" + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2016-10-10T12:19:37+00:00" } ], "packages-dev": [ @@ -323,7 +256,7 @@ "cli", "microframework" ], - "time": "2014-03-29 14:03:13" + "time": "2014-03-29T14:03:13+00:00" }, { "name": "cilex/console-service-provider", @@ -382,7 +315,7 @@ "service-provider", "silex" ], - "time": "2012-12-19 10:50:58" + "time": "2012-12-19T10:50:58+00:00" }, { "name": "doctrine/annotations", @@ -450,7 +383,7 @@ "docblock", "parser" ], - "time": "2015-08-31 12:32:49" + "time": "2015-08-31T12:32:49+00:00" }, { "name": "doctrine/instantiator", @@ -504,7 +437,7 @@ "constructor", "instantiate" ], - "time": "2015-06-14 21:17:01" + "time": "2015-06-14T21:17:01+00:00" }, { "name": "doctrine/lexer", @@ -558,25 +491,28 @@ "lexer", "parser" ], - "time": "2014-09-09 13:34:57" + "time": "2014-09-09T13:34:57+00:00" }, { "name": "erusev/parsedown", - "version": "1.6.1", + "version": "1.6.4", "source": { "type": "git", "url": "https://github.com/erusev/parsedown.git", - "reference": "20ff8bbb57205368b4b42d094642a3e52dac85fb" + "reference": "fbe3fe878f4fe69048bb8a52783a09802004f548" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/20ff8bbb57205368b4b42d094642a3e52dac85fb", - "reference": "20ff8bbb57205368b4b42d094642a3e52dac85fb", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/fbe3fe878f4fe69048bb8a52783a09802004f548", + "reference": "fbe3fe878f4fe69048bb8a52783a09802004f548", "shasum": "" }, "require": { "php": ">=5.3.0" }, + "require-dev": { + "phpunit/phpunit": "^4.8.35" + }, "type": "library", "autoload": { "psr-0": { @@ -600,19 +536,112 @@ "markdown", "parser" ], - "time": "2016-11-02 15:56:58" + "time": "2017-11-14T20:44:03+00:00" + }, + { + "name": "guzzle/guzzle", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/launchdarkly/guzzle3.git", + "reference": "ecb935d2d0ecd8cddae4dcfb90515cac5e2cb023" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": "~2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "~1.3", + "monolog/monolog": "~1.0", + "phpunit/phpunit": "3.7.*", + "psr/log": "~1.0", + "symfony/class-loader": "~2.1", + "zendframework/zend-cache": "2.*,<2.3", + "zendframework/zend-log": "2.*,<2.3" + }, + "suggest": { + "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.9-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "scripts": { + "test": [ + "phpunit" + ] + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "HTTP client", + "client", + "curl", + "framework", + "http", + "rest", + "web service" + ], + "time": "2015-12-08T23:21:31+00:00" }, { "name": "herrera-io/json", "version": "1.0.3", "source": { "type": "git", - "url": "https://github.com/kherge-abandoned/php-json.git", + "url": "https://github.com/kherge-php/json.git", "reference": "60c696c9370a1e5136816ca557c17f82a6fa83f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/kherge-abandoned/php-json/zipball/60c696c9370a1e5136816ca557c17f82a6fa83f1", + "url": "https://api.github.com/repos/kherge-php/json/zipball/60c696c9370a1e5136816ca557c17f82a6fa83f1", "reference": "60c696c9370a1e5136816ca557c17f82a6fa83f1", "shasum": "" }, @@ -660,7 +689,8 @@ "schema", "validate" ], - "time": "2013-10-30 16:51:34" + "abandoned": "kherge/json", + "time": "2013-10-30T16:51:34+00:00" }, { "name": "herrera-io/phar-update", @@ -717,27 +747,29 @@ "phar", "update" ], - "time": "2013-10-30 17:23:01" + "abandoned": true, + "time": "2013-10-30T17:23:01+00:00" }, { "name": "jms/metadata", - "version": "1.5.1", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/schmittjoh/metadata.git", - "reference": "22b72455559a25777cfd28c4ffda81ff7639f353" + "reference": "6a06970a10e0a532fb52d3959547123b84a3b3ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/22b72455559a25777cfd28c4ffda81ff7639f353", - "reference": "22b72455559a25777cfd28c4ffda81ff7639f353", + "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/6a06970a10e0a532fb52d3959547123b84a3b3ab", + "reference": "6a06970a10e0a532fb52d3959547123b84a3b3ab", "shasum": "" }, "require": { "php": ">=5.3.0" }, "require-dev": { - "doctrine/cache": "~1.0" + "doctrine/cache": "~1.0", + "symfony/cache": "~3.1" }, "type": "library", "extra": { @@ -752,14 +784,12 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache" + "Apache-2.0" ], "authors": [ { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com", - "homepage": "https://github.com/schmittjoh", - "role": "Developer of wrapped JMSSerializerBundle" + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" } ], "description": "Class/method/property metadata management in PHP", @@ -769,7 +799,7 @@ "xml", "yaml" ], - "time": "2014-07-12 07:13:19" + "time": "2016-12-05T10:18:33+00:00" }, { "name": "jms/parser-lib", @@ -804,7 +834,7 @@ "Apache2" ], "description": "A library for easily creating recursive-descent parsers.", - "time": "2012-11-18 18:08:43" + "time": "2012-11-18T18:08:43+00:00" }, { "name": "jms/serializer", @@ -874,7 +904,7 @@ "serialization", "xml" ], - "time": "2014-03-18 08:39:00" + "time": "2014-03-18T08:39:00+00:00" }, { "name": "kherge/version", @@ -916,85 +946,8 @@ ], "description": "A parsing and comparison library for semantic versioning.", "homepage": "http://github.com/kherge/Version", - "time": "2012-08-16 17:13:03" - }, - { - "name": "monolog/monolog", - "version": "1.21.0", - "source": { - "type": "git", - "url": "https://github.com/Seldaek/monolog.git", - "reference": "f42fbdfd53e306bda545845e4dbfd3e72edb4952" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f42fbdfd53e306bda545845e4dbfd3e72edb4952", - "reference": "f42fbdfd53e306bda545845e4dbfd3e72edb4952", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "psr/log": "~1.0" - }, - "provide": { - "psr/log-implementation": "1.0.0" - }, - "require-dev": { - "aws/aws-sdk-php": "^2.4.9", - "doctrine/couchdb": "~1.0@dev", - "graylog2/gelf-php": "~1.0", - "jakub-onderka/php-parallel-lint": "0.9", - "php-amqplib/php-amqplib": "~2.4", - "php-console/php-console": "^3.1.3", - "phpunit/phpunit": "~4.5", - "phpunit/phpunit-mock-objects": "2.3.0", - "ruflin/elastica": ">=0.90 <3.0", - "sentry/sentry": "^0.13", - "swiftmailer/swiftmailer": "~5.3" - }, - "suggest": { - "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", - "doctrine/couchdb": "Allow sending log messages to a CouchDB server", - "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-mongo": "Allow sending log messages to a MongoDB server", - "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", - "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", - "php-console/php-console": "Allow sending log messages to Google Chrome", - "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server", - "sentry/sentry": "Allow sending log messages to a Sentry server" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Monolog\\": "src/Monolog" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "Sends your logs to files, sockets, inboxes, databases and various web services", - "homepage": "http://github.com/Seldaek/monolog", - "keywords": [ - "log", - "logging", - "psr-3" - ], - "time": "2016-07-29 03:23:52" + "abandoned": true, + "time": "2012-08-16T17:13:03+00:00" }, { "name": "nikic/php-parser", @@ -1039,7 +992,7 @@ "parser", "php" ], - "time": "2015-09-19 14:15:08" + "time": "2015-09-19T14:15:08+00:00" }, { "name": "phpcollection/phpcollection", @@ -1087,7 +1040,7 @@ "sequence", "set" ], - "time": "2015-05-17 12:39:23" + "time": "2015-05-17T12:39:23+00:00" }, { "name": "phpdocumentor/fileset", @@ -1130,7 +1083,7 @@ "fileset", "phpdoc" ], - "time": "2013-08-06 21:07:42" + "time": "2013-08-06T21:07:42+00:00" }, { "name": "phpdocumentor/graphviz", @@ -1171,7 +1124,7 @@ "email": "mike.vanriel@naenius.com" } ], - "time": "2016-02-02 13:00:08" + "time": "2016-02-02T13:00:08+00:00" }, { "name": "phpdocumentor/phpdocumentor", @@ -1260,7 +1213,7 @@ "documentation", "phpdoc" ], - "time": "2016-05-22 09:50:56" + "time": "2016-05-22T09:50:56+00:00" }, { "name": "phpdocumentor/reflection", @@ -1314,20 +1267,20 @@ "reflection", "static analysis" ], - "time": "2016-05-21 08:42:32" + "time": "2016-05-21T08:42:32+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "2.0.4", + "version": "2.0.5", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" + "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", - "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e6a969a640b00d8daa3c66518b0405fb41ae0c4b", + "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b", "shasum": "" }, "require": { @@ -1363,7 +1316,7 @@ "email": "mike.vanriel@naenius.com" } ], - "time": "2015-02-03 12:10:50" + "time": "2016-01-25T08:17:30+00:00" }, { "name": "phpoption/phpoption", @@ -1413,37 +1366,37 @@ "php", "type" ], - "time": "2015-07-25 16:39:46" + "time": "2015-07-25T16:39:46+00:00" }, { "name": "phpspec/prophecy", - "version": "v1.6.2", + "version": "1.7.3", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "6c52c2722f8460122f96f86346600e1077ce22cb" + "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/6c52c2722f8460122f96f86346600e1077ce22cb", - "reference": "6c52c2722f8460122f96f86346600e1077ce22cb", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", + "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", - "sebastian/comparator": "^1.1", - "sebastian/recursion-context": "^1.0|^2.0" + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" }, "require-dev": { - "phpspec/phpspec": "^2.0", - "phpunit/phpunit": "^4.8 || ^5.6.5" + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.6.x-dev" + "dev-master": "1.7.x-dev" } }, "autoload": { @@ -1476,7 +1429,7 @@ "spy", "stub" ], - "time": "2016-11-21 14:58:47" + "time": "2017-11-24T13:59:53+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1538,20 +1491,20 @@ "testing", "xunit" ], - "time": "2015-10-06 15:47:00" + "time": "2015-10-06T15:47:00+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "1.4.1", + "version": "1.4.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", - "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", "shasum": "" }, "require": { @@ -1585,7 +1538,7 @@ "filesystem", "iterator" ], - "time": "2015-06-21 13:08:43" + "time": "2017-11-27T13:52:08+00:00" }, { "name": "phpunit/php-text-template", @@ -1626,29 +1579,34 @@ "keywords": [ "template" ], - "time": "2015-06-21 13:50:34" + "time": "2015-06-21T13:50:34+00:00" }, { "name": "phpunit/php-timer", - "version": "1.0.8", + "version": "1.0.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260" + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260", - "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^5.3.3 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "~4|~5" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, "autoload": { "classmap": [ "src/" @@ -1670,20 +1628,20 @@ "keywords": [ "timer" ], - "time": "2016-05-12 18:03:57" + "time": "2017-02-26T11:10:40+00:00" }, { "name": "phpunit/php-token-stream", - "version": "1.4.9", + "version": "1.4.12", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b" + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3b402f65a4cc90abf6e1104e388b896ce209631b", - "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16", "shasum": "" }, "require": { @@ -1719,20 +1677,20 @@ "keywords": [ "tokenizer" ], - "time": "2016-11-15 14:06:22" + "time": "2017-12-04T08:55:13+00:00" }, { "name": "phpunit/phpunit", - "version": "4.8.26", + "version": "4.8.36", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74" + "reference": "46023de9a91eec7dfb06cc56cb4e260017298517" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc1d8cd5b5de11625979125c5639347896ac2c74", - "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/46023de9a91eec7dfb06cc56cb4e260017298517", + "reference": "46023de9a91eec7dfb06cc56cb4e260017298517", "shasum": "" }, "require": { @@ -1748,7 +1706,7 @@ "phpunit/php-text-template": "~1.2", "phpunit/php-timer": "^1.0.6", "phpunit/phpunit-mock-objects": "~2.3", - "sebastian/comparator": "~1.1", + "sebastian/comparator": "~1.2.2", "sebastian/diff": "~1.2", "sebastian/environment": "~1.3", "sebastian/exporter": "~1.2", @@ -1791,7 +1749,7 @@ "testing", "xunit" ], - "time": "2016-05-17 03:09:28" + "time": "2017-06-21T08:07:12+00:00" }, { "name": "phpunit/phpunit-mock-objects", @@ -1847,7 +1805,7 @@ "mock", "xunit" ], - "time": "2015-10-02 06:51:40" + "time": "2015-10-02T06:51:40+00:00" }, { "name": "pimple/pimple", @@ -1893,24 +1851,24 @@ "container", "dependency injection" ], - "time": "2013-11-22 08:30:29" + "time": "2013-11-22T08:30:29+00:00" }, { "name": "predis/predis", - "version": "v1.0.4", + "version": "v1.1.1", "source": { "type": "git", "url": "https://github.com/nrk/predis.git", - "reference": "9ead747663bb1b1ae017dfa0d152aca87792b42f" + "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nrk/predis/zipball/9ead747663bb1b1ae017dfa0d152aca87792b42f", - "reference": "9ead747663bb1b1ae017dfa0d152aca87792b42f", + "url": "https://api.github.com/repos/nrk/predis/zipball/f0210e38881631afeafb56ab43405a92cafd9fd1", + "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1", "shasum": "" }, "require": { - "php": ">=5.3.2" + "php": ">=5.3.9" }, "require-dev": { "phpunit/phpunit": "~4.8" @@ -1943,20 +1901,20 @@ "predis", "redis" ], - "time": "2016-05-30 15:25:52" + "time": "2016-06-16T16:22:20+00:00" }, { "name": "sebastian/comparator", - "version": "1.2.2", + "version": "1.2.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "6a1ed12e8b2409076ab22e3897126211ff8b1f7f" + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/6a1ed12e8b2409076ab22e3897126211ff8b1f7f", - "reference": "6a1ed12e8b2409076ab22e3897126211ff8b1f7f", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", "shasum": "" }, "require": { @@ -2007,27 +1965,27 @@ "compare", "equality" ], - "time": "2016-11-19 09:18:40" + "time": "2017-01-29T09:50:25+00:00" }, { "name": "sebastian/diff", - "version": "1.4.1", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^5.3.3 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.8" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" }, "type": "library", "extra": { @@ -2059,7 +2017,7 @@ "keywords": [ "diff" ], - "time": "2015-12-08 07:14:41" + "time": "2017-05-22T07:24:03+00:00" }, { "name": "sebastian/environment", @@ -2109,7 +2067,7 @@ "environment", "hhvm" ], - "time": "2016-08-18 05:49:44" + "time": "2016-08-18T05:49:44+00:00" }, { "name": "sebastian/exporter", @@ -2176,7 +2134,7 @@ "export", "exporter" ], - "time": "2016-06-17 09:04:28" + "time": "2016-06-17T09:04:28+00:00" }, { "name": "sebastian/global-state", @@ -2227,20 +2185,20 @@ "keywords": [ "global state" ], - "time": "2015-10-12 03:26:01" + "time": "2015-10-12T03:26:01+00:00" }, { "name": "sebastian/recursion-context", - "version": "1.0.2", + "version": "1.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "913401df809e99e4f47b27cdd781f4a258d58791" + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", - "reference": "913401df809e99e4f47b27cdd781f4a258d58791", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7", "shasum": "" }, "require": { @@ -2280,7 +2238,7 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2015-11-11 19:50:13" + "time": "2016-10-03T07:41:43+00:00" }, { "name": "sebastian/version", @@ -2315,25 +2273,28 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2015-06-21 13:59:46" + "time": "2015-06-21T13:59:46+00:00" }, { "name": "seld/jsonlint", - "version": "1.5.0", + "version": "1.6.2", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "19495c181d6d53a0a13414154e52817e3b504189" + "reference": "7a30649c67ee0d19faacfd9fa2cfb6cc032d9b19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/19495c181d6d53a0a13414154e52817e3b504189", - "reference": "19495c181d6d53a0a13414154e52817e3b504189", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/7a30649c67ee0d19faacfd9fa2cfb6cc032d9b19", + "reference": "7a30649c67ee0d19faacfd9fa2cfb6cc032d9b19", "shasum": "" }, "require": { "php": "^5.3 || ^7.0" }, + "require-dev": { + "phpunit/phpunit": "^4.5" + }, "bin": [ "bin/jsonlint" ], @@ -2361,26 +2322,29 @@ "parser", "validator" ], - "time": "2016-11-14 17:59:58" + "time": "2017-11-30T15:34:22+00:00" }, { "name": "symfony/config", - "version": "v2.8.14", + "version": "v2.8.32", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "1361bc4e66f97b6202ae83f4190e962c624b5e61" + "reference": "f4f3f1d7090c464434bbbc3e8aa2b41149c59196" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/1361bc4e66f97b6202ae83f4190e962c624b5e61", - "reference": "1361bc4e66f97b6202ae83f4190e962c624b5e61", + "url": "https://api.github.com/repos/symfony/config/zipball/f4f3f1d7090c464434bbbc3e8aa2b41149c59196", + "reference": "f4f3f1d7090c464434bbbc3e8aa2b41149c59196", "shasum": "" }, "require": { "php": ">=5.3.9", "symfony/filesystem": "~2.3|~3.0.0" }, + "require-dev": { + "symfony/yaml": "~2.7|~3.0.0" + }, "suggest": { "symfony/yaml": "To use the yaml reference dumper" }, @@ -2414,25 +2378,25 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2016-11-03 07:52:58" + "time": "2017-11-07T11:56:23+00:00" }, { "name": "symfony/console", - "version": "v2.8.14", + "version": "v2.8.32", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "a871ba00e0f604dceac64c56c27f99fbeaf4854e" + "reference": "46270f1ca44f08ebc134ce120fd2c2baf5fd63de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/a871ba00e0f604dceac64c56c27f99fbeaf4854e", - "reference": "a871ba00e0f604dceac64c56c27f99fbeaf4854e", + "url": "https://api.github.com/repos/symfony/console/zipball/46270f1ca44f08ebc134ce120fd2c2baf5fd63de", + "reference": "46270f1ca44f08ebc134ce120fd2c2baf5fd63de", "shasum": "" }, "require": { "php": ">=5.3.9", - "symfony/debug": "~2.7,>=2.7.2|~3.0.0", + "symfony/debug": "^2.7.2|~3.0.0", "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { @@ -2475,20 +2439,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2016-11-15 23:02:12" + "time": "2017-11-29T09:33:18+00:00" }, { "name": "symfony/debug", - "version": "v2.8.14", + "version": "v2.8.32", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "62a68f640456f6761d752c62d81631428ef0d8a1" + "reference": "e72a0340dc2e273b3c4398d8eef9157ba51d8b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/62a68f640456f6761d752c62d81631428ef0d8a1", - "reference": "62a68f640456f6761d752c62d81631428ef0d8a1", + "url": "https://api.github.com/repos/symfony/debug/zipball/e72a0340dc2e273b3c4398d8eef9157ba51d8b95", + "reference": "e72a0340dc2e273b3c4398d8eef9157ba51d8b95", "shasum": "" }, "require": { @@ -2500,7 +2464,7 @@ }, "require-dev": { "symfony/class-loader": "~2.2|~3.0.0", - "symfony/http-kernel": "~2.3.24|~2.5.9|~2.6,>=2.6.2|~3.0.0" + "symfony/http-kernel": "~2.3.24|~2.5.9|^2.6.2|~3.0.0" }, "type": "library", "extra": { @@ -2532,20 +2496,80 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2016-11-15 12:53:17" + "time": "2017-11-19T19:05:05+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.8.32", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "b59aacf238fadda50d612c9de73b74751872a903" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b59aacf238fadda50d612c9de73b74751872a903", + "reference": "b59aacf238fadda50d612c9de73b74751872a903", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2017-11-05T15:25:56+00:00" }, { "name": "symfony/filesystem", - "version": "v2.8.14", + "version": "v2.8.32", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "a3784111af9f95f102b6411548376e1ae7c93898" + "reference": "15ceb6736a9eebd0d99f9e05a62296ab6ce1cf2b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/a3784111af9f95f102b6411548376e1ae7c93898", - "reference": "a3784111af9f95f102b6411548376e1ae7c93898", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/15ceb6736a9eebd0d99f9e05a62296ab6ce1cf2b", + "reference": "15ceb6736a9eebd0d99f9e05a62296ab6ce1cf2b", "shasum": "" }, "require": { @@ -2581,20 +2605,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2016-10-18 04:28:30" + "time": "2017-11-19T18:39:05+00:00" }, { "name": "symfony/finder", - "version": "v2.8.14", + "version": "v2.8.32", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "0023b024363dfc0cd21262e556f25a291fe8d7fd" + "reference": "efeceae6a05a9b2fcb3391333f1d4a828ff44ab8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/0023b024363dfc0cd21262e556f25a291fe8d7fd", - "reference": "0023b024363dfc0cd21262e556f25a291fe8d7fd", + "url": "https://api.github.com/repos/symfony/finder/zipball/efeceae6a05a9b2fcb3391333f1d4a828ff44ab8", + "reference": "efeceae6a05a9b2fcb3391333f1d4a828ff44ab8", "shasum": "" }, "require": { @@ -2630,20 +2654,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2016-11-03 07:52:58" + "time": "2017-11-05T15:25:56+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.3.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4" + "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4", - "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", + "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", "shasum": "" }, "require": { @@ -2655,7 +2679,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -2689,20 +2713,20 @@ "portable", "shim" ], - "time": "2016-11-14 01:06:16" + "time": "2017-10-11T12:05:26+00:00" }, { "name": "symfony/process", - "version": "v2.8.14", + "version": "v2.8.32", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "024de37f8a6b9e5e8244d9eb3fcf3e467dd2a93f" + "reference": "d25449e031f600807949aab7cadbf267712f4eee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/024de37f8a6b9e5e8244d9eb3fcf3e467dd2a93f", - "reference": "024de37f8a6b9e5e8244d9eb3fcf3e467dd2a93f", + "url": "https://api.github.com/repos/symfony/process/zipball/d25449e031f600807949aab7cadbf267712f4eee", + "reference": "d25449e031f600807949aab7cadbf267712f4eee", "shasum": "" }, "require": { @@ -2738,20 +2762,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2016-09-29 14:03:54" + "time": "2017-11-05T15:25:56+00:00" }, { "name": "symfony/stopwatch", - "version": "v2.8.14", + "version": "v2.8.32", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "35bae476693150728b0eb51647faac82faf9aaca" + "reference": "533bb9d7c2da1c6d2da163ecf0f22043ea98f59b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/35bae476693150728b0eb51647faac82faf9aaca", - "reference": "35bae476693150728b0eb51647faac82faf9aaca", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/533bb9d7c2da1c6d2da163ecf0f22043ea98f59b", + "reference": "533bb9d7c2da1c6d2da163ecf0f22043ea98f59b", "shasum": "" }, "require": { @@ -2787,20 +2811,20 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2016-06-29 05:29:29" + "time": "2017-11-10T18:59:36+00:00" }, { "name": "symfony/translation", - "version": "v2.8.14", + "version": "v2.8.32", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "edbe67e8f729885b55421d5cc58223d48540df07" + "reference": "0c63d56516c4c4c323228ca6348eadb7c91b1daf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/edbe67e8f729885b55421d5cc58223d48540df07", - "reference": "edbe67e8f729885b55421d5cc58223d48540df07", + "url": "https://api.github.com/repos/symfony/translation/zipball/0c63d56516c4c4c323228ca6348eadb7c91b1daf", + "reference": "0c63d56516c4c4c323228ca6348eadb7c91b1daf", "shasum": "" }, "require": { @@ -2813,7 +2837,7 @@ "require-dev": { "psr/log": "~1.0", "symfony/config": "~2.8", - "symfony/intl": "~2.4|~3.0.0", + "symfony/intl": "~2.7.25|^2.8.18|~3.2.5", "symfony/yaml": "~2.2|~3.0.0" }, "suggest": { @@ -2851,20 +2875,20 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2016-11-18 21:10:01" + "time": "2017-11-07T14:08:47+00:00" }, { "name": "symfony/validator", - "version": "v2.8.14", + "version": "v2.8.32", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "c4ef882117461a4151064ad16c5d638dd1c70b9e" + "reference": "d3f8bf19db07dde2ed3f4c43907bb18eb876a0c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/c4ef882117461a4151064ad16c5d638dd1c70b9e", - "reference": "c4ef882117461a4151064ad16c5d638dd1c70b9e", + "url": "https://api.github.com/repos/symfony/validator/zipball/d3f8bf19db07dde2ed3f4c43907bb18eb876a0c1", + "reference": "d3f8bf19db07dde2ed3f4c43907bb18eb876a0c1", "shasum": "" }, "require": { @@ -2875,13 +2899,13 @@ "require-dev": { "doctrine/annotations": "~1.0", "doctrine/cache": "~1.0", - "egulias/email-validator": "~1.2,>=1.2.1", + "egulias/email-validator": "^1.2.1", "symfony/config": "~2.2|~3.0.0", "symfony/expression-language": "~2.4|~3.0.0", "symfony/http-foundation": "~2.3|~3.0.0", - "symfony/intl": "~2.7.4|~2.8|~3.0.0", + "symfony/intl": "~2.7.25|^2.8.18|~3.2.5", "symfony/property-access": "~2.3|~3.0.0", - "symfony/yaml": "~2.0,>=2.0.5|~3.0.0" + "symfony/yaml": "^2.0.5|~3.0.0" }, "suggest": { "doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.", @@ -2924,20 +2948,20 @@ ], "description": "Symfony Validator Component", "homepage": "https://symfony.com", - "time": "2016-11-18 21:10:01" + "time": "2017-12-04T12:15:49+00:00" }, { "name": "symfony/yaml", - "version": "v2.8.14", + "version": "v2.8.32", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "befb26a3713c97af90d25dd12e75621ef14d91ff" + "reference": "968ef42161e4bc04200119da473077f9e7015128" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/befb26a3713c97af90d25dd12e75621ef14d91ff", - "reference": "befb26a3713c97af90d25dd12e75621ef14d91ff", + "url": "https://api.github.com/repos/symfony/yaml/zipball/968ef42161e4bc04200119da473077f9e7015128", + "reference": "968ef42161e4bc04200119da473077f9e7015128", "shasum": "" }, "require": { @@ -2973,38 +2997,42 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2016-11-14 16:15:57" + "time": "2017-11-29T09:33:18+00:00" }, { "name": "twig/twig", - "version": "v1.28.1", + "version": "v1.35.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "fff80c4a7ae1d47a81dfec10c76cbcb939170b45" + "reference": "daa657073e55b0a78cce8fdd22682fddecc6385f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/fff80c4a7ae1d47a81dfec10c76cbcb939170b45", - "reference": "fff80c4a7ae1d47a81dfec10c76cbcb939170b45", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/daa657073e55b0a78cce8fdd22682fddecc6385f", + "reference": "daa657073e55b0a78cce8fdd22682fddecc6385f", "shasum": "" }, "require": { - "php": ">=5.2.7" + "php": ">=5.3.3" }, "require-dev": { + "psr/container": "^1.0", "symfony/debug": "~2.7", - "symfony/phpunit-bridge": "~2.7" + "symfony/phpunit-bridge": "~3.3@dev" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.28-dev" + "dev-master": "1.35-dev" } }, "autoload": { "psr-0": { "Twig_": "lib/" + }, + "psr-4": { + "Twig\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3034,34 +3062,33 @@ "keywords": [ "templating" ], - "time": "2016-11-19 05:52:49" + "time": "2017-09-27T18:06:46+00:00" }, { "name": "zendframework/zend-cache", - "version": "2.4.10", + "version": "2.5.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-cache.git", - "reference": "5c4e6231082f74ab3e4fd58927c867ef4c24d71f" + "reference": "5999e5a03f7dcf82abbbe67eea74da641f959684" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-cache/zipball/5c4e6231082f74ab3e4fd58927c867ef4c24d71f", - "reference": "5c4e6231082f74ab3e4fd58927c867ef4c24d71f", + "url": "https://api.github.com/repos/zendframework/zend-cache/zipball/5999e5a03f7dcf82abbbe67eea74da641f959684", + "reference": "5999e5a03f7dcf82abbbe67eea74da641f959684", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-eventmanager": "~2.4.0", - "zendframework/zend-serializer": "~2.4.0", - "zendframework/zend-servicemanager": "~2.4.0", - "zendframework/zend-stdlib": "~2.4.0" + "zendframework/zend-eventmanager": "~2.5", + "zendframework/zend-serializer": "~2.5", + "zendframework/zend-servicemanager": "~2.5", + "zendframework/zend-stdlib": "~2.5" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", - "satooshi/php-coveralls": "dev-master", - "zendframework/zend-session": "~2.4.0" + "zendframework/zend-session": "~2.5" }, "suggest": { "ext-apc": "APC >= 3.1.6 to use the APC storage adapter", @@ -3076,8 +3103,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.4-dev", - "dev-develop": "2.5-dev" + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" } }, "autoload": { @@ -3095,34 +3122,34 @@ "cache", "zf2" ], - "time": "2015-09-15 16:23:56" + "time": "2015-06-03T15:31:59+00:00" }, { "name": "zendframework/zend-config", - "version": "2.4.10", + "version": "2.5.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-config.git", - "reference": "6b879e54096b8e0d2290f7414c38f9a5947cb8ac" + "reference": "ec49b1df1bdd9772df09dc2f612fbfc279bf4c27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-config/zipball/6b879e54096b8e0d2290f7414c38f9a5947cb8ac", - "reference": "6b879e54096b8e0d2290f7414c38f9a5947cb8ac", + "url": "https://api.github.com/repos/zendframework/zend-config/zipball/ec49b1df1bdd9772df09dc2f612fbfc279bf4c27", + "reference": "ec49b1df1bdd9772df09dc2f612fbfc279bf4c27", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-stdlib": "self.version" + "zendframework/zend-stdlib": "~2.5" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", - "satooshi/php-coveralls": "dev-master", - "zendframework/zend-filter": "self.version", - "zendframework/zend-i18n": "self.version", - "zendframework/zend-json": "self.version", - "zendframework/zend-servicemanager": "self.version" + "zendframework/zend-filter": "~2.5", + "zendframework/zend-i18n": "~2.5", + "zendframework/zend-json": "~2.5", + "zendframework/zend-mvc": "~2.5", + "zendframework/zend-servicemanager": "~2.5" }, "suggest": { "zendframework/zend-filter": "Zend\\Filter component", @@ -3133,8 +3160,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.4-dev", - "dev-develop": "2.5-dev" + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" } }, "autoload": { @@ -3152,36 +3179,35 @@ "config", "zf2" ], - "time": "2015-05-07 14:55:31" + "time": "2015-06-03T15:32:00+00:00" }, { "name": "zendframework/zend-eventmanager", - "version": "2.4.10", + "version": "2.5.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-eventmanager.git", - "reference": "c2c46a7a2809b74ceb66fd79f66d43f97e1747b4" + "reference": "d94a16039144936f107f906896349900fd634443" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/c2c46a7a2809b74ceb66fd79f66d43f97e1747b4", - "reference": "c2c46a7a2809b74ceb66fd79f66d43f97e1747b4", + "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/d94a16039144936f107f906896349900fd634443", + "reference": "d94a16039144936f107f906896349900fd634443", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-stdlib": "self.version" + "zendframework/zend-stdlib": "~2.5" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0", - "satooshi/php-coveralls": "dev-master" + "phpunit/phpunit": "~4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.4-dev", - "dev-develop": "2.5-dev" + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" } }, "autoload": { @@ -3193,38 +3219,40 @@ "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-event-manager", + "homepage": "https://github.com/zendframework/zend-eventmanager", "keywords": [ "eventmanager", "zf2" ], - "time": "2015-05-07 14:55:31" + "time": "2015-06-03T15:32:01+00:00" }, { "name": "zendframework/zend-filter", - "version": "2.4.10", + "version": "2.5.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-filter.git", - "reference": "a3711101850078b2aa69586c71897acaada2e9cb" + "reference": "93e6990a198e6cdd811064083acac4693f4b29ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/a3711101850078b2aa69586c71897acaada2e9cb", - "reference": "a3711101850078b2aa69586c71897acaada2e9cb", + "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/93e6990a198e6cdd811064083acac4693f4b29ae", + "reference": "93e6990a198e6cdd811064083acac4693f4b29ae", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-stdlib": "self.version" + "zendframework/zend-stdlib": "~2.5" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", - "satooshi/php-coveralls": "dev-master", - "zendframework/zend-crypt": "self.version", - "zendframework/zend-servicemanager": "self.version", - "zendframework/zend-uri": "self.version" + "zendframework/zend-config": "~2.5", + "zendframework/zend-crypt": "~2.5", + "zendframework/zend-i18n": "~2.5", + "zendframework/zend-loader": "~2.5", + "zendframework/zend-servicemanager": "~2.5", + "zendframework/zend-uri": "~2.5" }, "suggest": { "zendframework/zend-crypt": "Zend\\Crypt component", @@ -3235,8 +3263,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.4-dev", - "dev-develop": "2.5-dev" + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" } }, "autoload": { @@ -3254,37 +3282,36 @@ "filter", "zf2" ], - "time": "2015-05-07 14:55:31" + "time": "2015-06-03T15:32:01+00:00" }, { "name": "zendframework/zend-i18n", - "version": "2.4.10", + "version": "2.5.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-i18n.git", - "reference": "f26d6ae4be3f1ac98fbb3708aafae908c38f46c8" + "reference": "509271eb7947e4aabebfc376104179cffea42696" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-i18n/zipball/f26d6ae4be3f1ac98fbb3708aafae908c38f46c8", - "reference": "f26d6ae4be3f1ac98fbb3708aafae908c38f46c8", + "url": "https://api.github.com/repos/zendframework/zend-i18n/zipball/509271eb7947e4aabebfc376104179cffea42696", + "reference": "509271eb7947e4aabebfc376104179cffea42696", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-stdlib": "self.version" + "zendframework/zend-stdlib": "~2.5" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", - "satooshi/php-coveralls": "dev-master", - "zendframework/zend-cache": "self.version", - "zendframework/zend-config": "self.version", - "zendframework/zend-eventmanager": "self.version", - "zendframework/zend-filter": "self.version", - "zendframework/zend-servicemanager": "self.version", - "zendframework/zend-validator": "self.version", - "zendframework/zend-view": "self.version" + "zendframework/zend-cache": "~2.5", + "zendframework/zend-config": "~2.5", + "zendframework/zend-eventmanager": "~2.5", + "zendframework/zend-filter": "~2.5", + "zendframework/zend-servicemanager": "~2.5", + "zendframework/zend-validator": "~2.5", + "zendframework/zend-view": "~2.5" }, "suggest": { "ext-intl": "Required for most features of Zend\\I18n; included in default builds of PHP", @@ -3300,8 +3327,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.4-dev", - "dev-develop": "2.5-dev" + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" } }, "autoload": { @@ -3318,32 +3345,32 @@ "i18n", "zf2" ], - "time": "2015-05-07 14:55:31" + "time": "2015-06-03T15:32:01+00:00" }, { "name": "zendframework/zend-json", - "version": "2.4.10", + "version": "2.5.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-json.git", - "reference": "1db4b878846520e619fbcdc7ce826c8563f8e839" + "reference": "c74eaf17d2dd37dc1e964be8dfde05706a821ebc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-json/zipball/1db4b878846520e619fbcdc7ce826c8563f8e839", - "reference": "1db4b878846520e619fbcdc7ce826c8563f8e839", + "url": "https://api.github.com/repos/zendframework/zend-json/zipball/c74eaf17d2dd37dc1e964be8dfde05706a821ebc", + "reference": "c74eaf17d2dd37dc1e964be8dfde05706a821ebc", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-stdlib": "self.version" + "zendframework/zend-stdlib": "~2.5" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", - "satooshi/php-coveralls": "dev-master", - "zendframework/zend-http": "self.version", - "zendframework/zend-server": "self.version" + "zendframework/zend-http": "~2.5", + "zendframework/zend-server": "~2.5", + "zendframework/zendxml": "~1.0" }, "suggest": { "zendframework/zend-http": "Zend\\Http component", @@ -3353,8 +3380,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.4-dev", - "dev-develop": "2.5-dev" + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" } }, "autoload": { @@ -3372,20 +3399,20 @@ "json", "zf2" ], - "time": "2015-05-07 14:55:31" + "time": "2015-06-03T15:32:01+00:00" }, { "name": "zendframework/zend-math", - "version": "2.4.10", + "version": "2.5.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-math.git", - "reference": "1e7e803366fc7618a8668ce2403c932196174faa" + "reference": "9f02a1ac4d3374d3332c80f9215deec9c71558fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-math/zipball/1e7e803366fc7618a8668ce2403c932196174faa", - "reference": "1e7e803366fc7618a8668ce2403c932196174faa", + "url": "https://api.github.com/repos/zendframework/zend-math/zipball/9f02a1ac4d3374d3332c80f9215deec9c71558fc", + "reference": "9f02a1ac4d3374d3332c80f9215deec9c71558fc", "shasum": "" }, "require": { @@ -3393,8 +3420,9 @@ }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", + "ircmaxell/random-lib": "~1.1", "phpunit/phpunit": "~4.0", - "satooshi/php-coveralls": "dev-master" + "zendframework/zend-servicemanager": "~2.5" }, "suggest": { "ext-bcmath": "If using the bcmath functionality", @@ -3405,8 +3433,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.4-dev", - "dev-develop": "2.5-dev" + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" } }, "autoload": { @@ -3423,33 +3451,32 @@ "math", "zf2" ], - "time": "2015-05-07 14:55:31" + "time": "2015-06-03T15:32:02+00:00" }, { "name": "zendframework/zend-serializer", - "version": "2.4.10", + "version": "2.5.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-serializer.git", - "reference": "31a0da5c09f54fe76bc4e145e348b0d3d277aaf0" + "reference": "b7208eb17dc4a4fb3a660b85e6c4af035eeed40c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-serializer/zipball/31a0da5c09f54fe76bc4e145e348b0d3d277aaf0", - "reference": "31a0da5c09f54fe76bc4e145e348b0d3d277aaf0", + "url": "https://api.github.com/repos/zendframework/zend-serializer/zipball/b7208eb17dc4a4fb3a660b85e6c4af035eeed40c", + "reference": "b7208eb17dc4a4fb3a660b85e6c4af035eeed40c", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-json": "self.version", - "zendframework/zend-math": "self.version", - "zendframework/zend-stdlib": "self.version" + "zendframework/zend-json": "~2.5", + "zendframework/zend-math": "~2.5", + "zendframework/zend-stdlib": "~2.5" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", - "satooshi/php-coveralls": "dev-master", - "zendframework/zend-servicemanager": "self.version" + "zendframework/zend-servicemanager": "~2.5" }, "suggest": { "zendframework/zend-servicemanager": "To support plugin manager support" @@ -3457,8 +3484,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.4-dev", - "dev-develop": "2.5-dev" + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" } }, "autoload": { @@ -3476,20 +3503,20 @@ "serializer", "zf2" ], - "time": "2015-05-07 14:55:31" + "time": "2015-06-03T15:32:02+00:00" }, { "name": "zendframework/zend-servicemanager", - "version": "2.4.10", + "version": "2.5.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-servicemanager.git", - "reference": "855294e12771b4295c26446b6ed2df2f1785f234" + "reference": "3b22c403e351d92526c642cba0bd810bc22e1c56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-servicemanager/zipball/855294e12771b4295c26446b6ed2df2f1785f234", - "reference": "855294e12771b4295c26446b6ed2df2f1785f234", + "url": "https://api.github.com/repos/zendframework/zend-servicemanager/zipball/3b22c403e351d92526c642cba0bd810bc22e1c56", + "reference": "3b22c403e351d92526c642cba0bd810bc22e1c56", "shasum": "" }, "require": { @@ -3498,8 +3525,8 @@ "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", - "satooshi/php-coveralls": "dev-master", - "zendframework/zend-di": "self.version" + "zendframework/zend-di": "~2.5", + "zendframework/zend-mvc": "~2.5" }, "suggest": { "ocramius/proxy-manager": "ProxyManager 0.5.* to handle lazy initialization of services", @@ -3508,8 +3535,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.4-dev", - "dev-develop": "2.5-dev" + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" } }, "autoload": { @@ -3521,25 +3548,25 @@ "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-service-manager", + "homepage": "https://github.com/zendframework/zend-servicemanager", "keywords": [ "servicemanager", "zf2" ], - "time": "2015-05-07 14:55:31" + "time": "2015-06-03T15:32:02+00:00" }, { "name": "zendframework/zend-stdlib", - "version": "2.4.10", + "version": "2.5.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-stdlib.git", - "reference": "d8ecb629a72da9f91bd95c5af006384823560b42" + "reference": "cc8e90a60dd5d44b9730b77d07b97550091da1ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/d8ecb629a72da9f91bd95c5af006384823560b42", - "reference": "d8ecb629a72da9f91bd95c5af006384823560b42", + "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/cc8e90a60dd5d44b9730b77d07b97550091da1ae", + "reference": "cc8e90a60dd5d44b9730b77d07b97550091da1ae", "shasum": "" }, "require": { @@ -3548,11 +3575,12 @@ "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", - "satooshi/php-coveralls": "dev-master", - "zendframework/zend-eventmanager": "self.version", - "zendframework/zend-filter": "self.version", - "zendframework/zend-serializer": "self.version", - "zendframework/zend-servicemanager": "self.version" + "zendframework/zend-config": "~2.5", + "zendframework/zend-eventmanager": "~2.5", + "zendframework/zend-filter": "~2.5", + "zendframework/zend-inputfilter": "~2.5", + "zendframework/zend-serializer": "~2.5", + "zendframework/zend-servicemanager": "~2.5" }, "suggest": { "zendframework/zend-eventmanager": "To support aggregate hydrator usage", @@ -3563,8 +3591,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.4-dev", - "dev-develop": "2.5-dev" + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" } }, "autoload": { @@ -3581,23 +3609,24 @@ "stdlib", "zf2" ], - "time": "2015-07-21 13:55:46" + "time": "2015-06-03T15:32:03+00:00" }, { "name": "zetacomponents/base", - "version": "1.9", + "version": "1.9.1", "source": { "type": "git", "url": "https://github.com/zetacomponents/Base.git", - "reference": "f20df24e8de3e48b6b69b2503f917e457281e687" + "reference": "489e20235989ddc97fdd793af31ac803972454f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zetacomponents/Base/zipball/f20df24e8de3e48b6b69b2503f917e457281e687", - "reference": "f20df24e8de3e48b6b69b2503f917e457281e687", + "url": "https://api.github.com/repos/zetacomponents/Base/zipball/489e20235989ddc97fdd793af31ac803972454f1", + "reference": "489e20235989ddc97fdd793af31ac803972454f1", "shasum": "" }, "require-dev": { + "phpunit/phpunit": "~5.7", "zetacomponents/unit-test": "*" }, "type": "library", @@ -3644,7 +3673,7 @@ ], "description": "The Base package provides the basic infrastructure that all packages rely on. Therefore every component relies on this package.", "homepage": "https://github.com/zetacomponents", - "time": "2014-09-19 03:28:34" + "time": "2017-11-28T11:30:00+00:00" }, { "name": "zetacomponents/document", @@ -3695,7 +3724,7 @@ ], "description": "The Document components provides a general conversion framework for different semantic document markup languages like XHTML, Docbook, RST and similar.", "homepage": "https://github.com/zetacomponents", - "time": "2013-12-19 11:40:00" + "time": "2013-12-19T11:40:00+00:00" } ], "aliases": [], @@ -3706,7 +3735,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "5.3.*" + "php": "~5.3" }, "platform-dev": [] } diff --git a/integration-tests/LDDFeatureRequesterTest.php b/integration-tests/LDDFeatureRequesterTest.php index 602b87717..b4d82780d 100644 --- a/integration-tests/LDDFeatureRequesterTest.php +++ b/integration-tests/LDDFeatureRequesterTest.php @@ -1,19 +1,23 @@ "tcp", "host" => 'localhost', "port" => 6379)); - $client = new LDClient("BOGUS_API_KEY", array('feature_requester_class' => '\\LaunchDarkly\\LDDFeatureRequester')); + $client = new LDClient(static::API_KEY, array('feature_requester_class' => 'LaunchDarkly\LDDFeatureRequester')); $builder = new LDUserBuilder(3); $user = $builder->build(); @@ -24,11 +28,14 @@ public function testGet() { } public function testGetApc() { - $redis = new \Predis\Client(array( + if (!extension_loaded('apc')) { + self::markTestSkipped('Install `apc` extension to run this test.'); + } + $redis = new Client(array( "scheme" => "tcp", "host" => 'localhost', "port" => 6379)); - $client = new LDClient("BOGUS_API_KEY", array('feature_requester_class' => '\\LaunchDarkly\\ApcLDDFeatureRequester', + $client = new LDClient(static::API_KEY, array('feature_requester_class' => 'LaunchDarkly\ApcLDDFeatureRequester', 'apc_expiration' => 1)); $builder = new LDUserBuilder(3); $user = $builder->build(); @@ -46,20 +53,123 @@ public function testGetApc() { $this->assertEquals("baz", $client->variation('foo', $user, 'jim')); } + public function testGetApcu() { + if (!extension_loaded('apcu')) { + self::markTestSkipped('Install `apcu` extension to run this test.'); + } + + $redis = new Client(array( + 'scheme' => 'tcp', + 'host' => 'localhost', + 'port' => 6379 + )); + + $client = new LDClient(static::API_KEY, array( + 'feature_requester_class' => 'LaunchDarkly\ApcuLDDFeatureRequester', + 'apc_expiration' => 1 + )); + + $builder = new LDUserBuilder(3); + $user = $builder->build(); + + $redis->del('launchdarkly:features'); + $this->assertEquals('alice', $client->variation('fiz', $user, 'alice')); + $redis->hset('launchdarkly:features', 'fiz', $this->gen_feature('fiz', 'buz')); + $this->assertEquals('buz', $client->variation('fiz', $user, 'alice')); + + # cached value so not updated + $redis->hset('launchdarkly:features', 'fiz', $this->gen_feature('fiz', 'bob')); + $this->assertEquals('buz', $client->variation('fiz', $user, 'alice')); + + \apcu_delete('launchdarkly:features.fiz'); + $this->assertEquals('bob', $client->variation('fiz', $user, 'alice')); + } + + public function testGetAllWithoutFeatures() + { + $redis = new \Predis\Client(array( + 'scheme' => 'tcp', + 'host' => 'localhost', + 'port' => 6379, + )); + $redis->flushall(); + + $client = new LDClient(static::API_KEY, array('feature_requester_class' => 'LaunchDarkly\LDDFeatureRequester')); + $user = new LDUser(static::API_KEY); + $allFlags = $client->allFlags($user); + + $this->assertNull($allFlags); + } + + public function testGetAll() + { + $featureKey = 'foo'; + $featureValue = 'bar'; + + $redis = new \Predis\Client(array( + 'scheme' => 'tcp', + 'host' => 'localhost', + 'port' => 6379, + )); + $client = new LDClient(static::API_KEY, array('feature_requester_class' => 'LaunchDarkly\LDDFeatureRequester')); + $redis->hset('launchdarkly:features', $featureKey, $this->gen_feature($featureKey, $featureValue)); + $user = new LDUser(static::API_KEY); + $allFlags = $client->allFlags($user); + + $this->assertInternalType('array', $allFlags); + $this->assertArrayHasKey($featureKey, $allFlags); + $this->assertEquals($featureValue, $allFlags[$featureKey]); + } + private function gen_feature($key, $val) { - $data = << 'Feature ' . $key, + 'key' => $key, + 'kind' => 'flag', + 'salt' => 'Zm9v', + 'on' => true, + 'variations' => array( + $val, + false, + ), + 'commitDate' => '2015-09-08T21:24:16.712Z', + 'creationDate' => '2015-09-08T21:06:16.527Z', + 'version' => 4, + 'prerequisites' => array(), + 'targets' => array( + array( + 'values' => array( + $val, + ), + 'variation' => 0, + ), + array( + 'values' => array( + false, + ), + 'variation' => 1, + ), + ), + 'rules' => array(), + 'fallthrough' => array( + 'rollout' => array( + 'variations' => array( + array( + 'variation' => 0, + 'weight' => 95000, + ), + array( + 'variation' => 1, + 'weight' => 5000, + ), + ), + ), + ), + 'offVariation' => null, + 'deleted' => false, + ); + + return \json_encode($data); } } diff --git a/integration-tests/Vagrantfile b/integration-tests/Vagrantfile index ff73f459b..5529ac9d3 100644 --- a/integration-tests/Vagrantfile +++ b/integration-tests/Vagrantfile @@ -17,6 +17,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.box_url = "https://vagrantcloud.com/ubuntu/boxes/trusty64" config.vm.provision :shell, path: "bootstrap.sh" + config.vm.provision :shell, path: "bootstrap.user.sh", privileged: false # Create a forwarded port mapping which allows access to a specific port # within the machine from a port on the host machine. In the example below, diff --git a/integration-tests/bootstrap.sh b/integration-tests/bootstrap.sh index 3e75fe316..f3f4ad105 100755 --- a/integration-tests/bootstrap.sh +++ b/integration-tests/bootstrap.sh @@ -1,5 +1,5 @@ #!/bin/bash - +set -uxe # init apt-get update 2> /dev/null @@ -21,7 +21,7 @@ apt-get install -y php-apc 2> /dev/null apt-get install -y phpunit 2> /dev/null # phpbrew stuff for 5.4 -apt-get build-dep php5 2> /dev/null +apt-get build-dep -y php5 2> /dev/null apt-get install -y php5 php5-dev php-pear autoconf automake curl build-essential libxslt1-dev re2c libxml2 libxml2-dev php5-cli bison libbz2-dev libreadline-dev 2> /dev/null apt-get install -y libfreetype6 libfreetype6-dev libpng12-0 libpng12-dev libjpeg-dev libjpeg8-dev libjpeg8 libgd-dev libgd3 libxpm4 libltdl7 libltdl-dev 2> /dev/null apt-get install -y libssl-dev openssl 2> /dev/null @@ -36,24 +36,6 @@ set tabstop=4 EOF chown vagrant.vagrant /home/vagrant/.vimrc -su - vagrant -cd ~vagrant -pwd -curl -s -L -O https://github.com/phpbrew/phpbrew/raw/master/phpbrew -chmod +x phpbrew -sudo mv phpbrew /usr/bin/phpbrew -phpbrew init -phpbrew known --update -phpbrew update -phpbrew install 5.4.34 +default - -echo "source $HOME/.phpbrew/bashrc" >> /home/vagrant/.bashrc -source $HOME/.bashrc -phpbrew switch php-5.4.34 -phpbrew ext install apc -echo "apc.enable_cli = 1" >> ~/.phpbrew/php/php-5.4.34/etc/php.ini - - -cd /home/vagrant/project/integration-tests -curl -sS https://getcomposer.org/installer | php -php composer.phar install +# enable APC for php5 CLI +echo "apc.enable_cli = 1" >> /etc/php5/cli/conf.d/20-apcu.ini +php -i | grep apc diff --git a/integration-tests/bootstrap.user.sh b/integration-tests/bootstrap.user.sh new file mode 100755 index 000000000..fc36f5848 --- /dev/null +++ b/integration-tests/bootstrap.user.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -uxe + +echo +echo "update project dependencies" +cd /home/vagrant/project +curl -sS https://getcomposer.org/installer | php +php composer.phar update diff --git a/integration-tests/composer.json b/integration-tests/composer.json deleted file mode 100644 index e0cd69628..000000000 --- a/integration-tests/composer.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "launchdarkly/launchdarkly-php-integration-tests", - "description": "Integration tests", - "homepage": "https://github.com/launchdarkly/php-client", - "license": "Apache-2.0", - "authors": [ - { - "name": "LaunchDarkly ", - "homepage": "http://launchdarkly.com/" - } - ], - "require": { - "php": ">=5.3", - "predis/predis": "1.0.*" - }, - "require-dev": { - "phpunit/phpunit": "4.3.*" - }, - "autoload": { - "psr-4": { - "": "../src/" - } - }, - "autoload-dev": { - "psr-4": { - "": "." - } - } -} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 000000000..05bb415b5 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,12 @@ + + + + tests + + + + + src + + + \ No newline at end of file diff --git a/src/LaunchDarkly/ApcLDDFeatureRequester.php b/src/LaunchDarkly/ApcLDDFeatureRequester.php index 9ad8e7f82..d6d56e005 100644 --- a/src/LaunchDarkly/ApcLDDFeatureRequester.php +++ b/src/LaunchDarkly/ApcLDDFeatureRequester.php @@ -4,24 +4,36 @@ /** * Feature requester from an LDD-populated redis, with APC caching + * @deprecated Per the docs (http://php.net/manual/en/intro.apc.php): + * "This extension (APC) is considered unmaintained and dead". + * Install APCu and use \LaunchDarkly\ApcuLDDFeatureRequester instead! * * @package LaunchDarkly */ class ApcLDDFeatureRequester extends LDDFeatureRequester { protected $_expiration = 30; - function __construct($baseUri, $apiKey, $options) { - parent::__construct($baseUri, $apiKey, $options); + function __construct($baseUri, $sdkKey, $options) { + parent::__construct($baseUri, $sdkKey, $options); if (isset($options['apc_expiration'])) { $this->_expiration = (int)$options['apc_expiration']; } } + /** + * @param $key + * @param $success + * @return mixed + */ + protected function fetch($key, &$success = null) + { + return \apc_fetch($key, $success); + } protected function get_from_cache($key) { $key = self::make_cache_key($key); - $enabled = apc_fetch($key); + $enabled = $this->fetch($key); if ($enabled === false) { return null; } @@ -30,8 +42,19 @@ protected function get_from_cache($key) { } } + /** + * @param $key + * @param $var + * @param int $ttl + * @return mixed + */ + protected function add($key, $var, $ttl = 0) + { + return \apc_add($key, $var, $ttl); + } + protected function store_in_cache($key, $val) { - apc_add($this->make_cache_key($key), $val, $this->_expiration); + $this->add($this->make_cache_key($key), $val, $this->_expiration); } private function make_cache_key($name) { diff --git a/src/LaunchDarkly/ApcuLDDFeatureRequester.php b/src/LaunchDarkly/ApcuLDDFeatureRequester.php new file mode 100644 index 000000000..30f2fb2c9 --- /dev/null +++ b/src/LaunchDarkly/ApcuLDDFeatureRequester.php @@ -0,0 +1,34 @@ +_sdkKey = $sdkKey; + + $eventsUri = LDClient::DEFAULT_EVENTS_URI; + if (isset($options['events_uri'])) { + $eventsUri = $options['events_uri']; + } + $url = parse_url(rtrim($eventsUri,'/')); + $this->_host = $url['host']; + $this->_ssl = $url['scheme'] === 'https'; + if (isset($url['port'])) { + $this->_port = $url['port']; + } + else { + $this->_port = $this->_ssl ? 443 : 80; + } + if (isset($url['path'])) { + $this->_path = $url['path']; + } + else { + $this->_path = ''; + } + + if (array_key_exists('curl', $options)) { + $this->_curl = $options['curl']; + } + } + + public function publish($payload) { + $args = $this->createArgs($payload); + + return $this->makeRequest($args); + } + + private function createArgs($payload) { + $scheme = $this->_ssl ? "https://" : "http://"; + $args = " -X POST"; + $args.= " -H 'Content-Type: application/json'"; + $args.= " -H " . escapeshellarg("Authorization: " . $this->_sdkKey); + $args.= " -H 'User-Agent: PHPClient/" . LDClient::VERSION . "'"; + $args.= " -H 'Accept: application/json'"; + $args.= " -d " . escapeshellarg($payload); + $args.= " " . escapeshellarg($scheme . $this->_host . ":" . $this->_port . $this->_path . "/bulk"); + return $args; + } + + private function makeRequest($args) { + $cmd = $this->_curl . " " . $args . ">> /dev/null 2>&1 &"; + shell_exec($cmd); + return true; + } +} \ No newline at end of file diff --git a/src/LaunchDarkly/EventProcessor.php b/src/LaunchDarkly/EventProcessor.php index 4e2c2ea12..ade79958e 100644 --- a/src/LaunchDarkly/EventProcessor.php +++ b/src/LaunchDarkly/EventProcessor.php @@ -6,44 +6,20 @@ */ class EventProcessor { - private $_sdkKey; + private $_eventPublisher; + private $_eventSerializer; private $_queue; private $_capacity; private $_timeout; - private $_host; - private $_port; - private $_ssl; - - public function __construct($apiKey, $options = array()) { - $this->_sdkKey = $apiKey; - if (!isset($options['events_uri'])) { - $this->_host = 'events.launchdarkly.com'; - $this->_port = 443; - $this->_ssl = true; - $this->_path = ''; - } - else { - $url = parse_url(rtrim($options['events_uri'],'/')); - $this->_host = $url['host']; - $this->_ssl = $url['scheme'] === 'https'; - if (isset($url['port'])) { - $this->_port = $url['port']; - } - else { - $this->_port = $this->_ssl ? 443 : 80; - } - if (isset($url['path'])) { - $this->_path = $url['path']; - } - else { - $this->_path = ''; - } - } + public function __construct($sdkKey, $options = array()) { + $this->_eventPublisher = $this->getEventPublisher($sdkKey, $options); + $this->_eventSerializer = new EventSerializer($options); + $this->_capacity = $options['capacity']; $this->_timeout = $options['timeout']; - $this->_queue = array(); + $this->_queue = array(); } public function __destruct() { @@ -64,35 +40,45 @@ public function enqueue($event) { return true; } - protected function flush() { + /** + * Publish events to LaunchDarkly + * @return bool Whether the events were successfully published + */ + public function flush() { if (empty($this->_queue)) { return null; } - $payload = json_encode($this->_queue); + $payload = $this->_eventSerializer->serializeEvents($this->_queue); - $args = $this->createArgs($payload); + // We don't expect flush to be called more than once per request cycle, but let's empty the queue just in case + $this->_queue = array(); - return $this->makeRequest($args); - } + $this->_queue = array(); - private function createArgs($payload) { - $scheme = $this->_ssl ? "https://" : "http://"; - $args = " -X POST"; - $args.= " -H 'Content-Type: application/json'"; - $args.= " -H " . escapeshellarg("Authorization: " . $this->_sdkKey); - $args.= " -H 'User-Agent: PHPClient/" . LDClient::VERSION . "'"; - $args.= " -H 'Accept: application/json'"; - $args.= " -d " . escapeshellarg($payload); - $args.= " " . escapeshellarg($scheme . $this->_host . ":" . $this->_port . $this->_path . "/bulk"); - return $args; + return $this->_eventPublisher->publish($payload); } - private function makeRequest($args) { - $cmd = "/usr/bin/env curl " . $args . ">> /dev/null 2>&1 &"; - shell_exec($cmd); - return true; - } + /** + * @param string $sdkKey + * @param mixed[] $options + * @return EventPublisher + */ + private function getEventPublisher($sdkKey, array $options) + { + if (isset($options['event_publisher']) && $options['event_publisher'] instanceof EventPublisher) { + return $options['event_publisher']; + } + if (isset($options['event_publisher_class'])) { + $eventPublisherClass = $options['event_publisher_class']; + } else { + $eventPublisherClass = 'LaunchDarkly\CurlEventPublisher'; + } + if (!is_a($eventPublisherClass, 'LaunchDarkly\EventPublisher', true)) { + throw new \InvalidArgumentException; + } + return new $eventPublisherClass($sdkKey, $options); + } } \ No newline at end of file diff --git a/src/LaunchDarkly/EventPublisher.php b/src/LaunchDarkly/EventPublisher.php new file mode 100644 index 000000000..bfcc27764 --- /dev/null +++ b/src/LaunchDarkly/EventPublisher.php @@ -0,0 +1,21 @@ +_allAttrsPrivate = isset($options['all_attributes_private']) && $options['all_attributes_private']; + $this->_privateAttrNames = isset($options['private_attribute_names']) ? $options['private_attribute_names'] : array(); + } + + public function serializeEvents($events) { + $filtered = array(); + foreach ($events as $e) { + array_push($filtered, $this->filterEvent($e)); + } + return json_encode($filtered); + } + + private function filterEvent($e) { + $ret = array(); + foreach ($e as $key => $value) { + if ($key == 'user') { + $ret[$key] = $this->serializeUser($value); + } + else { + $ret[$key] = $value; + } + } + return $ret; + } + + private function filterAttrs($attrs, &$json, $userPrivateAttrs, &$allPrivateAttrs) { + foreach ($attrs as $key => $value) { + if ($value != null) { + if ($this->_allAttrsPrivate || + array_search($key, $userPrivateAttrs) !== FALSE || + array_search($key, $this->_privateAttrNames) !== FALSE) { + $allPrivateAttrs[$key] = true; + } + else { + $json[$key] = $value; + } + } + } + } + + private function serializeUser($user) { + $json = array("key" => $user->getKey()); + $userPrivateAttrs = $user->getPrivateAttributeNames(); + $allPrivateAttrs = array(); + + $attrs = array( + 'secondary' => $user->getSecondary(), + 'ip' => $user->getIP(), + 'country' => $user->getCountry(), + 'email' => $user->getEmail(), + 'name' => $user->getName(), + 'avatar' => $user->getAvatar(), + 'firstName' => $user->getFirstName(), + 'lastName' => $user->getLastName(), + 'anonymous' => $user->getAnonymous() + ); + $this->filterAttrs($attrs, $json, $userPrivateAttrs, $allPrivateAttrs); + if ($user->getCustom()) { + $customs = array(); + $this->filterAttrs($user->getCustom(), $customs, $userPrivateAttrs, $allPrivateAttrs); + $json['custom'] = $customs; + } + if (count($allPrivateAttrs)) { + $pa = array_keys($allPrivateAttrs); + sort($pa); + $json['privateAttrs'] = $pa; + } + return $json; + } +} diff --git a/src/LaunchDarkly/FeatureFlag.php b/src/LaunchDarkly/FeatureFlag.php index abc446af7..7b2be6d61 100644 --- a/src/LaunchDarkly/FeatureFlag.php +++ b/src/LaunchDarkly/FeatureFlag.php @@ -57,13 +57,13 @@ public static function getDecoder() { $v['key'], $v['version'], $v['on'], - array_map(Prerequisite::getDecoder(), $v['prerequisites']), + array_map(Prerequisite::getDecoder(), $v['prerequisites'] ?: array()), $v['salt'], - array_map(Target::getDecoder(), $v['targets']), - array_map(Rule::getDecoder(), $v['rules']), + array_map(Target::getDecoder(), $v['targets'] ?: array()), + array_map(Rule::getDecoder(), $v['rules'] ?: array()), call_user_func(VariationOrRollout::getDecoder(), $v['fallthrough']), $v['offVariation'], - $v['variations'], + $v['variations'] ?: array(), $v['deleted']); }; } @@ -88,7 +88,7 @@ public function evaluate($user, $featureRequester) { } if ($this->isOn()) { $result = $this->_evaluate($user, $featureRequester, $prereqEvents); - if ($result != null) { + if ($result !== null) { return new EvalResult($result, $prereqEvents); } } diff --git a/src/LaunchDarkly/GuzzleEventPublisher.php b/src/LaunchDarkly/GuzzleEventPublisher.php new file mode 100644 index 000000000..9e7bcef52 --- /dev/null +++ b/src/LaunchDarkly/GuzzleEventPublisher.php @@ -0,0 +1,66 @@ +_sdkKey = $sdkKey; + $this->_logger = $options['logger']; + if (isset($options['events_uri'])) { + $this->_eventsUri = $options['events_uri']; + } else { + $this->_eventsUri = LDClient::DEFAULT_EVENTS_URI; + } + $this->_requestHeaders = array( + 'Content-Type' => 'application/json', + 'Authorization' => $this->_sdkKey, + 'User-Agent' => 'PHPClient/' . LDClient::VERSION, + 'Accept' => 'application/json' + ); + $this->_requestOptions = array( + 'timeout' => $options['timeout'], + 'connect_timeout' => $options['connect_timeout'] + ); + } + + public function publish($payload) { + $client = new Client($this->_eventsUri); + $response = null; + + try { + $req = $client->post('/bulk', $this->_requestHeaders, $payload, $this->_requestOptions); + $response = $req->send(); + } catch (\Exception $e) { + $this->_logger->warning("GuzzleEventPublisher::publish caught $e"); + return false; + } + if ($response && ($response->getStatusCode() == 401)) { + throw new InvalidSDKKeyException(); + } + return $response && ($response->getStatusCode() < 300); + } +} \ No newline at end of file diff --git a/src/LaunchDarkly/GuzzleFeatureRequester.php b/src/LaunchDarkly/GuzzleFeatureRequester.php index 2a53909da..70e7a9ca4 100644 --- a/src/LaunchDarkly/GuzzleFeatureRequester.php +++ b/src/LaunchDarkly/GuzzleFeatureRequester.php @@ -6,14 +6,22 @@ use Guzzle\Plugin\Cache\CachePlugin; use Psr\Log\LoggerInterface; -class GuzzleFeatureRequester implements FeatureRequester { +class GuzzleFeatureRequester implements FeatureRequester +{ const SDK_FLAGS = "/sdk/flags"; /** @var Client */ private $_client; - /** @var LoggerInterface */ + /** @var string */ + private $_baseUri; + /** @var array */ + private $_defaults; + /** @var LoggerInterface */ private $_logger; + /** @var boolean */ + private $_loggedCacheNotice = FALSE; - function __construct($baseUri, $sdkKey, $options) { + function __construct($baseUri, $sdkKey, $options) + { $this->_client = new Client($baseUri, array( 'debug' => false, @@ -40,7 +48,7 @@ function __construct($baseUri, $sdkKey, $options) { * Gets feature data from a likely cached store * * @param $key string feature key - * @return array|null The decoded JSON feature data, or null if missing + * @return FeatureFlag|null The decoded FeatureFlag, or null if missing */ public function get($key) { @@ -51,7 +59,11 @@ public function get($key) return FeatureFlag::decode(json_decode($body, true)); } catch (BadResponseException $e) { $code = $e->getResponse()->getStatusCode(); - $this->_logger->error("GuzzleFeatureRetriever::get received an unexpected HTTP status code $code"); + if ($code == 404) { + $this->_logger->warning("GuzzleFeatureRequester::get returned 404. Feature flag does not exist for key: " . $key); + } else { + $this->handleUnexpectedStatus($code, "GuzzleFeatureRequester::get"); + } return null; } } @@ -68,9 +80,15 @@ public function getAll() { $body = $response->getBody(); return array_map(FeatureFlag::getDecoder(), json_decode($body, true)); } catch (BadResponseException $e) { - $code = $e->getResponse()->getStatusCode(); - $this->_logger->error("GuzzleFeatureRetriever::getAll received an unexpected HTTP status code $code"); + $this->handleUnexpectedStatus($e->getResponse()->getStatusCode(), "GuzzleFeatureRequester::getAll"); return null; } - } + } + + private function handleUnexpectedStatus($code, $method) { + $this->_logger->error("$method received an unexpected HTTP status code $code"); + if ($code == 401) { + throw new InvalidSDKKeyException(); + } + } } \ No newline at end of file diff --git a/src/LaunchDarkly/LDClient.php b/src/LaunchDarkly/LDClient.php index 2fdd90ba4..4152fb782 100644 --- a/src/LaunchDarkly/LDClient.php +++ b/src/LaunchDarkly/LDClient.php @@ -5,17 +5,27 @@ use Monolog\Logger; use Psr\Log\LoggerInterface; +/** + * Used internally. + */ +class InvalidSDKKeyException extends \Exception +{ +} + /** * A client for the LaunchDarkly API. */ class LDClient { const DEFAULT_BASE_URI = 'https://app.launchdarkly.com'; - const VERSION = '2.0.0'; + const DEFAULT_EVENTS_URI = 'https://events.launchdarkly.com'; + const VERSION = '2.4.0'; /** @var string */ protected $_sdkKey; /** @var string */ protected $_baseUri; + /** @var string */ + protected $_eventsUri; /** @var EventProcessor */ protected $_eventProcessor; /** @var bool */ @@ -24,10 +34,9 @@ class LDClient { protected $_send_events = true; /** @var array|mixed */ protected $_defaults = array(); - /** @var mixed|LoggerInterface */ + /** @var LoggerInterface */ protected $_logger; - - /** @var FeatureRequester */ + /** @var FeatureRequester */ protected $_featureRequester; /** @@ -39,10 +48,16 @@ class LDClient { * - events_uri: Base URI for sending events to LaunchDarkly. Defaults to 'https://events.launchdarkly.com' * - timeout: Float describing the maximum length of a request in seconds. Defaults to 3 * - connect_timeout: Float describing the number of seconds to wait while trying to connect to a server. Defaults to 3 - * - cache_storage: An optional GuzzleHttp\Subscriber\Cache\CacheStorageInterface. Defaults to an in-memory cache. + * - cache_storage: An optional Guzzle\Plugin\Cache\DefaultCacheStorage. Defaults to an in-memory cache. * - send_events: An optional bool that can disable the sending of events to LaunchDarkly. Defaults to false. * - logger: An optional Psr\Log\LoggerInterface. Defaults to a Monolog\Logger sending all messages to the php error_log. * - offline: An optional boolean which will disable all network calls and always return the default value. Defaults to false. + * - feature_requester: An optional LaunchDarkly\FeatureRequester instance. + * - feature_requester_class: An optional class implementing LaunchDarkly\FeatureRequester, if `feature_requester` is not specified. Defaults to GuzzleFeatureRequester. + * - event_publisher: An optional LaunchDarkly\EventPublisher instance. + * - event_publisher_class: An optional class implementing LaunchDarkly\EventPublisher, if `event_publisher` is not specified. Defaults to CurlEventPublisher. + * - all_attributes_private: True if no user attributes (other than the key) should be sent back to LaunchDarkly. By default, this is false. + * - private_attribute_names: An optional array of user attribute names to be marked private. Any users sent to LaunchDarkly with this configuration active will have attributes with these names removed. You can also set private attributes on a per-user basis in LDUserBuilder. */ public function __construct($sdkKey, $options = array()) { $this->_sdkKey = $sdkKey; @@ -51,6 +66,11 @@ public function __construct($sdkKey, $options = array()) { } else { $this->_baseUri = rtrim($options['base_uri'], '/'); } + if (!isset($options['events_uri'])) { + $this->_eventsUri = self::DEFAULT_EVENTS_URI; + } else { + $this->_eventsUri = rtrim($options['events_uri'], '/'); + } if (isset($options['send_events'])) { $this->_send_events = $options['send_events']; } @@ -82,13 +102,30 @@ public function __construct($sdkKey, $options = array()) { $this->_eventProcessor = new EventProcessor($sdkKey, $options); + $this->_featureRequester = $this->getFeatureRequester($sdkKey, $options); + } + + /** + * @param string $sdkKey + * @param mixed[] $options + * @return FeatureRequester + */ + private function getFeatureRequester($sdkKey, array $options) + { + if (isset($options['feature_requester']) && $options['feature_requester'] instanceof FeatureRequester) { + return $options['feature_requester']; + } + if (isset($options['feature_requester_class'])) { $featureRequesterClass = $options['feature_requester_class']; } else { - $featureRequesterClass = '\\LaunchDarkly\\GuzzleFeatureRequester'; + $featureRequesterClass = 'LaunchDarkly\GuzzleFeatureRequester'; } - $this->_featureRequester = new $featureRequesterClass($this->_baseUri, $sdkKey, $options); + if (!is_a($featureRequesterClass, 'LaunchDarkly\FeatureRequester', true)) { + throw new \InvalidArgumentException; + } + return new $featureRequesterClass($this->_baseUri, $sdkKey, $options); } /** @@ -96,7 +133,7 @@ public function __construct($sdkKey, $options = array()) { * * @param string $key The unique key for the feature flag * @param LDUser $user The end user requesting the flag - * @param boolean $default The default value of the flag + * @param mixed $default The default value of the flag * * @return mixed The result of the Feature Flag evaluation, or $default if any errors occurred. */ @@ -110,13 +147,18 @@ public function variation($key, $user, $default = false) { try { if (is_null($user) || is_null($user->getKey())) { $this->_sendFlagRequestEvent($key, $user, $default, $default); - $this->_logger->warn("Variation called with null user or null user key! Returning default value"); + $this->_logger->warning("Variation called with null user or null user key! Returning default value"); return $default; } if ($user->isKeyBlank()) { - $this->_logger->warn("User key is blank. Flag evaluation will proceed, but the user will not be stored in LaunchDarkly."); + $this->_logger->warning("User key is blank. Flag evaluation will proceed, but the user will not be stored in LaunchDarkly."); + } + try { + $flag = $this->_featureRequester->get($key); + } catch (InvalidSDKKeyException $e) { + $this->handleInvalidSDKKey(); + return $default; } - $flag = $this->_featureRequester->get($key); if (is_null($flag)) { $this->_sendFlagRequestEvent($key, $user, $default, $default); @@ -128,7 +170,7 @@ public function variation($key, $user, $default = false) { $this->_eventProcessor->enqueue($e); } } - if ($evalResult->getValue() != null) { + if ($evalResult->getValue() !== null) { $this->_sendFlagRequestEvent($key, $user, $evalResult->getValue(), $default, $flag->getVersion()); return $evalResult->getValue(); } @@ -175,11 +217,11 @@ public function track($eventName, $user, $data) { return; } if (is_null($user) || $user->isKeyBlank()) { - $this->_logger->warn("Track called with null user or null/empty user key!"); + $this->_logger->warning("Track called with null user or null/empty user key!"); } $event = array(); - $event['user'] = $user->toJSON(); + $event['user'] = $user; $event['kind'] = "custom"; $event['creationDate'] = Util::currentTimeUnixMillis(); $event['key'] = $eventName; @@ -197,11 +239,11 @@ public function identify($user) { return; } if (is_null($user) || $user->isKeyBlank()) { - $this->_logger->warn("Track called with null user or null/empty user key!"); + $this->_logger->warning("Track called with null user or null/empty user key!"); } $event = array(); - $event['user'] = $user->toJSON(); + $event['user'] = $user; $event['kind'] = "identify"; $event['creationDate'] = Util::currentTimeUnixMillis(); $event['key'] = $user->getKey(); @@ -224,17 +266,29 @@ public function allFlags($user) { $this->_logger->warn("allFlags called with null user or null/empty user key! Returning null"); return null; } - $flags = $this->_featureRequester->getAll(); + if ($this->isOffline()) { + return null; + } + try { + $flags = $this->_featureRequester->getAll(); + } catch (InvalidSDKKeyException $e) { + $this->handleInvalidSDKKey(); + return null; + } if ($flags === null) { return null; } - $results = array(); - - foreach ($flags as $flag) - $results[$flag->getKey()] = $flag->evaluate($user, $this->_featureRequester)->getValue(); + /** + * @param $flag FeatureFlag + * @return mixed|null + */ + $fr = $this->_featureRequester; + $eval = function($flag) use($user, $fr) { + return $flag->evaluate($user, $fr)->getValue(); + }; - return $results; + return array_map($eval, $flags); } /** Generates an HMAC sha256 hash for use in Secure mode: https://github.com/launchdarkly/js-client#secure-mode @@ -248,6 +302,19 @@ public function secureModeHash($user) { return hash_hmac("sha256", $user->getKey(), $this->_sdkKey, false); } + /** + * Publish events to LaunchDarkly + * @return bool Whether the events were successfully published + */ + public function flush() + { + try { + return $this->_eventProcessor->flush(); + } catch (InvalidSDKKeyException $e) { + $this->handleInvalidSDKKey(); + } + } + /** * @param $key string * @param $user LDUser @@ -270,4 +337,9 @@ protected function _get_default($key, $default) { return $default; } } + + protected function handleInvalidSDKKey() { + $this->_logger->error("Received 401 error, no further HTTP requests will be made during lifetime of LDClient since SDK key is invalid"); + $this->_offline = true; + } } diff --git a/src/LaunchDarkly/LDDFeatureRequester.php b/src/LaunchDarkly/LDDFeatureRequester.php index f163ee90f..dd5ec7262 100644 --- a/src/LaunchDarkly/LDDFeatureRequester.php +++ b/src/LaunchDarkly/LDDFeatureRequester.php @@ -2,19 +2,22 @@ namespace LaunchDarkly; +use Predis\ClientInterface; use Psr\Log\LoggerInterface; class LDDFeatureRequester implements FeatureRequester { protected $_baseUri; - protected $_apiKey; + protected $_sdkKey; protected $_options; protected $_features_key; /** @var LoggerInterface */ private $_logger; + /** @var ClientInterface */ + private $_connection; - function __construct($baseUri, $apiKey, $options) { + function __construct($baseUri, $sdkKey, $options) { $this->_baseUri = $baseUri; - $this->_apiKey = $apiKey; + $this->_sdkKey = $sdkKey; if (!isset($options['redis_host'])) { $options['redis_host'] = 'localhost'; } @@ -31,11 +34,21 @@ function __construct($baseUri, $apiKey, $options) { $this->_features_key = "$prefix:features"; $this->_logger = $options['logger']; + if (isset($this->_options['predis_client']) && $this->_options['predis_client'] instanceof ClientInterface) { + $this->_connection = $this->_options['predis_client']; + } } + /** + * @return ClientInterface + */ protected function get_connection() { + if ($this->_connection instanceof ClientInterface) { + return $this->_connection; + } + /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */ - return new \Predis\Client(array( + return $this->_connection = new \Predis\Client(array( "scheme" => "tcp", "host" => $this->_options['redis_host'], "port" => $this->_options['redis_port'])); @@ -96,7 +109,7 @@ public function getAll() { $redis = $this->get_connection(); $raw = $redis->hgetall($this->_features_key); if ($raw) { - $allFlags = array_map(FeatureFlag::getDecoder(), json_decode($raw, true)); + $allFlags = array_map(FeatureFlag::getDecoder(), $this->decodeFeatures($raw)); /** * @param $flag FeatureFlag * @return bool @@ -110,4 +123,18 @@ public function getAll() { return null; } } -} \ No newline at end of file + + /** + * @param array $features + * + * @return array + */ + private function decodeFeatures(array $features) + { + foreach ($features as $featureKey => $feature) { + $features[$featureKey] = json_decode($feature, true); + } + + return $features; + } +} diff --git a/src/LaunchDarkly/LDUser.php b/src/LaunchDarkly/LDUser.php index 76aaac5fe..fdd4d110e 100644 --- a/src/LaunchDarkly/LDUser.php +++ b/src/LaunchDarkly/LDUser.php @@ -18,6 +18,7 @@ class LDUser { protected $_lastName = null; protected $_anonyomus = false; protected $_custom = array(); + protected $_privateAttributeNames = array(); /** * @param string $key Unique key for the user. For authenticated users, this may be a username or e-mail address. For anonymous users, this could be an IP address or session ID. @@ -32,7 +33,8 @@ class LDUser { * @param boolean|null $anonymous Whether this is an anonymous user * @param array|null $custom Other custom attributes that can be used to create custom rules */ - public function __construct($key, $secondary = null, $ip = null, $country = null, $email = null, $name = null, $avatar = null, $firstName = null, $lastName = null, $anonymous = null, $custom = array()) { + public function __construct($key, $secondary = null, $ip = null, $country = null, $email = null, $name = null, $avatar = null, $firstName = null, $lastName = null, $anonymous = null, $custom = array(), + $privateAttributeNames = array()) { if ($key !== null) { $this->_key = strval($key); } @@ -46,6 +48,7 @@ public function __construct($key, $secondary = null, $ip = null, $country = null $this->_lastName = $lastName; $this->_anonymous = $anonymous; $this->_custom = $custom; + $this->_privateAttributeNames = $privateAttributeNames; } public function getValueForEvaluation($attr) { @@ -129,43 +132,11 @@ public function getAnonymous() { return $this->_anonymous; } + public function getPrivateAttributeNames() { + return $this->_privateAttributeNames; + } + public function isKeyBlank() { return isset($this->_key) && empty($this->_key); } - - public function toJSON() { - $json = array("key" => $this->_key); - - if (isset($this->_secondary)) { - $json['secondary'] = $this->_secondary; - } - if (isset($this->_ip)) { - $json['ip'] = $this->_ip; - } - if (isset($this->_country)) { - $json['country'] = $this->_country; - } - if (isset($this->_email)) { - $json['email'] = $this->_email; - } - if (isset($this->_name)) { - $json['name'] = $this->_name; - } - if (isset($this->_avatar)) { - $json['avatar'] = $this->_avatar; - } - if (isset($this->_firstName)) { - $json['firstName'] = $this->_firstName; - } - if (isset($this->_lastName)) { - $json['lastName'] = $this->_lastName; - } - if (isset($this->_custom) && !empty($this->_custom)) { - $json['custom'] = $this->_custom; - } - if (isset($this->_anonymous)) { - $json['anonymous'] = $this->_anonymous; - } - return $json; - } } diff --git a/src/LaunchDarkly/LDUserBuilder.php b/src/LaunchDarkly/LDUserBuilder.php index e0a4484d7..f08b523cb 100644 --- a/src/LaunchDarkly/LDUserBuilder.php +++ b/src/LaunchDarkly/LDUserBuilder.php @@ -1,6 +1,14 @@ builder that helps construct LDUser objects. + * + * Note that all user attributes, except for key and anonymous, can be designated as + * private so that they will not be sent back to LaunchDarkly. You can do this either on a per-user basis in + * LDUserBuilder, or globally via the private_attribute_names and all_attributes_private + * options in the client configuration. + */ class LDUserBuilder { protected $_key = null; protected $_secondary = null; @@ -13,7 +21,11 @@ class LDUserBuilder { protected $_lastName = null; protected $_anonymous = null; protected $_custom = array(); - + protected $_privateAttributeNames = array(); + + /** + * Creates a builder with the specified key. + */ public function __construct($key) { $this->_key = $key; } @@ -23,53 +35,163 @@ public function secondary($secondary) { return $this; } + public function privateSecondary($secondary) { + array_push($this->_privateAttributeNames, 'secondary'); + return $this->secondary($secondary); + } + + /** + * Sets the IP for a user. + */ public function ip($ip) { $this->_ip = $ip; return $this; } + /** + * Sets the IP for a user, and ensures that the IP attribute will not be sent back to LaunchDarkly. + */ + public function privateIp($ip) { + array_push($this->_privateAttributeNames, 'ip'); + return $this->ip($ip); + } + + /** + * Sets the country for a user. The country should be a valid ISO 3166-1 + * alpha-2 or alpha-3 code. If it is not a valid ISO-3166-1 code, an attempt will be made to look up the country by its name. + * If that fails, a warning will be logged, and the country will not be set. + */ public function country($country) { $this->_country = $country; return $this; } + /** + * Sets the country for a user, and ensures that the country attribute will not be sent back to LaunchDarkly. + * The country should be a valid ISO 3166-1 + * alpha-2 or alpha-3 code. If it is not a valid ISO-3166-1 code, an attempt will be made to look up the country by its name. + * If that fails, a warning will be logged, and the country will not be set. + */ + public function privateCountry($country) { + array_push($this->_privateAttributeNames, 'country'); + return $this->country($country); + } + + /** + * Sets the user's email address. + */ public function email($email) { $this->_email = $email; return $this; } + /** + * Sets the user's email address, and ensures that the email attribute will not be sent back to LaunchDarkly. + */ + public function privateEmail($email) { + array_push($this->_privateAttributeNames, 'email'); + return $this->email($email); + } + + /** + * Sets the user's full name. + */ public function name($name) { $this->_name = $name; return $this; } + /** + * Sets the user's full name, and ensures that the name attribute will not be sent back to LaunchDarkly. + */ + public function privateName($name) { + array_push($this->_privateAttributeNames, 'name'); + return $this->name($name); + } + + /** + * Sets the user's avatar. + */ public function avatar($avatar) { $this->_avatar = $avatar; return $this; } + /** + * Sets the user's avatar, and ensures that the avatar attribute will not be sent back to LaunchDarkly. + */ + public function privateAvatar($avatar) { + array_push($this->_privateAttributeNames, 'avatar'); + return $this->avatar($avatar); + } + + /** + * Sets the user's first name. + */ public function firstName($firstName) { $this->_firstName = $firstName; return $this; } + /** + * Sets the user's first name, and ensures that the first name attribute will not be sent back to LaunchDarkly. + */ + public function privateFirstName($firstName) { + array_push($this->_privateAttributeNames, 'firstName'); + return $this->firstName($firstName); + } + + /** + * Sets the user's last name. + */ public function lastName($lastName) { $this->_lastName = $lastName; return $this; } + /** + * Sets the user's last name, and ensures that the last name attribute will not be sent back to LaunchDarkly. + */ + public function privateLastName($lastName) { + array_push($this->_privateAttributeNames, 'lastName'); + return $this->lastName($lastName); + } + + /** + * Sets whether this user is anonymous. The default is false. + */ public function anonymous($anonymous) { $this->_anonymous = $anonymous; return $this; } + /** + * Sets any number of custom attributes for the user. + * @param array $custom An associative array of custom attribute names and values. + */ public function custom($custom) { $this->_custom = $custom; return $this; } + /** + * Sets a single custom attribute for the user. + */ + public function customAttribute($customKey, $customValue) { + $this->_custom[$customKey] = $customValue; + return $this; + } + + /** + * Sets a single custom attribute for the user, and ensures that the attribute will not be sent back to LaunchDarkly. + */ + public function privateCustomAttribute($customKey, $customValue) { + array_push($this->_privateAttributeNames, $customKey); + return $this->customAttribute($customKey, $customValue); + } + public function build() { - return new LDUser($this->_key, $this->_secondary, $this->_ip, $this->_country, $this->_email, $this->_name, $this->_avatar, $this->_firstName, $this->_lastName, $this->_anonymous, $this->_custom); + return new LDUser($this->_key, $this->_secondary, $this->_ip, $this->_country, $this->_email, $this->_name, $this->_avatar, $this->_firstName, $this->_lastName, $this->_anonymous, $this->_custom, $this->_privateAttributeNames); } } \ No newline at end of file diff --git a/src/LaunchDarkly/Util.php b/src/LaunchDarkly/Util.php index e1d84897a..b2eb3b246 100644 --- a/src/LaunchDarkly/Util.php +++ b/src/LaunchDarkly/Util.php @@ -36,7 +36,7 @@ public static function currentTimeUnixMillis() { */ public static function newFeatureRequestEvent($key, $user, $value, $default, $version = null, $prereqOf = null) { $event = array(); - $event['user'] = $user->toJSON(); + $event['user'] = $user; $event['value'] = $value; $event['kind'] = "feature"; $event['creationDate'] = Util::currentTimeUnixMillis(); diff --git a/tests/EventSerializerTest.php b/tests/EventSerializerTest.php new file mode 100644 index 000000000..5f8307e76 --- /dev/null +++ b/tests/EventSerializerTest.php @@ -0,0 +1,192 @@ +firstName('Sue') + ->custom(array('bizzle' => 'def', 'dizzle' => 'ghi')) + ->build(); + } + + private function getUserSpecifyingOwnPrivateAttr() { + $ub = new LDUserBuilder('abc'); + return $ub + ->firstName('Sue') + ->customAttribute('bizzle', 'def') + ->privateCustomAttribute('dizzle', 'ghi') + ->build(); + } + + private function getFullUserResult() { + return array( + 'key' => 'abc', + 'firstName' => 'Sue', + 'custom' => array('bizzle' => 'def', 'dizzle' => 'ghi') + ); + } + + private function getUserResultWithAllAttrsHidden() { + return array( + 'key' => 'abc', + 'custom' => array(), + 'privateAttrs' => array('bizzle', 'dizzle', 'firstName') + ); + } + + private function getUserResultWithSomeAttrsHidden() { + return array( + 'key' => 'abc', + 'custom' => array('dizzle' => 'ghi'), + 'privateAttrs' => array('bizzle', 'firstName') + ); + } + + private function getUserResultWithOwnSpecifiedAttrHidden() { + return array( + 'key' => 'abc', + 'firstName' => 'Sue', + 'custom' => array('bizzle' => 'def'), + 'privateAttrs' => array('dizzle') + ); + } + + private function makeEvent($user) { + return array( + 'creationDate' => 1000000, + 'key' => 'abc', + 'kind' => 'thing', + 'user' => $user + ); + } + + private function getJsonForUserBySerializingEvent($user) { + $es = new EventSerializer(array()); + $event = $this->makeEvent($user); + $json = json_decode($es->serializeEvents(array($event)), true); + return $json[0]['user']; + } + + public function testAllUserAttrsSerialized() { + $es = new EventSerializer(array()); + $event = $this->makeEvent($this->getUser()); + $json = $es->serializeEvents(array($event)); + $expected = $this->makeEvent($this->getFullUserResult()); + $this->assertEquals(array($expected), json_decode($json, true)); + } + + public function testAllUserAttrsPrivate() { + $es = new EventSerializer(array('all_attributes_private' => true)); + $event = $this->makeEvent($this->getUser()); + $json = $es->serializeEvents(array($event)); + $expected = $this->makeEvent($this->getUserResultWithAllAttrsHidden()); + $this->assertEquals(array($expected), json_decode($json, true)); + } + + public function testSomeUserAttrsPrivate() { + $es = new EventSerializer(array('private_attribute_names' => array('firstName', 'bizzle'))); + $event = $this->makeEvent($this->getUser()); + $json = $es->serializeEvents(array($event)); + $expected = $this->makeEvent($this->getUserResultWithSomeAttrsHidden()); + $this->assertEquals(array($expected), json_decode($json, true)); + } + + public function testPerUserPrivateAttr() { + $es = new EventSerializer(array()); + $event = $this->makeEvent($this->getUserSpecifyingOwnPrivateAttr()); + $json = $es->serializeEvents(array($event)); + $expected = $this->makeEvent($this->getUserResultWithOwnSpecifiedAttrHidden()); + $this->assertEquals(array($expected), json_decode($json, true)); + } + + public function testPerUserPrivateAttrPlusGlobalPrivateAttrs() { + $es = new EventSerializer(array('private_attribute_names' => array('firstName', 'bizzle'))); + $event = $this->makeEvent($this->getUserSpecifyingOwnPrivateAttr()); + $json = $es->serializeEvents(array($event)); + $expected = $this->makeEvent($this->getUserResultWithAllAttrsHidden()); + $this->assertEquals(array($expected), json_decode($json, true)); + } + + public function testUserKey() { + $builder = new LDUserBuilder("foo@bar.com"); + $user = $builder->build(); + $json = $this->getJsonForUserBySerializingEvent($user); + $this->assertEquals("foo@bar.com", $json['key']); + } + + public function testEmptyCustom() { + $builder = new LDUserBuilder("foo@bar.com"); + $user = $builder->build(); + $json = $this->getJsonForUserBySerializingEvent($user); + $this->assertFalse(isset($json['custom'])); + } + + public function testUserSecondary() { + $builder = new LDUserBuilder("foo@bar.com"); + $user = $builder->secondary("secondary")->build(); + $json = $this->getJsonForUserBySerializingEvent($user); + $this->assertEquals("secondary", $json['secondary']); + } + + public function testUserIP() { + $builder = new LDUserBuilder("foo@bar.com"); + $user = $builder->ip("127.0.0.1")->build(); + $json = $this->getJsonForUserBySerializingEvent($user); + $this->assertEquals("127.0.0.1", $json['ip']); + } + + public function testUserCountry() { + $builder = new LDUserBuilder("foo@bar.com"); + $user = $builder->country("US")->build(); + $json = $this->getJsonForUserBySerializingEvent($user); + $this->assertEquals("US", $json['country']); + } + + public function testUserEmail() { + $builder = new LDUserBuilder("foo@bar.com"); + $user = $builder->email("foo+test@bar.com")->build(); + $json = $this->getJsonForUserBySerializingEvent($user); + $this->assertEquals("foo+test@bar.com", $json['email']); + } + + public function testUserName() { + $builder = new LDUserBuilder("foo@bar.com"); + $user = $builder->name("Foo Bar")->build(); + $json = $this->getJsonForUserBySerializingEvent($user); + $this->assertEquals("Foo Bar", $json['name']); + } + + public function testUserAvatar() { + $builder = new LDUserBuilder("foo@bar.com"); + $user = $builder->avatar("http://www.gravatar.com/avatar/1")->build(); + $json = $this->getJsonForUserBySerializingEvent($user); + $this->assertEquals("http://www.gravatar.com/avatar/1", $json['avatar']); + } + + public function testUserFirstName() { + $builder = new LDUserBuilder("foo@bar.com"); + $user = $builder->firstName("Foo")->build(); + $json = $this->getJsonForUserBySerializingEvent($user); + $this->assertEquals("Foo", $json['firstName']); + } + + public function testUserLastName() { + $builder = new LDUserBuilder("foo@bar.com"); + $user = $builder->lastName("Bar")->build(); + $json = $this->getJsonForUserBySerializingEvent($user); + $this->assertEquals("Bar", $json['lastName']); + } + + public function testUserAnonymous() { + $builder = new LDUserBuilder("foo@bar.com"); + $user = $builder->anonymous(true)->build(); + $json = $this->getJsonForUserBySerializingEvent($user); + $this->assertEquals(true, $json['anonymous']); + } +} + diff --git a/tests/FeatureFlagTest.php b/tests/FeatureFlagTest.php index c61fb3e01..b778d9154 100644 --- a/tests/FeatureFlagTest.php +++ b/tests/FeatureFlagTest.php @@ -140,5 +140,52 @@ public function testDecode() { FeatureFlag::decode(json_decode(FeatureFlagTest::$json1, true)); FeatureFlag::decode(json_decode(FeatureFlagTest::$json2, true)); } + + public function dataDecodeMulti() + { + return array( + 'null-prerequisites' => array( + array( + 'key' => 'sysops-test', + 'version' => 14, + 'on' => true, + 'prerequisites' => NULL, + 'salt' => 'c3lzb3BzLXRlc3Q=', + 'sel' => '8ed13de1bfb14507ba7e6dde01f3e035', + 'targets' => array( + array( + 'values' => array(), + 'variation' => 0, + ), + array( + 'values' => array(), + 'variation' => 1, + ), + ), + 'rules' => array(), + 'fallthrough' => array( + 'variation' => 0, + ), + 'offVariation' => NULL, + 'variations' => array( + true, + false, + ), + 'deleted' => false, + ) + ), + ); + } + + /** + * @dataProvider dataDecodeMulti + * @param array $feature + */ + public function testDecodeMulti(array $feature) + { + $featureFlag = FeatureFlag::decode($feature); + + self::assertInstanceOf('LaunchDarkly\FeatureFlag', $featureFlag); + } } diff --git a/tests/LDClientTest.php b/tests/LDClientTest.php index bac929161..c906cbf3a 100644 --- a/tests/LDClientTest.php +++ b/tests/LDClientTest.php @@ -1,10 +1,12 @@ '\\LaunchDarkly\Tests\\MockFeatureRequester', + 'feature_requester_class' => 'LaunchDarkly\Tests\MockFeatureRequester', 'events' => false )); @@ -28,7 +30,7 @@ public function testToggleDefault() { public function testToggleFromArray() { MockFeatureRequester::$val = null; $client = new LDClient("someKey", array( - 'feature_requester_class' => '\\LaunchDarkly\Tests\\MockFeatureRequester', + 'feature_requester_class' => 'LaunchDarkly\Tests\MockFeatureRequester', 'events' => false, 'defaults' => array('foo' => 'fromarray') )); @@ -41,7 +43,7 @@ public function testToggleFromArray() { public function testToggleEvent() { MockFeatureRequester::$val = null; $client = new LDClient("someKey", array( - 'feature_requester_class' => '\\LaunchDarkly\Tests\\MockFeatureRequester', + 'feature_requester_class' => 'LaunchDarkly\Tests\MockFeatureRequester', 'events' => true )); @@ -53,11 +55,32 @@ public function testToggleEvent() { $this->assertEquals(1, sizeof($queue)); } + public function testOnlyValidFeatureRequester() { + $this->setExpectedException('InvalidArgumentException'); + new LDClient("BOGUS_SDK_KEY", array('feature_requester_class' => '\stdClass')); + } + public function testSecureModeHash() { $client = new LDClient("secret", array('offline' => true)); $user = new LDUser("Message"); $this->assertEquals("aa747c502a898200f9e4fa21bac68136f886a0e27aec70ba06daf2e2a5cb5597", $client->secureModeHash($user)); } + + public function testLoggerInterfaceWarn() + { + // Use LoggerInterface impl, instead of concreate Logger from Monolog, to demonstrate the problem with `warn`. + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + //TODO $logger->expects(self::atLeastOnce())->method('warning'); + + $client = new LDClient('secret', array( + 'logger' => $logger, + )); + + $user = new LDUser(''); + + $client->variation('MyFeature', $user); + } } diff --git a/tests/LDDFeatureRequesterTest.php b/tests/LDDFeatureRequesterTest.php new file mode 100644 index 000000000..877ff9837 --- /dev/null +++ b/tests/LDDFeatureRequesterTest.php @@ -0,0 +1,72 @@ +logger = new NullLogger(); + + $this->predisClient = $this->getMockBuilder('Predis\ClientInterface'); + $this->predisClient = $this->predisClient->setMethods(array('hget')) + ->getMockForAbstractClass(); + } + + public function testGet() + { + $sut = new LDDFeatureRequester('example.com', 'MySdkKey', array( + 'logger' => $this->logger, + 'predis_client' => $this->predisClient, + )); + + $this->predisClient->method('hget')->with('launchdarkly:features', 'foo') + ->willReturn(json_encode(array( + 'key' => 'foo', + 'version' => 14, + 'on' => true, + 'prerequisites' => array(), + 'salt' => 'c3lzb3BzLXRlc3Q=', + 'sel' => '8ed13de1bfb14507ba7e6dde01f3e035', + 'targets' => array( + array( + 'values' => array(), + 'variation' => 0, + ), + array( + 'values' => array(), + 'variation' => 1, + ), + ), + 'rules' => array(), + 'fallthrough' => array( + 'variation' => 0, + ), + 'offVariation' => null, + 'variations' => array( + true, + false, + ), + 'deleted' => false, + ))); + + $featureFlag = $sut->get('foo'); + + self::assertInstanceOf('LaunchDarkly\FeatureFlag', $featureFlag); + self::assertTrue($featureFlag->isOn()); + } +} diff --git a/tests/LDUserTest.php b/tests/LDUserTest.php index 0dfbb0d01..fd8a635dc 100644 --- a/tests/LDUserTest.php +++ b/tests/LDUserTest.php @@ -9,96 +9,140 @@ public function testLDUserKey() { $builder = new LDUserBuilder("foo@bar.com"); $user = $builder->build(); $this->assertEquals("foo@bar.com", $user->getKey()); - $json = $user->toJSON(); - $this->assertEquals("foo@bar.com", $json['key']); } public function testCoerceLDUserKey() { $builder = new LDUserBuilder(3); $user = $builder->build(); $this->assertEquals("string", gettype($user->getKey())); - $json = $user->toJSON(); - $this->assertEquals("string", gettype($json['key'])); } public function testEmptyCustom() { $builder = new LDUserBuilder("foo@bar.com"); $user = $builder->build(); - $json = $user->toJSON(); - $this->assertTrue(!isset($json['custom'])); } public function testLDUserSecondary() { $builder = new LDUserBuilder("foo@bar.com"); $user = $builder->secondary("secondary")->build(); $this->assertEquals("secondary", $user->getSecondary()); - $json = $user->toJSON(); - $this->assertEquals("secondary", $json['secondary']); + } + + public function testLDUserPrivateSecondary() { + $builder = new LDUserBuilder("foo@bar.com"); + $user = $builder->privateSecondary("secondary")->build(); + $this->assertEquals("secondary", $user->getSecondary()); + $this->assertEquals(array("secondary"), $user->getPrivateAttributeNames()); } public function testLDUserIP() { $builder = new LDUserBuilder("foo@bar.com"); $user = $builder->ip("127.0.0.1")->build(); $this->assertEquals("127.0.0.1", $user->getIP()); - $json = $user->toJSON(); - $this->assertEquals("127.0.0.1", $json['ip']); + } + + public function testLDUserPrivateIP() { + $builder = new LDUserBuilder("foo@bar.com"); + $user = $builder->privateIp("127.0.0.1")->build(); + $this->assertEquals("127.0.0.1", $user->getIP()); + $this->assertEquals(array("ip"), $user->getPrivateAttributeNames()); } public function testLDUserCountry() { $builder = new LDUserBuilder("foo@bar.com"); $user = $builder->country("US")->build(); $this->assertEquals("US", $user->getCountry()); - $json = $user->toJSON(); - $this->assertEquals("US", $json['country']); + } + + public function testLDUserPrivateCountry() { + $builder = new LDUserBuilder("foo@bar.com"); + $user = $builder->privateCountry("US")->build(); + $this->assertEquals("US", $user->getCountry()); + $this->assertEquals(array("country"), $user->getPrivateAttributeNames()); } public function testLDUserEmail() { $builder = new LDUserBuilder("foo@bar.com"); $user = $builder->email("foo+test@bar.com")->build(); $this->assertEquals("foo+test@bar.com", $user->getEmail()); - $json = $user->toJSON(); - $this->assertEquals("foo+test@bar.com", $json['email']); + } + + public function testLDUserPrivateEmail() { + $builder = new LDUserBuilder("foo@bar.com"); + $user = $builder->privateEmail("foo+test@bar.com")->build(); + $this->assertEquals("foo+test@bar.com", $user->getEmail()); + $this->assertEquals(array("email"), $user->getPrivateAttributeNames()); } public function testLDUserName() { $builder = new LDUserBuilder("foo@bar.com"); $user = $builder->name("Foo Bar")->build(); $this->assertEquals("Foo Bar", $user->getName()); - $json = $user->toJSON(); - $this->assertEquals("Foo Bar", $json['name']); + } + + public function testLDUserPrivateName() { + $builder = new LDUserBuilder("foo@bar.com"); + $user = $builder->privateName("Foo Bar")->build(); + $this->assertEquals("Foo Bar", $user->getName()); + $this->assertEquals(array("name"), $user->getPrivateAttributeNames()); } public function testLDUserAvatar() { $builder = new LDUserBuilder("foo@bar.com"); $user = $builder->avatar("http://www.gravatar.com/avatar/1")->build(); $this->assertEquals("http://www.gravatar.com/avatar/1", $user->getAvatar()); - $json = $user->toJSON(); - $this->assertEquals("http://www.gravatar.com/avatar/1", $json['avatar']); + } + + public function testLDUserPrivateAvatar() { + $builder = new LDUserBuilder("foo@bar.com"); + $user = $builder->privateAvatar("http://www.gravatar.com/avatar/1")->build(); + $this->assertEquals("http://www.gravatar.com/avatar/1", $user->getAvatar()); + $this->assertEquals(array("avatar"), $user->getPrivateAttributeNames()); } public function testLDUserFirstName() { $builder = new LDUserBuilder("foo@bar.com"); $user = $builder->firstName("Foo")->build(); $this->assertEquals("Foo", $user->getFirstName()); - $json = $user->toJSON(); - $this->assertEquals("Foo", $json['firstName']); + } + + public function testLDUserPrivateFirstName() { + $builder = new LDUserBuilder("foo@bar.com"); + $user = $builder->privateFirstName("Foo")->build(); + $this->assertEquals("Foo", $user->getFirstName()); + $this->assertEquals(array("firstName"), $user->getPrivateAttributeNames()); } public function testLDUserLastName() { $builder = new LDUserBuilder("foo@bar.com"); $user = $builder->lastName("Bar")->build(); $this->assertEquals("Bar", $user->getLastName()); - $json = $user->toJSON(); - $this->assertEquals("Bar", $json['lastName']); + } + + public function testLDUserPrivateLastName() { + $builder = new LDUserBuilder("foo@bar.com"); + $user = $builder->privateLastName("Bar")->build(); + $this->assertEquals("Bar", $user->getLastName()); + $this->assertEquals(array("lastName"), $user->getPrivateAttributeNames()); + } + + public function testLDUserCustom() { + $builder = new LDUserBuilder("foo@bar.com"); + $user = $builder->customAttribute("foo", "bar")->customAttribute("baz", "boo")->build(); + $this->assertEquals(array("foo" => "bar", "baz" => "boo"), $user->getCustom()); + } + + public function testLDUserPrivateCustom() { + $builder = new LDUserBuilder("foo@bar.com"); + $user = $builder->privateCustomAttribute("foo", "bar")->privateCustomAttribute("baz", "boo")->build(); + $this->assertEquals(array("foo" => "bar", "baz" => "boo"), $user->getCustom()); + $this->assertEquals(array("foo", "baz"), $user->getPrivateAttributeNames()); } public function testLDUserAnonymous() { $builder = new LDUserBuilder("foo@bar.com"); $user = $builder->anonymous(true)->build(); $this->assertEquals(true, $user->getAnonymous()); - $json = $user->toJSON(); - $this->assertEquals(true, $json['anonymous']); - } public function testLDUserBlankKey() { @@ -116,4 +160,3 @@ public function testLDUserBlankKey() { $this->assertFalse($user->isKeyBlank()); } } -