From 8dd917c4b2dcc05348954e2f7ddb527cac443706 Mon Sep 17 00:00:00 2001 From: Benjamin Friedman Date: Wed, 8 Nov 2017 22:11:20 -0800 Subject: [PATCH 1/8] updated ParseQuery::_setConditions --- src/Parse/ParseAudience.php | 15 ++++++++++ src/Parse/ParseQuery.php | 36 +++++++++++++++++++++-- tests/Parse/ParseAudienceTest.php | 31 ++++++++++++++++++++ tests/Parse/ParseQueryTest.php | 48 +++++++++++++++++++++++++++++-- 4 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 src/Parse/ParseAudience.php create mode 100644 tests/Parse/ParseAudienceTest.php diff --git a/src/Parse/ParseAudience.php b/src/Parse/ParseAudience.php new file mode 100644 index 00000000..52d034d5 --- /dev/null +++ b/src/Parse/ParseAudience.php @@ -0,0 +1,15 @@ + $entry) { - foreach ($entry as $condition => $value) { - $this->addCondition($key, $condition, $value); + switch($key) { + case 'where': + $this->where = $entry; + break; + + case 'include': + $this->includes = explode(',', $entry); + break; + + case 'keys': + $this->selectedKeys = explode(',', $entry); + break; + + case 'limit': + $this->limit = $entry; + break; + + // skip + case 'skip': + $this->skip = $entry; + break; + + // orderBy + case 'order': + $this->orderBy = explode(',', $entry); + break; + + // whether this query is for count or not + case 'count': + $this->count = $entry; + break; + + default: + throw new ParseException("Unknown condition to set '{$key}''"); } } } diff --git a/tests/Parse/ParseAudienceTest.php b/tests/Parse/ParseAudienceTest.php new file mode 100644 index 00000000..d8c73d13 --- /dev/null +++ b/tests/Parse/ParseAudienceTest.php @@ -0,0 +1,31 @@ +assertEquals([ 'where' => [ - [ - 'key' => 'value' - ] + 'key' => 'value' ] ], $query->_getOptions()); } + /** + * @group query-set-conditions + */ + public function testGetAndSetConditions() + { + $query = new ParseQuery('TestObject'); + $query->equalTo('key', 'value'); + $query->notEqualTo('key2', 'value2'); + $query->includeKey(['include1','include2']); + $query->contains('container', 'item'); + $query->addDescending('desc'); + $query->addAscending('asc'); + $query->select(['select1','select2']); + $query->skip(24); + $query->limit(42); + $conditions = $query->_getOptions(); + + $this->assertEquals([ + 'where' => [ + 'key' => 'value', + 'key2' => [ + '$ne' => 'value2', + ], + 'container' => [ + '$regex' => '\Qitem\E' + ] + ], + 'include' => 'include1,include2', + 'keys' => 'select1,select2', + 'limit' => 42, + 'skip' => 24, + 'order' => '-desc,asc' + ], $conditions, 'Conditions were different than expected'); + + $query2 = new ParseQuery('TestObject'); + $query2->_setConditions($conditions); + + $this->assertEquals( + $query, + $query2, + 'Conditions set on query did not give the expected result' + ); + } + public function testBadConditions() { $this->setExpectedException( From 80e38b71aad7aa743a4e993c9c88b206cffe35f8 Mon Sep 17 00:00:00 2001 From: Benjamin Friedman Date: Wed, 8 Nov 2017 22:12:29 -0800 Subject: [PATCH 2/8] Adds ParseAudience --- src/Parse/ParseAudience.php | 96 ++++++++++++++++++++++++-- tests/Parse/ParseAudienceTest.php | 110 +++++++++++++++++++++++++++--- 2 files changed, 191 insertions(+), 15 deletions(-) diff --git a/src/Parse/ParseAudience.php b/src/Parse/ParseAudience.php index 52d034d5..5e29ced3 100644 --- a/src/Parse/ParseAudience.php +++ b/src/Parse/ParseAudience.php @@ -1,15 +1,99 @@ + * @package Parse + */ +class ParseAudience extends ParseObject { + /** + * Parse Class name + * + * @var string + */ + public static $parseClassName = '_Audience'; + + /** + * Create a new audience with name & query + * + * @param string $name Name of the audience to create + * @param ParseQuery $query Query to create audience with + * @return ParseAudience + */ + public static function createAudience($name, $query) + { + $audience = new ParseAudience(); + $audience->setName($name); + $audience->setQuery($query); + return $audience; + } + + /** + * Sets the name of this audience + * + * @param string $name Name to set + */ + public function setName($name) + { + $this->set('name', $name); + } + + /** + * Gets the name for this audience + * + * @return string + */ + public function getName() + { + return $this->get('name'); + } + + /** + * Sets the query for this Audience + * + * @param ParseQuery $query Query for this Audience + */ + public function setQuery($query) + { + $this->set('query', json_encode($query->_getOptions())); + } + + /** + * Gets the query for this Audience + * + * @return ParseQuery + */ + public function getQuery() + { + $query = new ParseQuery('_Installation'); + $query->_setConditions(json_decode($this->get('query'), true)); + return $query; + } + + /** + * Gets when this Audience was last used + * + * @return \DateTime|null + */ + public function getLastUsed() + { + return $this->get('lastUsed'); + } + /** + * Gets the times this Audience has been used + * + * @return int + */ + public function getTimesUsed() + { + return $this->get('timesUsed'); + } } \ No newline at end of file diff --git a/tests/Parse/ParseAudienceTest.php b/tests/Parse/ParseAudienceTest.php index d8c73d13..f5e97344 100644 --- a/tests/Parse/ParseAudienceTest.php +++ b/tests/Parse/ParseAudienceTest.php @@ -9,23 +9,115 @@ namespace Parse\Test; -use Parse\ParseClient; +use Parse\ParseAudience; +use Parse\ParseInstallation; +use Parse\ParseObject; +use Parse\ParsePush; +use Parse\ParseQuery; class ParseAudienceTest extends \PHPUnit_Framework_TestCase { + public function setUp() + { + Helper::clearClass('_Audience'); + Helper::clearClass('_Installation'); + } + + public function createInstallations() + { + $androidInstallation = new ParseInstallation(); + $androidInstallation->set('installationId', 'id1'); + $androidInstallation->set('deviceToken', '12345'); + $androidInstallation->set('deviceType', 'android'); + $androidInstallation->save(true); + + $iOSInstallation = new ParseInstallation(); + $iOSInstallation->set('installationId', 'id2'); + $iOSInstallation->set('deviceToken', '54321'); + $iOSInstallation->set('deviceType', 'ios'); + $iOSInstallation->save(); + + ParseObject::saveAll([ + $androidInstallation, + $iOSInstallation + ]); + } + + /** + * @group audience-tests + */ + public function testPushAudiences() + { + $this->createInstallations(); + + $androidQuery = ParseInstallation::query() + ->equalTo('deviceType', 'android'); + + $audience = ParseAudience::createAudience('MyAudience', $androidQuery); + $audience->save(); + + // count no master should be 0 + $query = new ParseQuery('_Audience'); + $this->assertEquals(0, $query->count(), 'No master was not 0'); + + $query = new ParseQuery('_Audience'); + $audience = $query->first(true); + $this->assertNotNull($audience); + + $this->assertEquals('MyAudience', $audience->getName()); + $this->assertEquals($androidQuery, $audience->getQuery()); + $this->assertNull($audience->getLastUsed()); + $this->assertEquals(0, $audience->getTimesUsed()); + } + + /** + * @group audience-tests + */ + public function testSaveWithoutMaster() + { + $query = ParseAudience::query(); + $this->assertEquals(0, $query->count(true), 'Did not start at 0'); + + $audience = ParseAudience::createAudience( + 'MyAudience', + ParseInstallation::query() + ->equalTo('deviceType', 'android') + ); + $audience->save(); + + $query = ParseAudience::query(); + $this->assertEquals(1, $query->count(true), 'Did not end at 1'); + } + /** * @group audience-tests */ - public function testGetPushAudiences() + public function testPushWithAudience() { - $response = ParseClient::_request( - 'GET', - 'push_audiences', - null, - null, - true + $this->createInstallations(); + + $audience = ParseAudience::createAudience( + 'MyAudience', + ParseInstallation::query() + ->equalTo('deviceType', 'android') ); + $audience->save(true); + + ParsePush::send([ + 'data' => [ + 'alert' => 'sample message' + ], + 'where' => $audience->getQuery(), + 'audience_id' => $audience->getObjectId() + ], true); + + $query = ParseAudience::query(); + $found = $query->find(true); + $this->assertEquals(1, count($found)); + $audience = $found[0]; + + $this->assertEquals(1, $audience->getTimesUsed()); + $this->assertNotNull($audience->getLastUsed()); - echo json_encode($response, JSON_PRETTY_PRINT); } } \ No newline at end of file From da6fba8a2604e325d80de806988153f0ae3f3551 Mon Sep 17 00:00:00 2001 From: Benjamin Friedman Date: Wed, 8 Nov 2017 22:13:00 -0800 Subject: [PATCH 3/8] Handle registering and testing for new ParseAudience class --- src/Parse/ParseClient.php | 4 ++++ tests/Parse/ParseObjectTest.php | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/Parse/ParseClient.php b/src/Parse/ParseClient.php index bdabef9c..259d8147 100755 --- a/src/Parse/ParseClient.php +++ b/src/Parse/ParseClient.php @@ -151,6 +151,10 @@ public static function initialize( ParsePushStatus::registerSubclass(); } + if(!ParseObject::hasRegisteredSubclass('_Audience')) { + ParseAudience::registerSubclass(); + } + ParseSession::registerSubclass(); self::$applicationId = $app_id; self::$restKey = $rest_key; diff --git a/tests/Parse/ParseObjectTest.php b/tests/Parse/ParseObjectTest.php index 1bb791a9..454234b1 100644 --- a/tests/Parse/ParseObjectTest.php +++ b/tests/Parse/ParseObjectTest.php @@ -7,6 +7,7 @@ use Parse\Internal\SetOperation; use Parse\ParseACL; use Parse\ParseAggregateException; +use Parse\ParseAudience; use Parse\ParseClient; use Parse\ParseException; use Parse\ParseFile; @@ -1077,6 +1078,7 @@ public function testNoRegisteredSubclasses() ParseInstallation::_unregisterSubclass(); ParseSession::_unregisterSubclass(); ParsePushStatus::_unregisterSubclass(); + ParseAudience::_unregisterSubclass(); new ParseObject('TestClass'); } From 919155b3d48989a268c2bae4164f75607f55ca86 Mon Sep 17 00:00:00 2001 From: Benjamin Friedman Date: Wed, 8 Nov 2017 22:13:22 -0800 Subject: [PATCH 4/8] Modify test for push status to reflect change in _setConditions --- src/Parse/ParsePushStatus.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Parse/ParsePushStatus.php b/src/Parse/ParsePushStatus.php index eb0e42d3..b382ad69 100644 --- a/src/Parse/ParsePushStatus.php +++ b/src/Parse/ParsePushStatus.php @@ -75,7 +75,9 @@ public function getPushQuery() $query = new ParseQuery(self::$parseClassName); // set the conditions - $query->_setConditions($queryConditions); + $query->_setConditions([ + 'where' => $queryConditions + ]); return $query; } From b0892b7a584ecd998580273e98a9d748d5a79f59 Mon Sep 17 00:00:00 2001 From: Benjamin Friedman Date: Wed, 8 Nov 2017 22:27:39 -0800 Subject: [PATCH 5/8] updated README --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index 83ff9164..b25f101e 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ from your PHP app or script. Designed to work with the self-hosted Parse Server - [Push Notifications](#push) - [Push to Channels](#push-to-channels) - [Push with Query](#push-with-query) + - [Push with Audience](#push-with-audience) - [Push Status](#push-status) - [Server Info](#server-info) - [Version](#version) @@ -216,6 +217,7 @@ use Parse\ParseClient; use Parse\ParsePushStatus; use Parse\ParseServerInfo; use Parse\ParseLogs; +use Parse\ParseAudience; ``` ### Parse Objects @@ -428,6 +430,40 @@ ParsePush::send(array( ), true); ``` +#### Push with Audience + +If you want to keep track of your sends when using queries you can use the `ParseAudience` class. +You can create and configure your Audience objects with a name and query. +When you indicate it's being used in a push the `lastUsed` and `timesUsed` values are updated for you. +```php +$iosQuery = new ParseQuery(); +$iosQuery->equalTo("deviceType", "ios"); + +// create & save your audience +$audience = ParseAudience::createAudience( + 'MyiOSAudience', + $iosQuery +); +$audience->save(true); + +// send a push using the query in this audience and it's id +ParsePush::send([ + 'data' => [ + 'alert' => 'hello ios users!' + ], + 'where' => $audience->getQuery(), + 'audience_id' => $audience->getObjectId() +], true); + +// fetch changes to this audience +$audience->fetch(true); + +// get last & times used for tracking +$timesUsed = $audience->getTimesUsed(); +$lastUsed = $audience->getLastUsed(); +``` +Audiences provide you with a convenient way to group your queries and keep track of how often and when you send to them. + #### Push Status If your server supports it you can extract and check the current status of your pushes. From b006f52cca7877008891aa9db20c5b2d14d5fc99 Mon Sep 17 00:00:00 2001 From: Benjamin Friedman Date: Wed, 8 Nov 2017 22:28:06 -0800 Subject: [PATCH 6/8] lint --- src/Parse/ParseAudience.php | 2 +- src/Parse/ParseClient.php | 2 +- src/Parse/ParseQuery.php | 2 +- tests/Parse/ParseAudienceTest.php | 15 ++------------- 4 files changed, 5 insertions(+), 16 deletions(-) diff --git a/src/Parse/ParseAudience.php b/src/Parse/ParseAudience.php index 5e29ced3..27a79da8 100644 --- a/src/Parse/ParseAudience.php +++ b/src/Parse/ParseAudience.php @@ -96,4 +96,4 @@ public function getTimesUsed() { return $this->get('timesUsed'); } -} \ No newline at end of file +} diff --git a/src/Parse/ParseClient.php b/src/Parse/ParseClient.php index 259d8147..5a0b7d72 100755 --- a/src/Parse/ParseClient.php +++ b/src/Parse/ParseClient.php @@ -151,7 +151,7 @@ public static function initialize( ParsePushStatus::registerSubclass(); } - if(!ParseObject::hasRegisteredSubclass('_Audience')) { + if (!ParseObject::hasRegisteredSubclass('_Audience')) { ParseAudience::registerSubclass(); } diff --git a/src/Parse/ParseQuery.php b/src/Parse/ParseQuery.php index 7680eb51..3d5a7d8e 100755 --- a/src/Parse/ParseQuery.php +++ b/src/Parse/ParseQuery.php @@ -152,7 +152,7 @@ public function _setConditions($conditions) // iterate over and add each condition foreach ($conditions as $key => $entry) { - switch($key) { + switch ($key) { case 'where': $this->where = $entry; break; diff --git a/tests/Parse/ParseAudienceTest.php b/tests/Parse/ParseAudienceTest.php index f5e97344..b885330a 100644 --- a/tests/Parse/ParseAudienceTest.php +++ b/tests/Parse/ParseAudienceTest.php @@ -1,14 +1,7 @@ $audience->getObjectId() ], true); - $query = ParseAudience::query(); - $found = $query->find(true); - $this->assertEquals(1, count($found)); - $audience = $found[0]; + $audience->fetch(true); $this->assertEquals(1, $audience->getTimesUsed()); $this->assertNotNull($audience->getLastUsed()); - } -} \ No newline at end of file +} From e78a8b1e145e2f956b182963ace58de1c3f93658 Mon Sep 17 00:00:00 2001 From: Benjamin Friedman Date: Wed, 8 Nov 2017 22:37:18 -0800 Subject: [PATCH 7/8] typo --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b25f101e..f1c852b4 100644 --- a/README.md +++ b/README.md @@ -436,7 +436,7 @@ If you want to keep track of your sends when using queries you can use the `Pars You can create and configure your Audience objects with a name and query. When you indicate it's being used in a push the `lastUsed` and `timesUsed` values are updated for you. ```php -$iosQuery = new ParseQuery(); +$iosQuery = ParseInstallation::getQuery(); $iosQuery->equalTo("deviceType", "ios"); // create & save your audience @@ -447,6 +447,8 @@ $audience = ParseAudience::createAudience( $audience->save(true); // send a push using the query in this audience and it's id +// The 'audience_id' is what allows parse to update 'lastUsed' and 'timesUsed' +// You could use any audience_id with any query and it will still update that audience ParsePush::send([ 'data' => [ 'alert' => 'hello ios users!' From af148fe0623f45e2f751aa399c5ddc49d88f39a7 Mon Sep 17 00:00:00 2001 From: Benjamin Friedman Date: Wed, 8 Nov 2017 23:17:30 -0800 Subject: [PATCH 8/8] test uncovered paths --- tests/Parse/ParseQueryTest.php | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/tests/Parse/ParseQueryTest.php b/tests/Parse/ParseQueryTest.php index 38b05d99..c3dd74da 100644 --- a/tests/Parse/ParseQueryTest.php +++ b/tests/Parse/ParseQueryTest.php @@ -2282,7 +2282,12 @@ public function testGetAndSetConditions() $query->addAscending('asc'); $query->select(['select1','select2']); $query->skip(24); + + // sets count = 1 and limit = 0 + $query->count(); + // reset limit up to 42 $query->limit(42); + $conditions = $query->_getOptions(); $this->assertEquals([ @@ -2299,7 +2304,8 @@ public function testGetAndSetConditions() 'keys' => 'select1,select2', 'limit' => 42, 'skip' => 24, - 'order' => '-desc,asc' + 'order' => '-desc,asc', + 'count' => 1 ], $conditions, 'Conditions were different than expected'); $query2 = new ParseQuery('TestObject'); @@ -2312,7 +2318,7 @@ public function testGetAndSetConditions() ); } - public function testBadConditions() + public function testNotArrayConditions() { $this->setExpectedException( '\Parse\ParseException', @@ -2322,4 +2328,20 @@ public function testBadConditions() $query = new ParseQuery('TestObject'); $query->_setConditions('not-an-array'); } + + /** + * @group query-set-conditions + */ + public function testUnknownCondition() + { + $this->setExpectedException( + '\Parse\ParseException', + 'Unknown condition to set \'unrecognized\'' + ); + + $query = new ParseQuery('TestObject'); + $query->_setConditions([ + 'unrecognized' => 1 + ]); + } }