If you haven’t set up your project yet, please head over to the PHP SDK setup guide or read the Installation section below to get up and running. You can also check out our API Reference for more detailed information about our SDK.
The Parse platform provides a complete backend solution for your mobile application. Generally, our goal is to eliminate the need for managing servers and writing server-side code. Our PHP SDK is for those applications and situations where server-side code is necessary, or simply preferred.
The Parse PHP SDK is fully open source, and anyone can contribute to make it better, or make their own changes if necessary. Check out the GitHub repository for more information.
The Parse PHP SDK requires version 5.4 or greater of the PHP runtime.
There are various ways to install and use this sdk. We’ll elaborate on a couple here. Note that the Parse PHP SDK requires PHP 5.4 or newer. It can also run on HHVM (recommended 3.0 or newer).
[Get Composer], the PHP package manager. Then create a composer.json file in your projects root folder, containing:
{
"require": {
"parse/php-sdk" : "1.4.*"
}
}
Run “composer install” to download the SDK and set up the autoloader, and then require it from your PHP script:
require 'vendor/autoload.php';
You can clone down this sdk using your favorite github client, or via the terminal.
git clone https://github.com/parse-community/parse-php-sdk.git
You can then include the autoload.php
file in your code to automatically load the Parse SDK classes.
require 'autoload.php';
If you downloaded this sdk using any other means you can treat it like you used the git method above.
Once it’s installed you need only require the autoload.php
to have access to the sdk.
Once you have access to the sdk you’ll need to set it up in order to begin working with parse-server.
After including the required files from the SDK, you need to initialize the ParseClient using your Parse API keys:
ParseClient::initialize( $app_id, $rest_key, $master_key );
If your server does not use or require a REST key you may initialize the ParseClient as follows, safely omitting the REST key:
ParseClient::initialize( $app_id, null, $master_key );
Directly after initializing the sdk you should set the server url.
// Users of Parse Server will need to point ParseClient at their remote URL and Mount Point:
ParseClient::setServerURL('https://my-parse-server.com:port','parse');
Notice Parse server’s default port is 1337
and the second parameter parse
is the route prefix of your parse server.
For example if your parse server’s url is http://example.com:1337/parse
then you can set the server url using the following snippet
ParseClient::setServerURL('https://example.com:1337','parse');
To verify that the server url and mount path you’ve provided are correct you can run a health check on your server.
$health = ParseClient::getServerHealth();
if($health['status'] === 200) {
// everything looks good!
}
If you wanted to analyze it further the health response may look something like this.
{
"status" : 200,
"response" : {
"status" : "ok"
}
}
The ‘status’ being the http response code, and the ‘response’ containing what the server replies with. Any additional details in the reply can be found under ‘response’, and you can use them to check and determine the availability of parse-server before you make requests.
Note that it is not guaranteed that ‘response’ will be a parsable json array. If the response cannot be decoded it will be returned as a string instead.
A couple examples of bad health responses could include an incorrect mount path, port or domain.
// ParseClient::setServerURL('http://localhost:1337', 'not-good');
{
"status": 404,
"response": "<!DOCTYPE html>...Cannot GET \/not-good\/health..."
}
// ParseClient::setServerURL('http://__uh__oh__.com', 'parse');
{
"status": 0,
"error": 6,
"error_message": "Couldn't resolve host '__uh__oh__.com'"
}
Keep in mind error
& error_message
may change depending on whether you are using the curl (may change across versions of curl) or stream client.
This SDK has the ability to change the underlying http client at your convenience. The default is to use the curl http client if none is set, there is also a stream http client that can be used as well.
Setting the http client can be done as follows:
// set curl http client (default if none set)
ParseClient::setHttpClient(new ParseCurlHttpClient());
// set stream http client
// ** requires 'allow_url_fopen' to be enabled in php.ini **
ParseClient::setHttpClient(new ParseStreamHttpClient());
If you have a need for an additional http client you can request one by opening an issue or by submitting a PR.
If you wish to build one yourself make sure your http client implements ParseHttpable
for it be compatible with the SDK. Once you have a working http client that enhances the SDK feel free to submit it in a PR so we can look into adding it in.
It is possible that your local setup may not be able to verify with peers over SSL/TLS. This may especially be the case if you do not have control over your local installation, such as for shared hosting.
If this is the case you may need to specify a Certificate Authority bundle. You can download such a bundle from http://curl.haxx.se/ca/cacert.pem to use for this purpose. This one happens to be a Mozilla CA certificate store, you don’t necessarily have to use this one but it’s recommended.
Once you have your bundle you can set it as follows:
// ** Use an Absolute path for your file! **
// holds one or more certificates to verify the peer with
ParseClient::setCAFile(__DIR__ . '/certs/cacert.pem');
Storing data on Parse is built around the ParseObject
. Each ParseObject
contains key-value pairs of JSON-compatible data. This data is schemaless, which means that you don’t need to specify ahead of time what keys exist on each ParseObject
. You simply set whatever key-value pairs you want, and our backend will store it.
For example, let’s say you’re tracking high scores for a game. A single ParseObject
could contain:
score: 1337, playerName: "Sean Plott", cheatMode: false
Keys must be alphanumeric strings. Values can be strings, numbers, booleans, or even sequential arrays and associative arrays - anything that can be JSON-encoded. Note however that Arrays and Associative Arrays require separate methods to set them on a ParseObject
.
Let’s say you want to save the GameScore
described above to the Parse Cloud. The interface is similar to a our other SDKs, including the save
method:
$gameScore = new ParseObject("GameScore");
$gameScore->set("score", 1337);
$gameScore->set("playerName", "Sean Plott");
$gameScore->set("cheatMode", false);
try {
$gameScore->save();
echo 'New object created with objectId: ' . $gameScore->getObjectId();
} catch (ParseException $ex) {
// Execute any logic that should take place if the save fails.
// error is a ParseException object with an error code and message.
echo 'Failed to create new object, with error message: ' . $ex->getMessage();
}
After this code runs, you will probably be wondering if anything really happened. To make sure the data was saved, you can look at the Data Browser in your app on Parse. You should see something like this:
{
"objectId": "xWMyZ4YEGZ",
"score": 1337,
"playerName": "Sean Plott",
"cheatMode": false,
"createdAt":"2022-01-01T12:23:45.678Z",
"updatedAt":"2022-01-01T12:23:45.678Z"
}
There are two things to note here. You didn’t have to configure or set up a new Class called GameScore
before running this code. Your Parse app lazily creates this Class for you when it first encounters it.
There are also a few fields you don’t need to specify that are provided as a convenience. objectId
is a unique identifier for each saved object. createdAt
and updatedAt
represent the time that each object was created and last modified in the cloud. Each of these fields is filled in by Parse, so they don’t exist on a ParseObject
until a save operation has completed.
Saving data to the cloud is fun, but it’s even more fun to get that data out again. If the ParseObject
has been uploaded to the server, you can retrieve it with its objectId
using a ParseQuery
:
$query = new ParseQuery("GameScore");
try {
$gameScore = $query->get("xWMyZ4YEGZ");
// The object was retrieved successfully.
} catch (ParseException $ex) {
// The object was not retrieved successfully.
// error is a ParseException with an error code and message.
}
To get the values out of the ParseObject
, use the get
method.
$score = $gameScore->get("score");
$playerName = $gameScore->get("playerName");
$cheatMode = $gameScore->get("cheatMode");
The four special values are provided as the result of methods:
$objectId = $gameScore->getObjectId();
$updatedAt = $gameScore->getUpdatedAt();
$createdAt = $gameScore->getCreatedAt();
$acl = $gameScore->getACL();
If you need to refresh an object you already have with the latest data that
is in the Parse Cloud, you can call the fetch
method like so:
$gameScore->fetch();
If you need to check if an object has been fetched, you can call the isDataAvailable()
method:
if (!$gameScore->isDataAvailable()) {
$gameScore->fetch();
}
Updating an object is simple. Just set some new data on it and call the save method. For example:
// Create the object.
$gameScore = new ParseObject("GameScore");
$gameScore->set("score", 1337);
$gameScore->set("playerName", "Sean Plott");
$gameScore->set("cheatMode", false);
$gameScore->setArray("skills", ["pwnage", "flying"]);
$gameScore->save();
// Now let's update it with some new data. In this case, only cheatMode and score
// will get sent to the cloud. playerName hasn't changed.
$gameScore->set("cheatMode", true);
$gameScore->set("score", 1338);
$gameScore->save();
Parse automatically figures out which data has changed so only “dirty” fields will be sent to the Parse Cloud. You don’t need to worry about squashing data that you didn’t intend to update.
The above example contains a common use case. The “score” field is a counter that we’ll need to continually update with the player’s latest score. Using the above method works but it’s cumbersome and can lead to problems if you have multiple clients trying to update the same counter.
To help with storing counter-type data, Parse provides methods that atomically increment (or decrement) any number field. So, the same update can be rewritten as:
$gameScore->increment("score");
$gameScore->save();
You can also increment by any amount by passing in a second argument to increment
. When no amount is specified, 1 is used by default.
To help with storing array data, there are three operations that can be used to atomically change an array associated with a given key:
add
append the given object to the end of an array field.addUnique
add the given object only if it isn’t already contained in an array field. The position of the insert is not guaranteed.remove
remove all instances of the given object from an array field.For example, we can add items to the set-like “skills” field like so:
$gameScore->addUnique("skills", ["flying"]);
$gameScore->addUnique("skills", ["kungfu"]);
$gameScore->save();
Note that it is not currently possible to atomically add and remove items from an array in the same save.
You will have to call save
in between every different kind of array operation.
Using version 1.3.0 or later of the php sdk gives you the ability to encode/decode instances of ParseObject
.
Encoding an object will give you a JSON encoded array that can be later decoded to get the original object back, unsaved changes included.
// create an object
$obj = new ParseObject("YourClass");
$obj->set('info', 'an encodable object');
// encode this object
$encoded = $obj->encode();
// save this encoded object somewhere for later use...
// decode to get our object as it was before,
// unsaved changes included
$decoded = ParseObject::decode($encoded);
An object that is encoded can easily be stored away, sent across the wire or even saved as a value under another ParseObject
.
This can be used to create a snapshot of an object at a point in time (unsaved changes included), allowing you to later go back, decode and inspect that object later on.
To delete an object from the cloud:
$gameScore->destroy();
You can delete a single field from an object with the delete
method:
// After this, the playerName field will be empty
$gameScore->delete("playerName");
// Saves the field deletion to the Parse Cloud
$gameScore->save();
Objects may have relationships with other objects. For example, in a blogging application, a Post
object may have many Comment
objects. Parse supports all kind of relationships, including one-to-one, one-to-many, and many-to-many.
One-to-one and one-to-many relationships are modeled by saving a ParseObject
as a value in the other object. For example, each Comment
in a blogging app might correspond to one Post
.
To create a new Post
with a single Comment
, you could write:
// Create the post
$myPost = new ParseObject("Post");
$myPost->set("title", "I'm Hungry");
$myPost->set("content", "Where should we go for lunch?");
// Create the comment
$myComment = new ParseObject("Comment");
$myComment->set("content", "Let's do Sushirrito.");
// Add the post as a value in the comment
$myComment->set("parent", $myPost);
// This will save both myPost and myComment
$myComment->save();
Internally, the Parse framework will store the referred-to object in just one place, to maintain consistency. You can also link objects using just their objectId
s like so:
$post = new ParseObject("Post", "1zEcyElZ80");
$myComment->set("parent", $post);
By default, when fetching an object, related ParseObject
s are not fetched. These objects’ values cannot be retrieved until they have been fetched like so:
$post = $fetchedComment->get("parent");
$post->fetch();
$title = $post->get("title");
Many-to-many relationships are modeled using ParseRelation
. This works similar to storing an array of ParseObject
s in a key, except that you don’t need to fetch all of the objects in a relation at once. In addition, this allows ParseRelation
to scale to many more objects than the array of ParseObject
approach. For example, a User
may have many Posts
that she might like. In this case, you can store the set of Posts
that a User
likes using relation
. In order to add a Post
to the “likes” array of the User
, you can do:
$user = ParseUser::getCurrentUser();
$relation = $user->getRelation("likes");
$relation->add($post);
$user->save();
You can remove a post from a ParseRelation
:
$relation->remove($post);
$user->save();
You can call add
and remove
multiple times before calling save:
$relation->remove($post1);
$relation->remove($post2);
$user->save();
You can also pass in an array of ParseObject
to add
and remove
:
$relation->add([$post1, $post2, $post3]);
$user->save();
By default, the array of objects in this relation are not downloaded. You can get an array of the posts that a user likes by using the ParseQuery
returned by getQuery
. The code looks like:
$postsLiked = $relation->getQuery()->find();
// $postsLiked contains the posts that the current user likes.
If you want only a subset of the Posts, you can add extra constraints to the ParseQuery
returned by query like this:
$query = $relation->getQuery();
$query->equalTo("title", "I'm Hungry");
$postsLiked = $query->find();
// $postsLiked contains post liked by the current user which have the title "I'm Hungry".
For more details on ParseQuery
, please look at the query portion of this guide. A ParseRelation
behaves similar to an array of ParseObject
for querying purposes, so any query you can do on an array of objects, you can do on a ParseRelation
.
So far we’ve used values with type String
, Integer
, and ParseObject
. Parse also supports PHP DateTime
s and null
. You can nest PHP arrays and associative arrays (JSON Objects) to store more structured data within a single ParseObject
. Overall, the following types are allowed for each field in your object:
String
Integer
and float
Boolean
DateTime
ParseFile
ParseObject
ParseRelation
ParseGeoPoint
null
Some examples:
$number = 42;
$string = "the number is " . $number;
$date = new DateTime();
$array = [$string, $number];
$object = ["number" => $number, "string" => $string];
$geoPoint = new ParseGeoPoint(37.75, -122.68); // san fran
$bigObject = new ParseObject("BigObject");
$bigObject->set("myNumber", $number);
$bigObject->set("myString", $string);
$bigObject->set("myDate", $date);
$bigObject->setArray("myArray", $array);
$bigObject->setAssociativeArray("myObject", $object);
$bigObject->set("myGeoPoint", $geoPoint);
$bigObject->set("anyKey", null); // this value can only be saved to an existing key
$bigObject->save();
We do not recommend storing large pieces of binary data like images or documents on ParseObject
. We recommend you use ParseFile
s to store images, documents, and other types of files. You can do so by instantiating a ParseFile
object and setting it on a field. See Files for more details.
For more information about how Parse handles data, check out our documentation on Data.
Each ParseObject
is an instance of a specific subclass with a class name that you can use to distinguish different sorts of data. For example, we could call the high score object a GameScore
. We recommend that you NameYourClassesLikeThis and nameYourKeysLikeThis, just to keep your code looking pretty.
To create a new subclass, create a new class which extends the ParseObject
class, add the $parseClassName
static property, and call the registerSubclass
method before use. Any ParseQuery
will return instances of the new class for any ParseObject
with the same class name.
class GameScore extends ParseObject
{
public static $parseClassName = "GameScore";
}
// Do this once, at the start of your app, before ParseClient::initialize(...);
GameScore::registerSubclass();
// Create a new instance of that class.
$gameScore = new GameScore();
You can add additional methods and properties to your subclasses of ParseObject
.
// A complex subclass of ParseObject
class Monster extends ParseObject
{
public static $parseClassName = "Monster";
public function hasSuperHumanStrength() {
return $this->get("strength") > 18;
}
public static function spawn($strength) {
$monster = new Monster();
$monster->set("strength", $strength);
return $monster;
}
}
$monster = Monster::spawn(200);
echo monster->strength(); // Displays 200.
echo monster->hasSuperHumanStrength(); // Displays true.
If you want to override the __construct method, make sure the first three params are exactly as same as the parent ParseObject constructor:
class GameScore extends ParseObject
{
public static $parseClassName = "GameScore";
public function __construct($className = null, $objectId = null, $isPointer = false, $another_param) {
parent::__construct("GameScore", $objectId, $isPointer);
// ...
}
}
We’ve already seen how a ParseQuery
with get
can retrieve a single ParseObject
from Parse. There are many other ways to retrieve data with ParseQuery
- you can retrieve many objects at once, put conditions on the objects you wish to retrieve, and more.
In many cases, get
isn’t powerful enough to specify which objects you want to retrieve. ParseQuery
offers different ways to retrieve an array of objects rather than just a single object.
The general pattern is to create a ParseQuery
, put conditions on it, and then retrieve an Array
of matching ParseObject
s using find
. For example, to retrieve of the scores with a particular playerName
, use the equalTo
method to constrain the value for a key.
$query = new ParseQuery("GameScore");
$query->equalTo("playerName", "Dan Stemkoski");
$results = $query->find();
echo "Successfully retrieved " . count($results) . " scores.";
// Do something with the returned ParseObject values
for ($i = 0; $i < count($results); $i++) {
$object = $results[$i];
echo $object->getObjectId() . ' - ' . $object->get('playerName');
}
There are several ways to put constraints on the objects found by a ParseQuery
. You can filter out objects with a particular key-value pair with notEqualTo
:
$query->notEqualTo("playerName", "Michael Yabuti");
You can give multiple constraints, and objects will only be in the results if they match all of the constraints. In other words, it’s like an AND of constraints.
$query->notEqualTo("playerName", "Michael Yabuti");
$query->greaterThan("playerAge", 18);
You can limit the number of results by setting limit
. By default, results are limited to 100. In the old Parse hosted backend, the maximum limit was 1,000, but Parse Server removed that constraint:
$query->limit(10); // limit to at most 10 results
If you want exactly one result, a more convenient alternative may be to use first
instead of using find
.
$query = new ParseQuery("GameScore");
$query->equalTo("playerEmail", "[email protected]");
$object = $query->first();
You can skip the first results by setting skip
. In the old Parse hosted backend, the maximum skip value was 10,000, but Parse Server removed that constraint. This can be useful for pagination:
$query->skip(10); // skip the first 10 results
If you want to know the total number of rows in a table satisfying your query, for e.g. pagination purposes - you can use withCount
.
Note: Using this feature will change the structure of response, see the example below.
Let’s say you have 200 rows in a table called GameScore
:
$query = new ParseQuery('GameScore');
$query->withCount();
$query->limit(25);
$response = $query->find();
$response['count'] // Returns 200 the total number of objects dispite limit / skip
$response['results'] // Returns 25 objects
// As of PHP 7.1 you can use Array Destructuring
['count' => $count, 'results' => $results] = $query->find();
// Use $count and $results
⚠️ Сount operations can be slow and expensive.
If you only want to get the count without objects - use Counting Objects.
For sortable types like numbers and strings, you can control the order in which results are returned:
// Sorts the results in ascending order by the score field
$query->ascending("score");
// Sorts the results in descending order by the score field
$query->descending("score");
For sortable types, you can also use comparisons in queries:
// Restricts to wins < 50
$query->lessThan("wins", 50);
// Restricts to wins <= 50
$query->lessThanOrEqualTo("wins", 50);
// Restricts to wins > 50
$query->greaterThan("wins", 50);
// Restricts to wins >= 50
$query->greaterThanOrEqualTo("wins", 50);
For sorting by ParseGeoPoint
you can query for whether an object lies within a polygon of geo points:
// construct 3 geo points
$geoPoint1 = new ParseGeoPoint();
$geoPoint2 = new ParseGeoPoint();
$geoPoint3 = new ParseGeoPoint();
// restrict to any objects where `myGeoPoint` lies within a polygon made by 3 or more geo points
$query->withinPolygon("myGeoPoint", [$geoPoint1, $geoPoint2, $geoPoint3]);
If you want to retrieve objects matching several different values, you can use containedIn
, providing an array of acceptable values. This is often useful to replace multiple queries with a single query. For example, if you want to retrieve scores made by any player in a particular array:
// Finds scores from any of Jonathan, Dario, or Shawn
$query->containedIn("playerName",
["Jonathan Walsh", "Dario Wunsch", "Shawn Simon"]);
If you want to retrieve objects that do not match any of several values you can use notContainedIn
, providing an array of acceptable values. For example if you want to retrieve scores from players besides those in an array:
// Finds scores from anyone who is neither Jonathan, Dario, nor Shawn
$query->notContainedIn("playerName",
["Jonathan Walsh", "Dario Wunsch", "Shawn Simon"]);
If you want to retrieve objects that have a particular key set, you can use exists
. Conversely, if you want to retrieve objects without a particular key set, you can use doesNotExist
.
// Finds objects that have the score set
$query->exists("score");
// Finds objects that don't have the score set
$query->doesNotExist("score");
You can use the matchesKeyInQuery
method to get objects where a key matches the value of a key in a set of objects resulting from another query. For example, if you have a class containing sports teams and you store a user’s hometown in the user class, you can issue one query to find the array of users whose hometown teams have winning records. The query would look like:
$teamQuery = new ParseQuery("Team");
$teamQuery->greaterThan("winPct", 0.5);
$userQuery = ParseUser::query();
$userQuery->matchesKeyInQuery("hometown", "city", $teamQuery);
$results = $userQuery->find();
// results has the array of users with a hometown team with a winning record
Conversely, to get objects where a key does not match the value of a key in a set of objects resulting from another query, use doesNotMatchKeyInQuery
. For example, to find users whose hometown teams have losing records:
$losingUserQuery = ParseUser::query();
$losingUserQuery->doesNotMatchKeyInQuery("hometown", "city", teamQuery);
$results = $losingUserQuery->find();
// results has the array of users with a hometown team with a losing record
To filter rows based on objectId
’s from pointers in a second table, you can use dot notation:
$rolesOfTypeX = new ParseQuery('Role');
$rolesOfTypeX->equalTo('type', 'x');
$groupsWithRoleX = new ParseQuery('Group');
$groupsWithRoleX->matchesKeyInQuery('objectId', 'belongsTo.objectId', rolesOfTypeX);
$results = $groupsWithRoleX->find();
// results has the list of groups with role x
You can restrict the fields returned by calling select
with an array of keys. To retrieve documents that contain only the score
and playerName
fields (and also special built-in fields such as objectId
, createdAt
, and updatedAt
):
$query = new ParseQuery("GameScore");
$query->select(["score", "playerName"]);
$results = $query->find();
// each of results will only have the selected fields available.
The remaining fields can be fetched later by calling fetch
on the returned objects:
$result = $query->first();
// only the selected fields of the object will now be available here.
$result->fetch();
// all fields of the object will now be available here.
Queries can be made using aggregates, allowing you to retrieve objects over a set of input values. Note that the MasterKey is Required.
For a list of available operators please refer to Mongo Aggregate Documentation.
// group pipeline is similar to distinct, can apply $sum, $avg, $max, $min
// accumulate sum and store in total field
$pipeline = [
'group' => [
'objectId' => null,
'total' => [ '$sum' => '$score']
]
];
$results = $query->aggregate($pipeline);
// project pipeline is similar to keys, add or remove existing fields
// includes name key
$pipeline = [
'project' => [
'name' => 1
]
];
$results = $query->aggregate($pipeline);
// match pipeline is similar to equalTo
// filter out objects with score greater than 15
$pipeline = [
'match' => [
'score' => [ '$gt' => 15 ]
]
];
$results = $query->aggregate($pipeline);
Queries can be made using distinct, allowing you find unique values for a specified field. Keep in mind that the MasterKey is required.
// finds score that are unique
$results = $query->distinct('score');
// can be used with equalTo
$query = new ParseQuery('TestObject');
$query->equalTo('name', 'foo');
$results = $query->distinct('score');
Queries can be made using relative time, allowing you to retrieve objects over a varying ranges of relative dates. Keep in mind that all relative queries are performed using the server’s time and timezone.
// greater than 2 weeks ago
$query->greaterThanRelativeTime('createdAt', '2 weeks ago');
// less than 1 day in the future
$query->lessThanRelativeTime('updatedAt', 'in 1 day');
// can make queries to very specific points in time
$query->greaterThanOrEqualToRelativeTime('createdAt', '1 year 2 weeks 30 days 2 hours 5 minutes 10 seconds ago');
// can make queries based on right now
// gets everything updated up to this point in time
$query->lessThanOrEqualToRelativeTime('updatedAt', 'now');
// shorthand keywords work as well
$query->greaterThanRelativeTime('date', '1 yr 2 wks 30 d 2 hrs 5 mins 10 secs ago');
For keys with an array type, you can find objects where the key’s array value contains 2 by:
// Find objects where the array in arrayKey contains 2.
$query->equalTo("arrayKey", 2);
You can also find objects where the key’s array value contains each of the elements 2, 3, and 4 with the following:
// Find objects where the array in arrayKey contains all of the elements 2, 3, and 4.
$query->containsAll("arrayKey", [2, 3, 4]);
Use startsWith
to restrict to string values that start with a particular string. Similar to a MySQL LIKE operator, this is indexed so it is efficient for large datasets:
// Finds barbecue sauces that start with "Big Daddy's".
$query = new ParseQuery("BarbecueSauce");
$query->startsWith("name", "Big Daddy's");
The above example will match any BarbecueSauce
objects where the value in the “name” String key starts with “Big Daddy’s”. For example, both “Big Daddy’s” and “Big Daddy’s BBQ” will match, but “big daddy’s” or “BBQ Sauce: Big Daddy’s” will not.
You may also use endsWith
to restrict to string values that end with a particular string instead:
// Finds barbecue sauces that end with "Sauce".
$query = new ParseQuery("BarbecueSauce");
$query->endsWith("name", "Sauce");
The above example will match any BarbecueSauce
objects where the value in the “name” String key ends with “Sauce”. For example, both “Big Daddy’s BBQ Sauce” and “Home Style BBQ Sauce” will match, but “Big Daddy’s BBQ” or “Best Sauce: Big Daddy’s” will not.
Queries that have regular expression constraints are very expensive, especially for classes with over 100,000 records. Parse restricts how many such operations can be run on a particular app at any given time.
There are several ways to issue queries for relational data. If you want to retrieve objects where a field matches a particular ParseObject
, you can use equalTo
just like for other data types. For example, if each Comment
has a Post
object in its post
field, you can fetch comments for a particular Post
:
// Assume ParseObject $myPost was previously created.
$query = new ParseQuery("Comment");
$query->equalTo("post", $myPost);
$comments = $query->find();
// comments now contains the comments for myPost
If you want to retrieve objects where a field contains a ParseObject
that matches a different query, you can use matchesQuery
. In order to find comments for posts containing images, you can do:
$innerQuery = new ParseQuery("Post");
$innerQuery->exists("image");
$query = new ParseQuery("Comment");
$query->matchesQuery("post", $innerQuery);
$comments = $query->find();
// comments now contains the comments for posts with images.
If you want to retrieve objects where a field contains a ParseObject
that does not match a different query, you can use doesNotMatchQuery
. In order to find comments for posts without images, you can do:
$innerQuery = new ParseQuery("Post");
$innerQuery->exists("image");
$query = new ParseQuery("Comment");
$query->doesNotMatchQuery("post", $innerQuery);
$query->find();
// comments now contains the comments for posts without images.
You can also do relational queries by objectId
:
$post = new ParseObject("Post", "1zEcyElZ80");
$query->equalTo("post", $post);
In some situations, you want to return multiple types of related objects in one query. You can do this with the include
method. For example, let’s say you are retrieving the last ten comments, and you want to retrieve their related posts at the same time:
$query = new ParseQuery("Comment");
// Retrieve the most recent ones
query->descending("createdAt");
// Only retrieve the last ten
query->limit(10);
// Include the post data with each comment
query->includeKey("post");
$comments = query->find();
// Comments now contains the last ten comments, and the "post" field
// has been populated. For example:
for ($i = 0; $i < count($comments); $i++) {
// This does not require a network access.
$post = $comments[$i]->get("post");
}
You can also do multi level includes using dot notation. If you wanted to include the post for a comment and the post’s author as well you can do:
$query->includeKey("post.author");
You can issue a query with multiple fields included by calling includeKey
multiple times. This functionality also works with ParseQuery helpers like first
and get
.
Note: In the old Parse hosted backend, count queries were rate limited to a maximum of 160 requests per minute. They also returned inaccurate results for classes with more than 1,000 objects. But, Parse Server has removed both constraints and can count objects well above 1,000.
If you just need to count how many objects match a query, but you do not need to retrieve all the objects that match, you can use count
instead of find
. For example, to count how many games have been played by a particular player:
$query = new ParseQuery("GameScore");
$query->equalTo("playerName", "Sean Plott");
$count = $query->count();
// The count request succeeded. Show the count
echo "Sean has played " . $count . " games";
If you want to find objects that match one of several queries, you can use ParseQuery.or
method to construct a query that is an OR of the queries passed in. For instance if you want to find players who either have a lot of wins or a few wins, you can do:
$lotsOfWins = new ParseQuery("Player");
$lotsOfWins->greaterThan("wins", 150);
$fewWins = new ParseQuery("Player");
$fewWins->lessThan("wins", 5);
$mainQuery = ParseQuery::orQueries([$lotsOfWins, $fewWins]);
$results = $mainQuery->find();
// results contains an array of players that either have won a lot of games or won only a few games.
You can add additional constraints to the newly created ParseQuery
that act as an ‘and’ operator.
Note that we do not, however, support GeoPoint or non-filtering constraints (e.g. near
, withinGeoBox
, limit
, skip
, ascending
/descending
, includeKey
) in the subqueries of the compound query.
Each ParseObject
is an instance of a specific subclass with a class name that you can use to distinguish different sorts of data. For example, we could call the high score object a GameScore
. We recommend that you NameYourClassesLikeThis and nameYourKeysLikeThis, just to keep your code looking pretty.
To create a new subclass, create a new class which extends the ParseObject
class, add the $parseClassName
static property, and call the registerSubclass
method before use. Any ParseQuery
will return instances of the new class for any ParseObject
with the same class name.
class GameScore extends ParseObject
{
public static $parseClassName = "GameScore";
}
// Do this once, at the start of your app, before ParseClient::initialize(...);
GameScore::registerSubclass();
// Create a new instance of that class.
$gameScore = new GameScore();
You can add additional methods and properties to your subclasses of ParseObject
.
// A complex subclass of ParseObject
class Monster extends ParseObject
{
public static $parseClassName = "Monster";
public function hasSuperHumanStrength() {
return this->get("strength") > 18;
}
public static function spawn($strength) {
$monster = new Monster();
$monster->set("strength", $strength);
return $monster;
}
}
$monster = Monster::spawn(200);
echo monster->strength(); // Displays 200.
echo monster->hasSuperHumanStrength(); // Displays true.
When using a MongoDB replica set, you can use the $query->readPreference(readPreference, includeReadPreference, subqueryReadPreference)
function to choose from which replica the objects will be retrieved. The includeReadPreference
argument chooses from which replica the included pointers will be retrieved and the subqueryReadPreference
argument chooses in which replica the subqueries will run. The possible values are PRIMARY
(default), PRIMARY_PREFERRED
, SECONDARY
, SECONDARY_PREFERRED
, or NEAREST
. If the includeReadPreference
argument is not passed, the same replica chosen for readPreference
will be also used for the includes. The same rule applies for the subqueryReadPreference
argument.
$query->readPreference(
"SECONDARY",
"SECONDARY_PREFERRED",
"NEAREST"
);
At the core of many apps, there is a notion of user accounts that lets users access their information in a secure manner. We provide a specialized user class called ParseUser
that automatically handles much of the functionality required for user account management.
With this class, you’ll be able to add user account functionality in your app.
ParseUser
is a subclass of ParseObject
, and has all the same features, such as flexible schema, automatic persistence, and a key value interface. All the methods that are on ParseObject
also exist in ParseUser
. The difference is that ParseUser has some special additions specific to user accounts.
ParseUser
PropertiesParseUser
has several values that set it apart from ParseObject
:
We’ll go through each of these in detail as we run through the various use cases for users.
The first thing your app will do is probably ask the user to sign up. The following code illustrates a typical sign up:
$user = new ParseUser();
$user->set("username", "my name");
$user->set("password", "my pass");
$user->set("email", "[email protected]");
// other fields can be set just like with ParseObject
$user->set("phone", "415-392-0202");
try {
$user->signUp();
// Hooray! Let them use the app now.
} catch (ParseException $ex) {
// Show the error message somewhere and let the user try again.
echo "Error: " . $ex->getCode() . " " . $ex->getMessage();
}
This call will asynchronously create a new user in your Parse App. Before it does this, it also checks to make sure that both the username and email are unique. Also, it securely hashes the password in the cloud using bcrypt. We never store passwords in plaintext, nor will we ever transmit passwords back to the client in plaintext.
Note that we used the signUp
method, not the save
method. New ParseUser
s should always be created using the signUp
method. Subsequent updates to a user can be done by calling save
.
If a signup isn’t successful, you should read the error object that is returned. The most likely case is that the username or email has already been taken by another user. You should clearly communicate this to your users, and ask them try a different username.
You are free to use an email address as the username. Simply ask your users to enter their email, but fill it in the username property — ParseUser
will work as normal. We’ll go over how this is handled in the reset password section.
Of course, after you allow users to sign up, you need to let them log in to their account in the future. To do this, you can use the class method logIn
.
try {
$user = ParseUser::logIn("myname", "mypass");
// Do stuff after successful login.
} catch (ParseException $error) {
// The login failed. Check error to see why.
}
Enabling email verification in an application’s settings allows the application to reserve part of its experience for users with confirmed email addresses. Email verification adds the emailVerified
key to the ParseUser
object. When a ParseUser
’s email
is set or modified, emailVerified
is set to false
. Parse then emails the user a link which will set emailVerified
to true
.
There are three emailVerified
states to consider:
true
- the user confirmed his or her email address by clicking on the link Parse emailed them. ParseUsers
can never have a true
value when the user account is first created.false
- at the time the ParseUser
object was last refreshed, the user had not confirmed his or her email address. If emailVerified
is false
, consider calling fetch
on the ParseUser
.ParseUser
was created when email verification was off or the ParseUser
does not have an email
.When email verification is enabled verification emails are automatically sent to a user’s email address upon them signing up.
Although this usually is good enough on it’s own you may run into some issues, such as if the verification email was lost, expired due to exceeding token validity or the user simple wants another request emailed to them for their own reasons.
In any of those cases all you would need to do is call ParseUser::requestVerificationEmail
with the email in question to have a verification email resent to them.
ParseUser::requestVerificationEmail('[email protected]');
Note that this will only send if the account for the email requested has not already been verified.
It would be bothersome if the user had to log in every time they open your app. You can avoid this by using the cached current ParseUser
object.
By default, whenever you use any signup or login methods, the user will be saved in PHP Session storage, the $_SESSION
superglobal. When re-initializing the Parse PHP SDK, you may need to instruct PHP to load the stored session data into the $_SESSION
superglobal using session_start()
before initializing the Parse PHP SDK to make Parse use persistent storage and continue the previous user session.
$currentUser = ParseUser::getCurrentUser();
if ($currentUser) {
// do stuff with the user
} else {
// show the signup or login page
}
You can clear the current user by logging them out:
ParseUser::logOut();
$currentUser = ParseUser::getCurrentUser(); // this will now be null
If you’ve created your own authentication routines, or otherwise logged in a user on the server side, you can now pass the session token to the client and use the become
method. This method will ensure the session token is valid before setting the current user.
try {
$user = ParseUser::become("session-token-here");
// The current user is now set to user.
} catch (ParseException $ex) {
// The token could not be validated.
}
If you do not want to use the default PHP $_SESSION
for storage, you can add your own storage mechanism. We provide the ParseSessionStorageInterface
and a default implementation, ParseSessionStorage
. Simply create your own storage class that implements ParseSessionStorageInterface
and inject it to the SDK when you initialize:
ParseClient::setStorage(new MyStorageClass());
The ParseUser
class is secured by default. Data stored in a ParseUser
can only be modified by that user. By default, the data can still be read by any client. Thus, some ParseUser
objects are authenticated and can be modified, whereas others are read-only.
Specifically, you are not able to invoke any of the save
or delete
methods unless the ParseUser
was obtained using an authenticated method, like logIn
or signUp
. This ensures that only the user can alter their own data.
The following illustrates this security policy:
$user = ParseUser::logIn("my_username", "my_password");
$user->set("username", "my_new_username"); // attempt to change username
$user->save();
// This succeeds, since the user was authenticated on the device
// Get the user from a non-authenticated method
$query = ParseUser::query();
$userAgain = $query->get($user->getObjectId());
$userAgain->set("username", "another_username");
// This will throw a ParseException, since the ParseUser is not authenticated
$userAgain->save();
The ParseUser
obtained from ParseUser::getCurrentUser()
will always be authenticated.
If you need to check if a ParseUser
is authenticated, you can invoke the authenticated
method. You do not need to check authenticated
with ParseUser
objects that are obtained via an authenticated method.
The same security model that applies to the ParseUser
can be applied to other objects. For any object, you can specify which users are allowed to read the object, and which users are allowed to modify an object. To support this type of security, each object has an access control list, implemented by the ParseACL
class.
The simplest way to use a ParseACL
is to specify that an object may only be read or written by a single user. To create such an object, there must first be a logged in ParseUser
. Then, new ParseACL::createACLWithUser($user)
generates a ParseACL
that limits access to that user. An object’s ACL is updated when the object is saved, like any other property. Thus, to create a private note that can only be accessed by the current user:
$privateNote = new ParseObject("Note");
$privateNote->set("content", "This note is private!");
$privateNote->setACL(ParseACL::createACLWithUser(ParseUser::getCurrentUser()));
$privateNote->save();
This note will then only be accessible to the current user, although it will be accessible to any device where that user is signed in. This functionality is useful for applications where you want to enable access to user data across multiple devices, like a personal todo list.
Permissions can also be granted on a per-user basis. You can add permissions individually to a ParseACL
using setReadAccess
and setWriteAccess
. For example, let’s say you have a message that will be sent to a group of several users, where each of them have the rights to read and delete that message:
$groupMessage = new ParseObject("Message");
$groupACL = new ParseACL();
// userList is an array with the users we are sending this message to.
for ($i = 0; $i < count($userList); $i++) {
$groupACL->setReadAccess($userList[$i], true);
$groupACL->setWriteAccess($userList[$i], true);
}
$groupMessage->setACL($groupACL);
$groupMessage->save();
You can also grant permissions to all users at once using setPublicReadAccess
and setPublicWriteAccess
. This allows patterns like posting comments on a message board. For example, to create a post that can only be edited by its author, but can be read by anyone:
$publicPost = new ParseObject("Post");
$postACL = ParseACL::createACLWithUser(ParseUser::getCurrentUser());
$postACL->setPublicReadAccess(true);
$publicPost->setACL($postACL);
$publicPost->save();
Operations that are forbidden, such as deleting an object that you do not have write access to, result in a ParseError.OBJECT_NOT_FOUND
error code. For security purposes, this prevents clients from distinguishing which object ids exist but are secured, versus which object ids do not exist at all.
It’s a fact that as soon as you introduce passwords into a system, users will forget them. In such cases, our library provides a way to let them securely reset their password.
To kick off the password reset flow, ask the user for their email address, and call:
try {
ParseUser::requestPasswordReset("[email protected]");
// Password reset request was sent successfully
} catch (ParseException $ex) {
// Password reset failed, check the exception message
}
This will attempt to match the given email with the user’s email or username field, and will send them a password reset email. By doing this, you can opt to have users use their email as their username, or you can collect it separately and store it in the email field.
The flow for password reset is as follows:
Note that the messaging in this flow will reference your app by the name that you specified when you created this app on Parse.
To query for users, you can get a new ParseQuery
for ParseUser
s:
$query = ParseUser::query();
$query->equalTo("gender", "female");
$results = $query->find();
Associations involving a ParseUser
work right out of the box. For example, let’s say you’re making a blogging app. To store a new post for a user and retrieve all of their posts:
$user = ParseUser::getCurrentUser()
// Make a new post
$post = new ParseObject("Post");
$post->set("title", "My New Post");
$post->set("body", "This is some great content.");
$post->set("user", $user);
$post->save();
// Find all posts by the current user
$query = new ParseQuery("Post");
$query->equalTo("user", $user);
$userPosts = $query->find();
// $userPosts contains all of the posts by the current user.
Sessions represent an instance of a user logged into a device. Sessions are automatically created when users log in or sign up. They are automatically deleted when users log out. There is one distinct Session
object for each user-installation pair; if a user issues a login request from a device they’re already logged into, that user’s previous Session
object for that Installation is automatically deleted. Session
objects are stored on Parse in the Session class, and you can view them on the Parse Dashboard Data Browser. We provide a set of APIs to manage Session
objects in your app.
Session
is a subclass of a Parse Object
, so you can query, update, and delete sessions in the same way that you manipulate normal objects on Parse. Because Parse Server automatically creates sessions when you log in or sign up users, you should not manually create Session
objects unless you are building an IoT app (e.g. Arduino or Embedded C). Deleting a Session
will log the user out of the device that is currently using this session’s token.
Unlike other Parse objects, the Session
class does not have Cloud Code triggers. So you cannot register a beforeSave
or afterSave
handler for the Session class.
Session
PropertiesThe Session
object has these special fields:
sessionToken
(readonly): String token for authentication on Parse API requests. In the response of Session
queries, only your current Session
object will contain a session token.user
: (readonly) Pointer to the User
object that this session is for.createdWith
(readonly): Information about how this session was created (e.g. { "action": "login", "authProvider": "password"}
).
action
could have values: login
, signup
, create
, or upgrade
. The create
action is when the developer manually creates the session by saving a Session
object. The upgrade
action is when the user is upgraded to revocable session from a legacy session token.authProvider
could have values: password
, anonymous
, facebook
, or twitter
.expiresAt
(readonly): Approximate UTC date when this Session
object will be automatically deleted. You can configure session expiration settings (either 1-year inactivity expiration or no expiration) in your app’s Parse Dashboard settings page.installationId
(can be set only once): String referring to the Installation
where the session is logged in from. For Parse SDKs, this field will be automatically set when users log in or sign up.
All special fields except installationId
can only be set automatically by Parse Server. You can add custom fields onto Session
objects, but please keep in mind that any logged-in device (with session token) can read other sessions that belong to the same user (unless you disable Class-Level Permissions, see below).With revocable sessions, your current session token could become invalid if its corresponding Session
object is deleted from your Parse Server. This could happen if you implement a Session Manager UI that lets users log out of other devices, or if you manually delete the session via Cloud Code, REST API, or Data Browser. Sessions could also be deleted due to automatic expiration (if configured in app settings). When a device’s session token no longer corresponds to a Session
object on your Parse Server, all API requests from that device will fail with “Error 209: invalid session token”.
To handle this error, we recommend writing a global utility function that is called by all of your Parse request error callbacks. You can then handle the “invalid session token” error in this global function. You should prompt the user to login again so that they can obtain a new session token. This code could look like this:
public class ParseErrorHandler {
public static handleParseError(ParseException $e) {
$code = $e->getCode();
switch ($code) {
case: 209: // INVALID_SESSION_TOKEN
ParseUser::logOut();
... // Redirect the to login page.
break;
... // Other Parse API errors that you want to explicitly handle
}
}
});
// For each API request, call the global error handler
try {
$results = $query->find();
// ...
} catch (ParseException $e) {
ParseErrorHandler::handleParseError($e)
}
Session
SecuritySession
objects can only be accessed by the user specified in the user field. All Session
objects have an ACL that is read and write by that user only. You cannot change this ACL. This means querying for sessions will only return objects that match the current logged-in user.
When you log in a user via a User
login method, Parse will automatically create a new unrestricted Session
object in your Parse Server. Same for signups and Facebook/Twitter logins.
You can configure Class-Level Permissions (CLPs) for the Session class just like other classes on Parse. CLPs restrict reading/writing of sessions via the Session
API, but do not restrict Parse Server’s automatic session creation/deletion when users log in, sign up, and log out. We recommend that you disable all CLPs not needed by your app. Here are some common use cases for Session CLPs:
As your app grows in scope and user-base, you may find yourself needing more coarse-grained control over access to pieces of your data than user-linked ACLs can provide. To address this requirement, Parse supports a form of Role-based Access Control. Roles provide a logical way of grouping users with common access privileges to your Parse data. Roles are named objects that contain users and other roles. Any permission granted to a role is implicitly granted to its users as well as to the users of any roles that it contains.
For example, in your application with curated content, you may have a number of users that are considered “Moderators” and can modify and delete content created by other users. You may also have a set of users that are “Administrators” and are allowed all of the same privileges as Moderators, but can also modify the global settings for the application. By adding users to these roles, you can ensure that new users can be made moderators or administrators, without having to manually grant permission to every resource for each user.
We provide a specialized class called ParseRole
that represents these role objects in your client code. ParseRole
is a subclass of ParseObject
, and has all of the same features, such as a flexibl schema, automatic persistence, and a key value interface. All the methods that are on ParseObject
also exist on ParseRole
. The difference is that ParseRole
has some additions specific to management of roles.
ParseRole
PropertiesParseRole
has several properties that set it apart from ParseObject
:
The ParseRole
uses the same security scheme (ACLs) as all other objects on Parse, except that it requires an ACL to be set explicitly. Generally, only users with greatly elevated privileges (e.g. a master user or Administrator) should be able to create or modify a Role, so you should define its ACLs accordingly. Remember, if you give write-access to a ParseRole
to a user, that user can add other users to the role, or even delete the role altogether.
To create a new ParseRole
, you would write:
// By specifying no write privileges for the ACL, we can ensure the role cannot be altered.
$roleACL = new ParseACL();
$roleACL->setPublicReadAccess(true);
$role = ParseRole::createRole("Administrator", $roleACL);
$role->save();
You can add users and roles that should inherit your new role’s permissions through the “users” and “roles” relations on ParseRole
:
$role = ParseRole::createRole($roleName, $roleACL);
for ($i = 0; $i < count($usersToAddToRole); $i++) {
$role->getUsers()->add($usersToAddToRole[$i]);
}
for ($i = 0; $i < count($rolesToAddToRole); $i++) {
$role->getRoles()->add($rolesToAddToRole[$i]);
}
$role->save();
Take great care when assigning ACLs to your roles so that they can only be modified by those who should have permissions to modify them.
Now that you have created a set of roles for use in your application, you can use them with ACLs to define the privileges that their users will receive. Each ParseObject
can specify a ParseACL
, which provides an access control list that indicates which users and roles should be granted read or write access to the object.
Giving a role read or write permission to an object is straightforward. You can either use the ParseRole
:
$moderators = /* Query for some ParseRole */;
$wallPost = new ParseObject("WallPost");
$postACL = new ParseACL();
$postACL->setRoleWriteAccess($moderators, true);
$wallPost->setACL($postACL);
$wallPost->save();
You can avoid querying for a role by specifying its name for the ACL:
$wallPost = new ParseObject("WallPost");
$postACL = new ParseACL();
$postACL->setRoleWriteAccessWithName("Moderators", true);
$wallPost->setACL($postACL);
$wallPost->save();
As described above, one role can contain another, establishing a parent-child relationship between the two roles. The consequence of this relationship is that any permission granted to the parent role is implicitly granted to all of its child roles.
These types of relationships are commonly found in applications with user-managed content, such as forums. Some small subset of users are “Administrators”, with the highest level of access to tweaking the application’s settings, creating new forums, setting global messages, and so on. Another set of users are “Moderators”, who are responsible for ensuring that the content created by users remains appropriate. Any user with Administrator privileges should also be granted the permissions of any Moderator. To establish this relationship, you would make your “Administrators” role a child role of “Moderators”, like this:
$administrators = /* Your "Administrators" role */;
$moderators = /* Your "Moderators" role */;
$moderators->getRoles()->add($administrators);
$moderators->save();
ParseFile
lets you store application files in the cloud that would otherwise be too large or cumbersome to fit into a regular ParseObject
. The most common use case is storing images, but you can also use it for documents, videos, music, and any other binary data.
Getting started with ParseFile
is easy. There are a couple of ways to create a file. The first is to provide the contents of the file.
$contents = "Hello World.";
$file = ParseFile::createFromData($contents, "myfile.txt");
Alternatively, you can create a file from the contents of a local file:
$localFilePath = "/tmp/myFile.txt";
$file = ParseFile::createFromFile($localFilePath, "myfile.txt");
Notice in this example that we give the file a name of myfile.txt
. There’s two things to note here:
photo.jpg
..png
.Next you’ll want to save the file up to the cloud. As with ParseObject
, the save
method is the way to go.
$file->save();
// The file has been saved to Parse and now has a URL.
$url = $file->getURL();
Finally, after the save completes, you can associate a ParseFile
with a ParseObject
just like any other piece of data:
$jobApplication = new ParseObject("JobApplication");
$jobApplication->set("applicantName", "Joe Smith");
$jobApplication->set("applicantResumeFile", $file);
$jobApplication->save();
How to best retrieve the file contents back depends on the context of your application. It’s best if you can make the visitors browser do the work for you. Typically, that means rendering the file’s URL into your output. Here we output an uploaded profile photo in an image tag:
$profilePhoto = $profile->get("photoFile");
echo '<img src="' . $profilePhoto->getURL() . '">';
If you want to fetch the contents of the file, you can retrieve it like this:
$contents = $file->getData();
Parse allows you to associate real-world latitude and longitude coordinates with an object. Adding a ParseGeoPoint
to a ParseObject
allows queries to take into account the proximity of an object to a reference point. This allows you to easily do things like find out what user is closest to another user or which places are closest to a user.
To associate a point with an object you first need to create a ParseGeoPoint
. For example, to create a point with latitude of 40.0 degrees and -30.0 degrees longitude:
$point = new ParseGeoPoint(40.0, -30.0);
This point is then stored in the object as a regular field.
$placeObject->set("location", $point);
Note: Currently only one key in a class may be a ParseGeoPoint
.
Now that you have a bunch of objects with spatial coordinates, it would be nice to find out which objects are closest to a point. This can be done by adding another restriction to ParseQuery
using near
. Getting an array of ten places that are closest to a user may look something like:
// User's location
$userGeoPoint = $userObject->get("location");
// Create a query for places
$query = new ParseQuery("PlaceObject");
// Interested in locations near user.
$query->near("location", $userGeoPoint);
// Limit what could be a lot of points.
$query->limit(10);
// Final array of objects
$placesObjects = $query->find();
At this point $placesObjects
will be an array of objects ordered by distance (nearest to farthest) from $userGeoPoint
. Note that if an additional ascending()
/descending()
order-by constraint is applied, it will take precedence over the distance ordering.
To limit the results using distance, check out withinMiles
, withinKilometers
, and withinRadians
.
It’s also possible to query for the set of objects that are contained within a particular area. To find the objects in a rectangular bounding box, add the withinGeoBox
restriction to your ParseQuery
.
$southwestOfSF = new ParseGeoPoint(37.708813, -122.526398);
$northeastOfSF = new ParseGeoPoint(37.822802, -122.373962);
$query = new ParseQuery("PizzaPlaceObject");
$query->withinGeoBox("location", $southwestOfSF, $northeastOfSF);
$pizzaPlacesInSF = $query->find();
At the moment there are a couple of things to watch out for:
near
constraint will also limit results to within 100 miles.Push Notifications are a great way to keep your users engaged and informed about your app. You can reach your entire user base quickly and effectively. This guide will help you through the setup process and the general usage of Parse to send push notifications.
There is no setup required to use the PHP SDK for sending push notifications. If you haven’t configured your iOS or Android clients to use Push, take a look at their respective setup instruction using the platform toggle at the top.
Every Parse application installed on a device registered for push notifications has an associated Installation
object. The Installation
object is where you store all the data needed to target push notifications. For example, in a baseball app, you could store the teams a user is interested in to send updates about their performance.
Note that Installation
data can only be modified by the client SDKs, the data browser, or the REST API.
This class has several special fields that help you manage and target devices.
badge
: The current value of the icon badge for iOS apps. Changes to this value on the server will be used for future badge-increment push notifications.channels
: An array of the channels to which a device is currently subscribed.timeZone
: The current time zone where the target device is located. This value is synchronized every time an Installation
object is saved from the device (readonly).deviceType
: The type of device, “ios” or “android” (readonly).installationId
: Unique Id for the device used by Parse (readonly).deviceToken
: The Apple generated token used for iOS devices (readonly).There are two ways to send push notifications using Parse: channels and advanced targeting. Channels offer a simple and easy to use model for sending pushes, while advanced targeting offers a more powerful and flexible model. Both are fully compatible with each other and will be covered in this section.
You can view your past push notifications on the Parse Dashboard push console for up to 30 days after creating your push. For pushes scheduled in the future, you can delete the push on the web console as long as no sends have happened yet. After you send the push, the web console shows push analytics graphs.
The simplest way to start sending notifications is using channels. This allows you to use a publisher-subscriber model for sending pushes. Devices start by subscribing to one or more channels, and notifications can later be sent to these subscribers. The channels subscribed to by a given Installation
are stored in the channels
field of the Installation
object.
The PHP SDK does not currently support subscribing iOS and Android devices for pushes. Take a look at the iOS, Android or REST Push guide using the platform toggle at the top.
With the PHP SDK, the following code can be used to alert all subscribers of the “Giants” and “Mets” channels about the results of the game. This will display a notification center alert to iOS users and a system tray notification to Android users.
$data = array("alert" => "Hi!");
ParsePush::send(array(
"channels" => ["PHPFans"],
"data" => $data
), true);
While channels are great for many applications, sometimes you need more precision when targeting the recipients of your pushes. Parse allows you to write a query for any subset of your Installation
objects using the querying API and to send them a push.
Since Installation
objects are just like any other object stored in Parse, you can save any data you want and even create relationships between Installation
objects and your other objects. This allows you to send pushes to a very customized and dynamic segment of your user base.
The PHP SDK currently supports modifying Installation
objects via the ParseInstallation
class, but it is not the primary function of this sdk. If you do need to modify installation objects take a look at the iOS, Android or REST Push guide using the platform toggle at the top.
Generally if you need to saving installation data it will be a modification to an existing installation. Considering this it is recommended you read the related SDK docs for that installation first before you start modifying them, as mentioned above.
Once you have your data stored on your Installation
objects, you can use a query to target a subset of these devices. Parse.Installation
queries work just like any other Parse query.
$query = ParseInstallation::query();
$query->equalTo("design", "rad");
ParsePush::send(array(
"where" => $query,
"data" => $data
), true);
We can even use channels with our query. To send a push to all subscribers of the “Giants” channel but filtered by those who want score update, we can do the following:
$query = ParseInstallation::query();
$query->equalTo("channels", "Giants");
$query->equalTo("scores", true);
ParsePush::send(array(
"where" => $query,
"data" => array(
"alert" => "Giants scored against the A's! It's now 2-2."
)
), true);
If we store relationships to other objects in our Installation
class, we can also use those in our query. For example, we could send a push notification to all users near a given location like this.
// Find users near a given location
$userQuery = ParseUser::query();
$userQuery->withinMiles("location", $stadiumLocation, 1.0);
// Find devices associated with these users
$pushQuery = ParseInstallation::query();
$pushQuery->matchesQuery('user', $userQuery);
// Send push notification to query
ParsePush::send(array(
"where" => $pushQuery,
"data" => array(
"alert" => "Free hotdogs at the Parse concession stand!"
)
), true);
If you want to keep track of your sends when using queries you can use the ParseAudience
class (available in sdk versions 1.4.0 and up).
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.
$iosQuery = ParseInstallation::getQuery();
$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
// 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!'
],
'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 notifications can do more than just send a message. In iOS, pushes can also include the sound to be played, the badge number to display as well as any custom data you wish to send. In Android, it is even possible to specify an Intent
to be fired upon receipt of a notification. An expiration date can also be set for the notification in case it is time sensitive.
If you want to send more than just a message, you can set other fields in the data
dictionary. There are some reserved fields that have a special meaning.
alert
: the notification’s message.badge
: (iOS only) the value indicated in the top right corner of the app icon. This can be set to a value or to Increment
in order to increment the current value by 1.sound
: (iOS only) the name of a sound file in the application bundle.content-available
: (iOS only) If you are a writing an app using the Remote Notification Background Mode introduced in iOS7 (a.k.a. “Background Push”), set this value to 1 to trigger a background download.category
: (iOS only) the identifier of the UNNotificationCategory
for this push notification.uri
: (Android only) an optional field that contains a URI. When the notification is opened, an Activity
associated with opening the URI is launched.title
: (Android only) the value displayed in the Android system tray notification.For example, to send a notification that increases the current badge number by 1 and plays a custom sound for iOS devices, and displays a particular title for Android users, you can do the following:
ParsePush::send(array(
"channels" => [ "Mets" ],
"data" => array(
"alert" => "The Mets scored! The game is now tied 1-1.",
"badge" => "Increment",
"sound" => "cheering.caf",
"title" => "Mets Score!"
)
), true);
It is also possible to specify your own data in this dictionary. As explained in the Receiving Notifications section for iOS and Android, iOS will give you access to this data only when the user opens your app via the notification and Android will provide you this data in the Intent
if one is specified.
$query = ParseInstallation::query();
$query->equalTo('channels', 'Indians');
$query->equalTo('injuryReports', true);
ParsePush::send(array(
"where" => $query,
"data" => array(
"action" => "com.example.UPDATE_STATUS"
"alert" => "Ricky Vaughn was injured in last night's game!",
"name" => "Vaughn",
"newsItem" => "Man bites dog"
)
), true);
When a user’s device is turned off or not connected to the internet, push notifications cannot be delivered. If you have a time sensitive notification that is not worth delivering late, you can set an expiration date. This avoids needlessly alerting users of information that may no longer be relevant.
There are two parameters provided by Parse to allow setting an expiration date for your notification. The first is expiration_time
which takes a DateTime
specifying when Parse should stop trying to send the notification.
Alternatively, you can use the expiration_interval
parameter to specify a duration of time before your notification expires. This value is relative to the push_time
parameter used to schedule notifications. This means that a push notification scheduled to be sent out in 1 day and an expiration interval of 6 days can be received up to a week from now.
If you build a cross platform app, it is possible you may only want to target iOS or Android devices. There are two methods provided to filter which of these devices are targeted. Note that both platforms are targeted by default.
The following examples would send a different notification to Android and iOS users.
// Notification for Android users
$queryAndroid = ParseInstallation::query();
$queryAndroid->equalTo('deviceType', 'android');
ParsePush::send(array(
"where" => $queryAndroid,
"data" => array(
"alert" => "Your suitcase has been filled with tiny robots!"
)
), true);
// Notification for iOS users
$queryIOS = ParseInstallation::query();
$queryIOS->equalTo('deviceType', 'ios');
ParsePush::send(array(
"where" => $queryIOS,
"data" => array(
"alert" => "Your suitcase has been filled with tiny apples!"
)
), true);
// Notification for Windows 8 users
$queryWindows = ParseInstallation::query();
$queryWindows->equalTo('deviceType', 'winrt');
ParsePush::send(array(
"where" => $queryWindows,
"data" => array(
"alert" => "Your suitcase has been filled with tiny surfaces!"
)
), true);
// Notification for Windows Phone 8 users
$queryWP8 = ParseInstallation::query();
$queryWP8->equalTo('deviceType', 'winphone');
ParsePush::send(array(
"where" => $queryWP8,
"data" => array(
"alert" => "Your suitcase is very hip; very metro."
)
), true);
You can schedule a push in advance by specifying a push_time
parameter of type DateTime
.
If you also specify an expiration_interval
, it will be calculated from the scheduled push time, not from the time the push is submitted. This means a push scheduled to be sent in a week with an expiration interval of a day will expire 8 days after the request is sent.
The scheduled time cannot be in the past, and can be up to two weeks in the future. It can be an ISO 8601 date with a date, time, and timezone, as in the example above, or it can be a numeric value representing a UNIX epoch time in seconds (UTC).
If the version of ParseServer you are running supports it you can retrieve a PushStatus
object from the response.
From that you can fetch the status message of the push request, number of pushes sent, number failed, and more.
$data = array("alert" => "Hi!");
$response = ParsePush::send(array(
"channels" => ["PHPFans"],
"data" => $data
), true);
// check if a push status id is present
if(ParsePush::hasStatus($response)) {
// Retrieve PushStatus object
$pushStatus = ParsePush::getStatus($response);
// get push status string
$status = $pushStatus->getPushStatus();
if($status == "succeeded") {
// handle a successful push request
} else if($status == "running") {
// handle a running push request
} else {
// push request did not succeed
}
// get # pushes sent
$sent = $pushStatus->getPushesSent();
// get # pushes failed
$failed = $pushStatus->getPushesFailed();
}
The PHP SDK does not currently support receiving pushes. To learn more about handling received notifications in iOS or Android, use the platform toggle at the top.
For tips on troubleshooting push notifications, check the troubleshooting sections for iOS, Android, and .NET using the platform toggle at the top.
Any server version 2.1.4 or later supports access to detailed information about itself and it’s capabilities.
You can leverage ParseServerInfo
(present in sdk version 1.4.0 and up) to check on the features and version of your server.
You can get the current version of the server you are connected to. Do keep in mind that server must be at version 2.1.4 or above for this to work.
// get the current version of the server you are connected to (2.6.5, 2.5.4, etc.)
$version = ParseServerInfo::getVersion();
Check which features your server has and how they are configured in some cases.
// get various features
$globalConfigFeatures = ParseServerInfo::getGlobalConfigFeatures();
/**
* Returns json of the related features
* {
* "create" : true,
* "read" : true,
* "update" : true,
* "delete" : true
* }
*/
// you can always get all feature data
$data = ParseServerInfo::getFeatures();
For your convenience we’ve added getters for the following features in particular:
ParseServerInfo::getHooksFeatures();
ParseServerInfo::getCloudCodeFeatures();
ParseServerInfo::getLogsFeatures();
ParseServerInfo::getPushFeatures();
ParseServerInfo::getSchemasFeatures();
// additional features can be obtained manually using 'get'
$feature = ParseServerInfo::get('new-feature');
Direct manipulation of the classes that are on your server is possible through ParseSchema
. Although fields and classes can be automatically generated (the latter assuming client class creation is enabled) ParseSchema gives you explicit control over these classes and their fields.
// create an instance to manage your class
$mySchema = new ParseSchema("MyClass");
// gets the current schema data as an associative array, for inspection
$data = $mySchema->get();
// add any # of fields, without having to create any objects
$mySchema->addString('string_field');
$mySchema->addNumber('num_field');
$mySchema->addBoolean('bool_field');
$mySchema->addDate('date_field');
$mySchema->addFile('file_field');
$mySchema->addGeoPoint('geopoint_field');
$mySchema->addPolygon('polygon_field');
$mySchema->addArray('array_field');
$mySchema->addObject('obj_field');
$mySchema->addPointer('pointer_field');
// you can even setup pointer/relation fields this way
$mySchema->addPointer('pointer_field', 'TargetClass');
$mySchema->addRelation('relation_field', 'TargetClass');
// new types can be added as they are available
$mySchema->addField('new_field', 'ANewDataType');
// save/update this schema to persist your field changes
$mySchema->save();
// or
$mySchema->update();
Assuming you want to remove a field you can simply call deleteField
and save/update
to clear it out.
$mySchema->deleteField('string_field');
$mySchema->save():
// or for an existing schema...
$mySchema->update():
Indexes support efficient execution of queries from the database. Keep in mind that the masterKey is required for these operations, so be sure it’s set in your initialization code before you use this feature.
// To add an index, the field must exist before you create an index
$schema->addString('field');
$index = [ 'field' => 1 ];
$schema->addIndex('index_name', $index);
$schema->save();
// Delete an index
$schema->deleteIndex('index_name');
$schema->save();
// If indexes exist, you can retrieve them
$result = $schema->get();
$indexes = $result['indexes'];
All objects can be purged from a schema (class) via purge
. But be careful! This can be considered an irreversible action. Only do this if you really need to delete all objects from a class, such as when you need to delete the class (as in the code example above).
// delete all objects in the schema
$mySchema->purge();
Server side cloud code is a powerful component of parse server.
You can read more about how to setup cloud code in the cloud code guide.
Directly call server-side cloud code functions and get their results.
$results = ParseCloud::run("aCloudFunction", array("from" => "php"));
Like cloud functions, cloud jobs allow you to run code server-side but in an asynchronous fashion. Instead of waiting for execution to complete you are immediately returned an id for tracking the job’s progress. You can use this id to see the current information on a job and whether it has completed.
// start job
$jobStatusId = ParseCloud::startJob('MyCloudJob', array("startedBy" => "me!"));
// get job status, a ParseObject!
$jobStatus = ParseCloud::getJobStatus($jobStatusId);
$status = $jobStatus->get('status'); // failed / succeeded when done
ParseLogs
allows info and error logs to be retrieved from the server as JSON.
This is available in sdk versions 1.4.0 and up.
Using the same approach as that which is utilized in the dashboard you can view your logs with specific ranges in time, type and order. Note that this requires the correct masterKey to be set during your initialization for access.
// get last 100 info logs, sorted in descending order
$logs = ParseLogs::getInfoLogs();
// get last 100 info logs, sorted in descending order
$logs = ParseLogs::getErrorLogs();
// logs can be retrieved with further specificity
// get 10 logs from a date up to a date in ascending order
$logs = ParseLogs::getInfoLogs(10, $fromDate, $untilDate, 'asc');
// above can be done for 'getErrorLogs' as well
ParseConfig allows you to access the global Config object for your parse server setup.
You can get, set and update simple values much like you would on an instance of ParseObject. Through this all your SDKs and applications can have access to global settings, options, and more. What you choose to put in your config is purely up to you however.
// get existing application-wide config
$config = new ParseConfig();
// check a config value of yours
$allowed = $config->get('feature_allowed');
// add a simple config value
$config->set('feature_allowed', true);
// save this global config
$config->save();
Parse provides a number of hooks for you to get a glimpse into the ticking heart of your app. We understand that it’s important to understand what your app is doing, how frequently, and when.
While this section will cover different ways to instrument your app to best take advantage of Parse’s analytics backend, developers using Parse to store and retrieve data can already take advantage of metrics on Parse.
ParseAnalytics
also allows you to track free-form events, with a handful of string
keys and values. These extra dimensions allow segmentation of your custom events via your app’s Dashboard.
Say your app offers search functionality for apartment listings, and you want to track how often the feature is used, with some additional metadata.
// Define ranges to bucket data points into meaningful segments
$dimensions = [
"priceRange" => '1000-1500',
"source" => 'craigslist',
"dayType": 'weekday'
];
// Send the dimensions to Parse along with the 'search' event
ParseAnalytics::track('search', $dimensions);
ParseAnalytics
can even be used as a lightweight error tracker — simply invoke the following and you’ll have access to an overview of the rate and frequency of errors, broken down by error code, in your application:
$codeString = '' + $error->getCode();
ParseAnalytics::track('error', ["code" => codeString]);
Note that Parse currently only stores the first eight dimension pairsper call to ParseAnalytics::track()
.
We’ve designed the Parse SDKs so that you typically don’t need to worry about how data is saved while using the client SDKs. Simply add data to the Parse Object
, and it’ll be saved correctly.
Nevertheless, there are some cases where it’s useful to be aware of how data is stored on the Parse platform.
Internally, Parse stores data as JSON, so any datatype that can be converted to JSON can be stored on Parse. Refer to the Data Types in Objects section of this guide to see platform-specific examples.
Keys including the characters $
or .
, along with the key __type
key, are reserved for the framework to handle additional types, so don’t use those yourself. Key names must contain only numbers, letters, and underscore, and must start with a letter. Values can be anything that can be JSON-encoded.
When a class is initially created, it doesn’t have an inherent schema defined. This means that for the first object, it could have any types of fields you want.
However, after a field has been set at least once, that field is locked into the particular type that was saved. For example, if a User
object is saved with field name
of type String
, that field will be restricted to the String
type only (the server will return an error if you try to save anything else).
One special case is that any field can be set to null
, no matter what type it is.
The Data Browser is the web UI where you can update and create objects in each of your apps. Here, you can see the raw JSON values that are saved that represents each object in your class.
When using the interface, keep in mind the following:
objectId
, createdAt
, updatedAt
fields cannot be edited (these are set automatically).null
).The Data Browser is also a great place to test the Cloud Code validations contained in your Cloud Code functions (such as beforeSave
). These are run whenever a value is changed or object is deleted from the Data Browser, just as they would be if the value was changed or deleted from your client code.
You may import data into your Parse app by using CSV or JSON files. To create a new class with data from a CSV or JSON file, go to the Data Browser and click the “Import” button on the left hand column.
The JSON format is an array of objects in our REST format or a JSON object with a results
that contains an array of objects. It must adhere to the JSON standard. A file containing regular objects could look like:
{ "results": [
{
"score": 1337,
"playerName": "Sean Plott",
"cheatMode": false,
"createdAt": "2022-01-01T12:23:45.678Z",
"updatedAt": "2022-01-01T12:23:45.678Z",
"objectId": "fchpZwSuGG"
}]
}
Objects in either format should contain keys and values that also satisfy the following:
\n
’.Normally, when objects are saved to Parse, they are automatically assigned a unique identifier through the objectId
field, as well as a createdAt
field and updatedAt
field which represent the time that the object was created and last modified in your Parse Server. These fields can be manually set when data is imported from a JSON file. Please keep in mind the following:
objectId
fields.createdAt
field or the updatedAt
field.In addition to the exposed fields, objects in the Parse User class can also have the bcryptPassword
field set. The value of this field is a String
that is the bcrypt hashed password + salt in the modular crypt format described in this StackOverflow answer. Most OpenSSL based bcrypt implementations should have built-in methods to produce these strings.
A file containing a User
object could look like:
{ "results":
[{
"username": "cooldude",
"createdAt": "1983-09-13T22:42:30.548Z",
"updatedAt": "2015-09-04T10:12:42.137Z",
"objectId": "ttttSEpfXm",
"sessionToken": "dfwfq3dh0zwe5y2sqv514p4ib",
"bcryptPassword": "$2a$10$ICV5UeEf3lICfnE9W9pN9.O9Ved/ozNo7G83Qbdk5rmyvY8l16MIK"
}]
}
Note that in CSV the import field types are limited to String
, Boolean
, and Number
.
You can request an export of your data at any time from your app’s Settings page. The data export runs at a lower priority than production queries, so if your app is still serving queries, production traffic will always be given a higher priority, which may slow down the delivery of your data export.
Each collection will be exported in the same JSON format used by our REST API and delivered in a single zipped file. Since data is stored internally as JSON, this allows us to ensure that the export closely matches how the data is saved to Parse. Other formats such as CSV cannot represent all of the data types supported by Parse without losing information. If you’d like to work with your data in CSV format, you can use any of the JSON-to-CSV converters available widely on the web.
For offline analysis of your data, we highly recommend using alternate ways to access your data that do not require extracting the entire collection at once. For example, you can try exporting only the data that has changed since your last export. Here are some ways of achieving this:
Use the JavaScript SDK in a node app. Parse.Query.each()
will allow you to extract every single object that matches a query. You can use date constraints to make sure the query only matches data that has been updated since you last ran this app. Your node app can write this data to disk for offline analysis.
Use the REST API in a script. You can run queries against your class and use skip/limit to page through results, which can then be written to disk for offline analysis. You can again use date constraints to make sure only newly updated data is extracted.
If the above two options do not fit your needs, you can try using the Data Browser to export data selectively. Use the Funnel icon to create a filter for the specific data that you need to export, such as newly updated objects. Once the filter has been applied, click on the Export data icon on the upper right of your Data Browser. This type of export will only include the objects that match your criteria.
There are three kinds of relationships. One-to-one relationships enable one object to be associated with another object. One-to-many relationships enable one object to have many related objects. Finally, many-to-many relationships enable complex relationships among many objects.
There are four ways to build relationships in Parse:
When you’re thinking about one-to-many relationships and whether to implement Pointers or Arrays, there are several factors to consider. First, how many objects are involved in this relationship? If the “many” side of the relationship could contain a very large number (greater than 100 or so) of objects, then you have to use Pointers. If the number of objects is small (fewer than 100 or so), then Arrays may be more convenient, especially if you typically need to get all of the related objects (the “many” in the “one-to-many relationship”) at the same time as the parent object.
Let’s say we have a game app. The game keeps track of the player’s score and achievements every time she chooses to play. In Parse, we can store this data in a single Game
object. If the game becomes incredibly successful, each player will store thousands of Game
objects in the system. For circumstances like this, where the number of relationships can be arbitrarily large, Pointers are the best option.
Suppose in this game app, we want to make sure that every Game
object is associated with a Parse User. We can implement this like so:
$game = ParseObject::create("Game");
$game->set("createdBy", ParseUser::getCurrentUser());
We can obtain all of the Game
objects created by a Parse User with a query:
$gameQuery = new ParseQuery("Game");
$gameQuery->equalTo("createdBy", ParseUser::getCurrentUser());
And, if we want to find the Parse User who created a specific Game
, that is a lookup on the createdBy
key:
// say we have a Game object
$game = ...
// getting the user who created the Game
$user = $game->get("createdBy");
For most scenarios, Pointers will be your best bet for implementing one-to-many relationships.
Arrays are ideal when we know that the number of objects involved in our one-to-many relationship are going to be small. Arrays will also provide some productivity benefit via the includeKey
parameter. Supplying the parameter will enable you to obtain all of the “many” objects in the “one-to-many” relationship at the same time that you obtain the “one” object. However, the response time will be slower if the number of objects involved in the relationship turns out to be large.
Suppose in our game, we enabled players to keep track of all the weapons their character has accumulated as they play, and there can only be a dozen or so weapons. In this example, we know that the number of weapons is not going to be very large. We also want to enable the player to specify the order in which the weapons will appear on screen. Arrays are ideal here because the size of the array is going to be small and because we also want to preserve the order the user has set each time they play the game:
Let’s start by creating a column on our Parse User object called weaponsList
.
Now let’s store some Weapon
objects in the weaponsList
:
// let's say we have four weapons
$scimitar = ...
$plasmaRifle = ...
$grenade = ...
$bunnyRabbit = ...
// stick the objects in an array
$weapons = [$scimitar, $plasmaRifle, $grenade, $bunnyRabbit];
// store the weapons for the user
$user = ParseUser::getCurrentUser();
$user->set("weaponsList", weapons);
Later, if we want to retrieve the Weapon
objects, it’s just one line of code:
$weapons = ParseUser::getCurrentUser()->get("weaponsList");
Sometimes, we will want to fetch the “many” objects in our one-to-many relationship at the same time as we fetch the “one” object. One trick we could employ is to use the includeKey
(or include
in Android) parameter whenever we use a Parse Query to also fetch the array of Weapon
objects (stored in the weaponsList
column) along with the Parse User object:
// set up our query for a User object
$userQuery = ParseUser::query();
// configure any constraints on your query...
// for example, you may want users who are also playing with or against you
// tell the query to fetch all of the Weapon objects along with the user
// get the "many" at the same time that you're getting the "one"
$userQuery->includeKey("weaponsList");
// execute the query
$results = $userQuery->find();
// results contains all of the User objects, and their associated Weapon objects, too
You can also get the “one” side of the one-to-many relationship from the “many” side. For example, if we want to find all Parse User objects who also have a given Weapon
, we can write a constraint for our query like this:
// add a constraint to query for whenever a specific Weapon is in an array
$userQuery->equalTo("weaponsList", $scimitar);
// or query using an array of Weapon objects...
$userQuery->containedIn("weaponsList", $arrayOfWeapons);
Now let’s tackle many-to-many relationships. Suppose we had a book reading app and we wanted to model Book
objects and Author
objects. As we know, a given author can write many books, and a given book can have multiple authors. This is a many-to-many relationship scenario where you have to choose between Arrays, Parse Relations, or creating your own Join Table.
The decision point here is whether you want to attach any metadata to the relationship between two entities. If you don’t, Parse Relation or using Arrays are going to be the easiest alternatives. In general, using arrays will lead to higher performance and require fewer queries. If either side of the many-to-many relationship could lead to an array with more than 100 or so objects, then, for the same reason Pointers were better for one-to-many relationships, Parse Relation or Join Tables will be better alternatives.
On the other hand, if you want to attach metadata to the relationship, then create a separate table (the “Join Table”) to house both ends of the relationship. Remember, this is information about the relationship, not about the objects on either side of the relationship. Some examples of metadata you may be interested in, which would necessitate a Join Table approach, include:
Using Parse Relations, we can create a relationship between a Book
and a few Author
objects. In the Data Browser, you can create a column on the Book
object of type relation and name it authors
.
After that, we can associate a few authors with this book:
// let’s say we have a few objects representing Author objects
$authorOne = ...
$authorTwo = ...
$authorThree = ...
// now we create a book object
$book = new ParseObject("Book");
// now let’s associate the authors with the book
// remember, we created a "authors" relation on Book
$relation = $book->getRelation("authors");
$relation->add($authorOne);
$relation->add($authorTwo);
$relation->add($authorThree);
// now save the book object
$book->save();
To get the list of authors who wrote a book, create a query:
// suppose we have a book object
$book = ...
// create a relation based on the authors key
$relation = $book->getRelation("authors");
// generate a query based on that relation
$query = $relation->getQuery();
// now execute the query
Perhaps you even want to get a list of all the books to which an author contributed. You can create a slightly different kind of query to get the inverse of the relationship:
// suppose we have a author object, for which we want to get all books
$author = ...
// first we will create a query on the Book object
$query = new ParseQuery("Book");
// now we will query the authors relation to see if the author object we have
// is contained therein
$query->equalTo("authors", $author);
There may be certain cases where we want to know more about a relationship. For example, suppose we were modeling a following/follower relationship between users: a given user can follow another user, much as they would in popular social networks. In our app, we not only want to know if User A is following User B, but we also want to know when User A started following User B. This information could not be contained in a Parse Relation. In order to keep track of this data, you must create a separate table in which the relationship is tracked. This table, which we will call Follow
, would have a from
column and a to
column, each with a pointer to a Parse User. Alongside the relationship, you can also add a column with a Date
object named date
.
Now, when you want to save the following relationship between two users, create a row in the Follow
table, filling in the from
, to
, and date
keys appropriately:
// suppose we have a user we want to follow
$otherUser = ...
// create an entry in the Follow table
$follow = new ParseObject("Follow");
$follow->set("from", ParseUser::getCurrentUser());
$follow->set("to", $otherUser);
$follow->set("date", new DateTime());
$follow->save();
If we want to find all of the people we are following, we can execute a query on the Follow
table:
// set up the query on the Follow table
$query = new ParseQuery("Follow");
$query->equalTo("from", ParseUser::getCurrentUser());
// execute the query
$results = $query->find();
It’s also pretty easy to find all the users that are following the current user by querying on the to
key:
// create an entry in the Follow table
$query = new ParseQuery("Follow");
$query->equalTo("to", ParseUser::getCurrentUser());
$results = $query->find();
Arrays are used in Many-to-Many relationships in much the same way that they are for One-to-Many relationships. All objects on one side of the relationship will have an Array column containing several objects on the other side of the relationship.
Suppose we have a book reading app with Book
and Author
objects. The Book
object will contain an Array of Author
objects (with a key named authors
). Arrays are a great fit for this scenario because it’s highly unlikely that a book will have more than 100 or so authors. We will put the Array in the Book
object for this reason. After all, an author could write more than 100 books.
Here is how we save a relationship between a Book
and an Author
.
// let's say we have an author
$author = ...
// and let's also say we have an book
$book = ...
// add the author to the authors list for the book
$book->addUnique("authors", array($author));
Because the author list is an Array, you should use the includeKey
(or include
on Android) parameter when fetching a Book
so that Parse returns all the authors when it also returns the book:
// set up our query for the Book object
$bookQuery = new ParseQuery("Book");
// configure any constraints on your query...
// tell the query to fetch all of the Author objects along with the Book
$bookQuery->includeKey("authors");
// execute the query
$books= $bookQuery->find();
At that point, getting all the Author
objects in a given Book
is a pretty straightforward call:
$authorList = $book->get("authors");
Finally, suppose you have an Author
and you want to find all the Book
objects in which she appears. This is also a pretty straightforward query with an associated constraint:
// set up our query for the Book object
$bookQuery = new ParseQuery("Book");
// configure any constraints on your query...
$bookQuery->equalTo("authors", $author);
// tell the query to fetch all of the Author objects along with the Book
$bookQuery->includeKey("authors");
// execute the query
$books = $bookQuery->find();
In Parse, a one-to-one relationship is great for situations where you need to split one object into two objects. These situations should be rare, but two examples include:
Thank you for reading this far. We apologize for the complexity. Modeling relationships in data is a hard subject, in general. But look on the bright side: it’s still easier than relationships with people.
The Parse PHP SDK throws ParseException
s when errors are returned from the Parse API. For other errors, the base Exception
class will be thrown. It is recommended to wrap your Parse calls in try/catch blocks to handle any errors which occur.
$query = new ParseQuery("Note");
try {
// This will throw a ParseException, the object is not found.
$result = $query->get("aBcDeFgH")
} catch (ParseException $error) {
// $error is an instance of ParseException with details about the error.
echo $error->getCode();
echo $error->getMessage();
}
For a list of all possible error codes, scroll down to Error Codes.
As your app development progresses, you will want to use Parse’s security features in order to safeguard data. This document explains the ways in which you can secure your apps.
If your app is compromised, it’s not only you as the developer who suffers, but potentially the users of your app as well. Continue reading for our suggestions for sensible defaults and precautions to take before releasing your app into the wild.
When an app first connects to Parse, it identifies itself with an Application ID and a Client key (or REST Key, or .NET Key, or JavaScript Key, depending on which platform you’re using). These are not secret and by themselves they do not secure an app. These keys are shipped as a part of your app, and anyone can decompile your app or proxy network traffic from their device to find your client key. This exploit is even easier with JavaScript — one can simply “view source” in the browser and immediately find your client key.
This is why Parse has many other security features to help you secure your data. The client key is given out to your users, so anything that can be done with just the client key is doable by the general public, even malicious hackers.
The master key, on the other hand, is definitely a security mechanism. Using the master key allows you to bypass all of your app’s security mechanisms, such as class-level permissions and ACLs. Having the master key is like having root access to your app’s servers, and you should guard your master key with the same zeal with which you would guard your production machines’ root password.
The overall philosophy is to limit the power of your clients (using client keys), and to perform any sensitive actions requiring the master key in Cloud Code. You’ll learn how to best wield this power in the section titled Implementing Business Logic in Cloud Code.
A final note: It is recommended to setup HTTPS and SSL in your server, to avoid man-in-the-middle attacks, but Parse works fine as well with non-HTTPS connections.
The second level of security is at the schema and data level. Enforcing security measures at this level will restrict how and when client applications can access and create data on Parse. When you first begin developing your Parse application, all of the defaults are set so that you can be a more productive developer. For example:
You can configure any of these permissions to apply to everyone, no one, or to specific users or roles in your app. Roles are groups that contain users or other roles, which you can assign to an object to restrict its use. Any permission granted to a role is also granted to any of its children, whether they are users or other roles, enabling you to create an access hierarchy for your apps. Each of the Parse guides includes a detailed description of employing Roles in your apps.
Once you are confident that you have the right classes and relationships between classes in your app, you should begin to lock it down by doing the following:
Almost every class that you create should have these permissions tweaked to some degree. For classes where every object has the same permissions, class-level settings will be most effective. For example, one common use case entails having a class of static data that can be read by anyone but written by no one.
As a start, you can configure your application so that clients cannot create new classes on Parse. This is done by setting the key allowClientClassCreation
to false
in your ParseServer configuration. See the project Readme for an overview of Configuring your ParseServer. Once restricted, classes may only be created from the Data Browser or with a the masterKey
. This will prevent attackers from filling your database with unlimited, arbitrary new classes.
Requires Parse Server 5.0.0+
By default, Parse Server creates Users with public read access. This allows other users, and un-authenticated users, to read data such as email
. When moving to production, set the key enforcePrivateUsers
to true
, as this will remove the public read access to new users.
Parse lets you specify what operations are allowed per class. This lets you restrict the ways in which clients can access or modify your classes. To change these settings, go to the Data Browser, select a class, and click the “Security” button.
You can configure the client’s ability to perform each of the following operations for the selected class:
Read:
Get: With Get permission, users can fetch objects in this table if they know their objectIds.
Find: Anyone with Find permission can query all of the objects in the table, even if they don’t know their objectIds. Any table with public Find permission will be completely readable by the public, unless you put an ACL on each object.
Write:
Update: Anyone with Update permission can modify the fields of any object in the table that doesn’t have an ACL. For publicly readable data, such as game levels or assets, you should disable this permission.
Create: Like Update, anyone with Create permission can create new objects of a class. As with the Update permission, you’ll probably want to turn this off for publicly readable data.
Delete: With this permission, people can delete any object in the table that doesn’t have an ACL. All they need is its objectId.
Add fields: Parse classes have schemas that are inferred when objects are created. While you’re developing your app, this is great, because you can add a new field to your object without having to make any changes on the backend. But once you ship your app, it’s very rare to need to add new fields to your classes automatically. You should pretty much always turn off this permission for all of your classes when you submit your app to the public.
For each of the above actions, you can grant permission to all users (which is the default), or lock permissions down to a list of roles and users. For example, a class that should be available to all users would be set to read-only by only enabling get and find. A logging class could be set to write-only by only allowing creates. You could enable moderation of user-generated content by providing update and delete access to a particular set of users or roles.
Once you’ve locked down your schema and class-level permissions, it’s time to think about how data is accessed by your users. Object-level access control enables one user’s data to be kept separate from another’s, because sometimes different objects in a class need to be accessible by different people. For example, a user’s private personal data should be accessible only to them.
Parse also supports the notion of anonymous users for those apps that want to store and protect user-specific data without requiring explicit login.
When a user logs into an app, they initiate a session with Parse. Through this session they can add and modify their own data but are prevented from modifying other users’ data.
The easiest way to control who can access which data is through access control lists, commonly known as ACLs. The idea behind an ACL is that each object has a list of users and roles along with what permissions that user or role has. A user needs read permissions (or must belong to a role that has read permissions) in order to retrieve an object’s data, and a user needs write permissions (or must belong to a role that has write permissions) in order to update or delete that object.
Once you have a User, you can start using ACLs. Remember: Users can be created through traditional username/password sign up, through a third-party login system like Facebook or Twitter, or even by using Parse’s automatic anonymous users functionality. To set an ACL on the current user’s data to not be publicly readable, all you have to do is:
$user = ParseUser::getCurrentUser();
$user->setACL(new ParseACL($user))
Most apps should do this. If you store any sensitive user data, such as email addresses or phone numbers, you need to set an ACL like this so that the user’s private information isn’t visible to other users. If an object doesn’t have an ACL, it’s readable and writeable by everyone. The only exception is the _User
class. We never allow users to write each other’s data, but they can read it by default. (If you as the developer need to update other _User
objects, remember that your master key can provide the power to do this.)
To make it super easy to create user-private ACLs for every object, we have a way to set a default ACL that will be used for every new object you create:
ParseACL::setDefaultACL(new ParseACL(), true);
If you want the user to have some data that is public and some that is private, it’s best to have two separate objects. You can add a pointer to the private data from the public one.
$privateData = ParseObject::create("PrivateUserData");
$privateData->setACL(new ParseACL(ParseUser::getCurrentUser()));
$privateData->set("phoneNumber", "555-5309");
ParseUser::getCurrentUser()->set("privateData", $privateData);
Of course, you can set different read and write permissions on an object. For example, this is how you would create an ACL for a public post by a user, where anyone can read it:
$acl = new ParseACL();
$acl->setPublicReadAccess(true);
$acl->setWriteAccess(ParseUser::getCurrentUser(), true);
Sometimes it’s inconvenient to manage permissions on a per-user basis, and you want to have groups of users who get treated the same (like a set of admins with special powers). Roles are a special kind of object that let you create a group of users that can all be assigned to the ACL. The best thing about roles is that you can add and remove users from a role without having to update every single object that is restricted to that role. To create an object that is writeable only by admins:
$acl = new ParseACL();
$acl->setPublicReadAccess(true);
$acl->setRoleWriteAccessWithName("admins", true);
Of course, this snippet assumes you’ve already created a role named “admins”. This is often reasonable when you have a small set of special roles set up while developing your app. Roles can also be created and updated on the fly — for example, adding new friends to a “friendOf___” role after each connection is made.
All this is just the beginning. Applications can enforce all sorts of complex access patterns through ACLs and class-level permissions. For example:
For the curious, here’s the format for an ACL that restricts read and write permissions to the owner (whose objectId
is identified by "aSaMpLeUsErId"
) and enables other users to read the object:
{
"*": { "read":true },
"aSaMpLeUsErId": { "read" :true, "write": true }
}
And here’s another example of the format of an ACL that uses a Role:
{
"role:RoleName": { "read": true },
"aSaMpLeUsErId": { "read": true, "write": true }
}
Pointer permissions are a special type of class-level permission that create a virtual ACL on every object in a class, based on users stored in pointer fields on those objects. For example, given a class with an owner
field, setting a read pointer permission on owner
will make each object in the class only readable by the user in that object’s owner
field. For a class with a sender
and a reciever
field, a read pointer permission on the receiver
field and a read and write pointer permission on the sender
field will make each object in the class readable by the user in the sender
and receiver
field, and writable only by the user in the sender
field.
Given that objects often already have pointers to the user(s) that should have permissions on the object, pointer permissions provide a simple and fast solution for securing your app using data which is already there, that doesn’t require writing any client code or cloud code.
Pointer permissions are like virtual ACLs. They don’t appear in the ACL column, but if you are familiar with how ACLs work, you can think of them like ACLs. In the above example with the sender
and receiver
, each object will act as if it has an ACL of:
{
"<SENDER_USER_ID>": {
"read": true,
"write": true
},
"<RECEIVER_USER_ID>": {
"read": true
}
}
Note that this ACL is not actually created on each object. Any existing ACLs will not be modified when you add or remove pointer permissions, and any user attempting to interact with an object can only interact with the object if both the virtual ACL created by the pointer permissions, and the real ACL already on the object allow the interaction. For this reason, it can sometimes be confusing to combine pointer permissions and ACLs, so we recommend using pointer permissions for classes that don’t have many ACLs set. Fortunately, it’s easy to remove pointer permissions if you later decide to use Cloud Code or ACLs to secure your app.
Starting version 2.3.0, parse-server introduces a new Class Level Permission requiresAuthentication
.
This CLP prevents any non authenticated user from performing the action protected by the CLP.
For example, you want to allow your authenticated users to find
and get
Announcement
’s from your application and your admin role to have all privileged, you would set the CLP:
// POST http://my-parse-server.com/schemas/Announcement
// Set the X-Parse-Application-Id and X-Parse-Master-Key header
// body:
{
classLevelPermissions:
{
"find": {
"requiresAuthentication": true,
"role:admin": true
},
"get": {
"requiresAuthentication": true,
"role:admin": true
},
"create": { "role:admin": true },
"update": { "role:admin": true },
"delete": { "role:admin": true }
}
}
Effects:
:warning: Note that this is in no way securing your content, if you allow anyone to login to your server, every client will still be able to query this object.
Class-Level Permissions (CLPs) and Access Control Lists (ACLs) are both powerful tools for securing your app, but they don’t always interact exactly how you might expect. They actually represent two separate layers of security that each request has to pass through to return the correct information or make the intended change. These layers, one at the class level, and one at the object level, are shown below. A request must pass through BOTH layers of checks in order to be authorized. Note that despite acting similarly to ACLs, Pointer Permissions are a type of class level permission, so a request must pass the pointer permission check in order to pass the CLP check.
As you can see, whether a user is authorized to make a request can become complicated when you use both CLPs and ACLs. Let’s look at an example to get a better sense of how CLPs and ACLs can interact. Say we have a Photo
class, with an object, photoObject
. There are 2 users in our app, user1
and user2
. Now lets say we set a Get CLP on the Photo
class, disabling public Get, but allowing user1
to perform Get. Now let’s also set an ACL on photoObject
to allow Read - which includes GET - for only user2
.
You may expect this will allow both user1
and user2
to Get photoObject
, but because the CLP layer of authentication and the ACL layer are both in effect at all times, it actually makes it so neither user1
nor user2
can Get photoObject
. If user1
tries to Get photoObject
, it will get through the CLP layer of authentication, but then will be rejected because it does not pass the ACL layer. In the same way, if user2
tries to Get photoObject
, it will also be rejected at the CLP layer of authentication.
Now lets look at example that uses Pointer Permissions. Say we have a Post
class, with an object, myPost
. There are 2 users in our app, poster
, and viewer
. Lets say we add a pointer permission that gives anyone in the Creator
field of the Post
class read and write access to the object, and for the myPost
object, poster
is the user in that field. There is also an ACL on the object that gives read access to viewer
. You may expect that this will allow poster
to read and edit myPost
, and viewer
to read it, but viewer
will be rejected by the Pointer Permission, and poster
will be rejected by the ACL, so again, neither user will be able to access the object.
Because of the complex interaction between CLPs, Pointer Permissions, and ACLs, we recommend being careful when using them together. Often it can be useful to use CLPs only to disable all permissions for a certain request type, and then using Pointer Permissions or ACLs for other request types. For example, you may want to disable Delete for a Photo
class, but then put a Pointer Permission on Photo
so the user who created it can edit it, just not delete it. Because of the especially complex way that Pointer Permissions and ACLs interact, we usually recommend only using one of those two types of security mechanisms.
There are some special classes in Parse that don’t follow all of the same security rules as every other class. Not all classes follow Class-Level Permissions (CLPs) or Access Control Lists (ACLs) exactly how they are defined, and here those exceptions are documented. Here “normal behavior” refers to CLPs and ACLs working normally, while any other special behaviors are described in the footnotes.
_User |
_Installation |
|
---|---|---|
Get | normal behaviour [1, 2, 3] | ignores CLP, but not ACL |
Find | normal behavior [3] | master key only [6] |
Create | normal behavior [4] | ignores CLP |
Update | normal behavior [5] | ignores CLP, but not ACL [7] |
Delete | normal behavior [5] | master key only [7] |
Add Field | normal behavior | normal behavior |
Logging in, or /parse/login
in the REST API, does not respect the Get CLP on the user class. Login works just based on username and password, and cannot be disabled using CLPs.
Retrieving the current user, or becoming a User based on a session token, which are both /parse/users/me
in the REST API, do not respect the Get CLP on the user class.
Read ACLs do not apply to the logged in user. For example, if all users have ACLs with Read disabled, then doing a find query over users will still return the logged in user. However, if the Find CLP is disabled, then trying to perform a find on users will still return an error.
Create CLPs also apply to signing up. So disabling Create CLPs on the user class also disables people from signing up without the master key.
Users can only Update and Delete themselves. Public CLPs for Update and Delete may still apply. For example, if you disable public Update for the user class, then users cannot edit themselves. But no matter what the write ACL on a user is, that user can still Update or Delete itself, and no other user can Update or Delete that user. As always, however, using the master key allows users to update other users, independent of CLPs or ACLs.
Get requests on installations follow ACLs normally. Find requests without master key is not allowed unless you supply the installationId
as a constraint.
Update requests on installations do adhere to the ACL defined on the installation, but Delete requests are master-key-only. For more information about how installations work, check out the installations section of the REST guide.
For most apps, care around keys, class-level permissions, and object-level ACLs are all you need to keep your app and your users’ data safe. Sometimes, though, you’ll run into an edge case where they aren’t quite enough. For everything else, there’s Cloud Code.
Cloud Code allows you to upload JavaScript to Parse’s servers, where we will run it for you. Unlike client code running on users’ devices that may have been tampered with, Cloud Code is guaranteed to be the code that you’ve written, so it can be trusted with more responsibility.
One particularly common use case for Cloud Code is preventing invalid data from being stored. For this sort of situation, it’s particularly important that a malicious client not be able to bypass the validation logic.
To create validation functions, Cloud Code allows you to implement a beforeSave
trigger for your class. These triggers are run whenever an object is saved, and allow you to modify the object or completely reject a save. For example, this is how you create a Cloud Code beforeSave trigger to make sure every user has an email address set:
Parse.Cloud.beforeSave(Parse.User, request => {
const user = request.object;
if (!user.get("email")) {
throw "Every user must have an email address.";
}
});
Validations can lock down your app so that only certain values are acceptable. You can also use afterSave
validations to normalize your data (e.g. formatting all phone numbers or currency identically). You get to retain most of the productivity benefits of accessing Parse data directly from your client applications, but you can also enforce certain invariants for your data on the fly.
Common scenarios that warrant validation include:
While validation often makes sense in Cloud Code, there are likely certain actions that are particularly sensitive, and should be as carefully guarded as possible. In these cases, you can remove permissions or the logic from clients entirely and instead funnel all such operations to Cloud Code functions.
When a Cloud Code function is called, it can use the optional {useMasterKey:true}
parameter to gain the ability to modify user data. With the master key, your Cloud Code function can override any ACLs and write data. This means that it’ll bypass all the security mechanisms you’ve put in place in the previous sections.
Say you want to allow a user to “like” a Post
object without giving them full write permissions on the object. You can do this by having the client call a Cloud Code function instead of modifying the Post itself:
The master key should be used carefully. setting useMasterKey
to true
only in the individual API function calls that need that security override:
Parse.Cloud.define("like", async request => {
var post = new Parse.Object("Post");
post.id = request.params.postId;
post.increment("likes");
await post.save(null, { useMasterKey: true })
});
One very common use case for Cloud Code is sending push notifications to particular users. In general, clients can’t be trusted to send push notifications directly, because they could modify the alert text, or push to people they shouldn’t be able to. Your app’s settings will allow you to set whether “client push” is enabled or not; we recommend that you make sure it’s disabled. Instead, you should write Cloud Code functions that validate the data to be pushed and sent before sending a push.
It’s important to restrict how often a client can call the Parse Server API. This prevents malicious attacks that could:
Parse Sever offers a mechanism to enforce rate limits by setting the Parse Server option rateLimit
, or by specifying a rateLimit
object on a Cloud Function validator.
The valid options for a rate limit are:
requestPath
: The path of the API route to be rate limited.requestMethods
: Optional, the HTTP request methods to be rate limited.requestTimeWindow
: The window of time in milliseconds within which the number of requests set in requestCount
can be made before the rate limit is applied.requestCount
: The number of requests that can be made per IP address within the time window set in requestTimeWindow
before the rate limit is applied.errorResponseMessage
: The error message that should be returned in the body of the HTTP 429 response when the rate limit is hit. Default is Too many requests.
.includeInternalRequests
: Optional, whether the rate limit will also apply to requests that are made in by Cloud Code.includeMasterKey
: Optional, whether the rate limit will also apply to requests using the masterKey
redisUrl
Optional, the URL of the Redis server to store rate limit data.To specify a server-wide rate limit of 200 requests per 15 minute window:
const parseServer = new ParseServer({
rateLimit: {
requestPath: '*',
requestTimeWindow: 15 * 60 * 1000,
requestCount: 200,
},
});
To specify a cloud function specific rate limit of 3 request per hour:
Parse.Cloud.define('someFunction', () => {
return 'Hello world';
}, {
rateLimit: {
requestTimeWindow: 60 * 60 * 1000,
requestCount: 3,
}
});
Rate limits can also be applied to beforeSave
triggers to restrict how often a given class is written to:
Parse.Cloud.beforeSave('TestObject', () => {}, {
rateLimit: {
requestTimeWindow: 1 * 60 * 1000 // one write per minute,,
requestCount: 1,
errorResponseMessage: 'Too many requests!',
},
});
⚠️ Rate limits should be enforced as far away from Parse Server as possible to mitigate possible impacts on resource costs, availability and integrity. While Parse Server offers a rate limiting mechanism as a conveniently available security feature without requiring a deep level of expertise, it is not considered best practice to enforce rate limits only after requests already reached the server. For better protection we advice to examine your network architecture an consider enforcing rate limits on the outer edge of the cloud if using a content delivery network, or at least before requests reach the server resource. Consult your cloud service provider for recommended rate limit and firewall solutions for your resources.
Parse provides a number of ways for you to secure data in your app. As you build your app and evaluate the kinds of data you will be storing, you can make the decision about which implementation to choose.
It is worth repeating that that the Parse User object is readable by all other users by default. You will want to set the ACL on your User object accordingly if you wish to prevent data contained in the User object (for example, the user’s email address) from being visible by other users.
Most classes in your app will fall into one of a couple of easy-to-secure categories. For fully public data, you can use class-level permissions to lock down the table to put publicly readable and writeable by no one. For fully private data, you can use ACLs to make sure that only the user who owns the data can read it. But occasionally, you’ll run into situations where you don’t want data that’s fully public or fully private. For example, you may have a social app, where you have data for a user that should be readable only to friends whom they’ve approved. For this you’ll need to a combination of the techniques discussed in this guide to enable exactly the sharing rules you desire.
We hope that you’ll use these tools to do everything you can to keep your app’s data and your users’ data secure. Together, we can make the web a safer place.
As your app scales, you will want to ensure that it performs well under increased load and usage. This document provides guidelines on how you can optimize your app’s performance. While you can use Parse Server for quick prototyping and not worry about performance, you will want to keep our performance guidelines in mind when you’re initially designing your app. We strongly advise that you make sure you’ve followed all suggestions before releasing your app.
You can improve your app’s performance by looking at the following:
Keep in mind that not all suggestions may apply to your app. Let’s look into each one of these in more detail.
Parse objects are stored in a database. A Parse query retrieves objects that you are interested in based on conditions you apply to the query. To avoid looking through all the data present in a particular Parse class for every query, the database can use an index. An index is a sorted list of items matching a given criteria. Indexes help because they allow the database to do an efficient search and return matching results without looking at all of the data. Indexes are typically smaller in size and available in memory, resulting in faster lookups.
You are responsible for managing your database and maintaining indexes when using Parse Server. If your data is not indexed, every query will have to go through the the entire data for a class to return a query result. On the other hand, if your data is indexed appropriately, the number of documents scanned to return a correct query result should be low.
The order of a query constraint’s usefulness is:
Take a look at the following query to retrieve GameScore objects:
$query = new ParseQuery("GameScore");
$query->equalTo("score", 50);
$query->containedIn("playerName",
["Jonathan Walsh", "Dario Wunsch", "Shawn Simon"]);
Creating an index query based on the score field would yield a smaller search space in general than creating one on the playerName
field.
When examining data types, booleans have a very low entropy and and do not make good indexes. Take the following query constraint:
$query->equalTo("cheatMode", false);
The two possible values for "cheatMode"
are true
and false
. If an index was added on this field it would be of little use because it’s likely that 50% of the records will have to be looked at to return query results.
Data types are ranked by their expected entropy of the value space for the key:
Even the best indexing strategy can be defeated by suboptimal queries.
Writing efficient queries means taking full advantage of indexes. Let’s take a look at some query constraints that negate the use of indexes:
Additionally, the following queries under certain scenarios may result in slow query responses if they can’t take advantage of indexes:
For example, let’s say you’re tracking high scores for a game in a GameScore class. Now say you want to retrieve the scores for all players except a certain one. You could create this query:
$query = new ParseQuery("GameScore");
$query->notEqualTo("playerName", "Michael Yabuti");
$gameScores = $query->find();
// Retrieved game scores
This query can’t take advantage of indexes. The database has to look at all the objects in the "GameScore"
class to satisfy the constraint and retrieve the results. As the number of entries in the class grows, the query takes longer to run.
Luckily, most of the time a “Not Equal To” query condition can be rewritten as a “Contained In” condition. Instead of querying for the absence of values, you ask for values which match the rest of the column values. Doing this allows the database to use an index and your queries will be faster.
For example if the User class has a column called state which has values “SignedUp”, “Verified”, and “Invited”, the slow way to find all users who have used the app at least once would be to run the query:
$query = new ParseQuery("_User");
$query->notEqualTo("state", "Invited");
It would be faster to use the “Contained In” condition when setting up the query:
$query->containedIn("state", ["SignedUp", "Verified"]);
Sometimes, you may have to completely rewrite your query. Going back to the "GameScore"
example, let’s say we were running that query to display players who had scored higher than the given player. We could do this differently, by first getting the given player’s high score and then using the following query:
$query = new ParseQuery("GameScore");
// Previously retrieved highScore for Michael Yabuti
$query->greaterThan("score", $highScore);
$gameScores = $query->find();
// Retrieved game scores
The new query you use depends on your use case. This may sometimes mean a redesign of your data model.
Similar to “Not Equal To”, the “Not Contained In” query constraint can’t use an index. You should try and use the complementary “Contained In” constraint. Building on the User example, if the state column had one more value, “Blocked”, to represent blocked users, a slow query to find active users would be:
$query = new ParseQuery("_User");
$query->notContainedIn("state", ["Invited", "Blocked"]);
Using a complimentary “Contained In” query constraint will always be faster:
$query->containedIn("state", ["SignedUp", "Verified"]);
This means rewriting your queries accordingly. Your query rewrites will depend on your schema set up. It may mean redoing that schema.
Regular expression queries should be avoided due to performance considerations. MongoDB is not efficient for doing partial string matching except for the special case where you only want a prefix match. Queries that have regular expression constraints are therefore very expensive, especially for classes with over 100,000 records. Consider restricting how many such operations can be run on a particular app at any given time.
You should avoid using regular expression constraints that don’t use indexes. For example, the following query looks for data with a given string in the "playerName"
field. The string search is case insensitive and therefore cannot be indexed:
$query->matches("playerName", "Michael", "i");
The following query, while case sensitive, looks for any occurrence of the string in the field and cannot be indexed:
$query->contains("playerName", "Michael");
These queries are both slow. In fact, the matches
and contains
query constraints are not covered in our querying guides on purpose and we do not recommend using them. Depending on your use case, you should switch to using the following constraint that uses an index, such as:
$query->startsWith("playerName", "Michael");
This looks for data that starts with the given string. This query will use the backend index, so it will be faster even for large datasets.
As a best practice, when you use regular expression constraints, you’ll want to ensure that other constraints in the query reduce the result set to the order of hundreds of objects to make the query efficient. If you must use the matches
or contains
constraints for legacy reasons, then use case sensitive, anchored queries where possible, for example:
$query->matches("playerName", "^Michael");
Most of the use cases around using regular expressions involve implementing search. A more performant way of implementing search is detailed later.
Writing restrictive queries allows you to return only the data that the client needs. This is critical in a mobile environment were data usage can be limited and network connectivity unreliable. You also want your mobile app to appear responsive and this is directly affected by the objects you send back to the client. The Querying section shows the types of constraints you can add to your existing queries to limit the data returned. When adding constraints, you want to pay attention and design efficient queries.
You can use skip and limit to page through results and load the data as is needed. The query limit is 100 by default:
$query->limit(10); // limit to at most 10 results
If you’re issuing queries on GeoPoints, make sure you specify a reasonable radius:
$query = new ParseQuery("Place");
$query->withinMiles("location", $userGeoPoint, 10);
$placeObjects = $query.find();
// Gets an array of objects within 10 miles of a user's location
You can further limit the fields returned by calling select:
$query = new ParseQuery("GameScore");
$query->select(["score", "playerName"]);
$results = $query->find();
// each of results will only have the selected fields available.
For queries run from iOS and Android, you can turn on query caching. See the iOS and Android guides for more details. Caching queries will increase your mobile app’s performance especially in cases where you want to display cached data while fetching the latest data from Parse.
Cloud Code allows you to run custom JavaScript logic on Parse Server instead of on the client.
You can use this to offload processing to the Parse servers thus increasing your app’s perceived performance. You can create hooks that run whenever an object is saved or deleted. This is useful if you want to validate or sanitize your data. You can also use Cloud Code to modify related objects or kick off other processes such as sending off a push notification.
We saw examples of limiting the data returned by writing restrictive queries. You can also use Cloud Functions to help limit the amount of data returned to your app. In the following example, we use a Cloud Function to get a movie’s average rating:
Parse.Cloud.define("averageStars", async (request) => {
const query = new Parse.Query("Review");
query.equalTo("movie", request.params.movie);
const results = await query.find();
let sum = 0;
for (let i = 0; i < results.length; ++i) {
sum += results[i].get("stars");
}
return sum / results.length;
});
You could have ran a query on the Review class on the client, returned only the stars field data and computed the result on the client. As the number of reviews for a movie increases you can see that the data being returned to the device using this methodology also increases. Implementing the functionality through a Cloud Function returns the one result if successful.
As you look at optimizing your queries, you’ll find that you may have to change the queries - sometimes even after you’ve shipped your app to the App Store or Google Play. The ability to change your queries without a client update is possible if you use Cloud Functions. Even if you have to redesign your schema, you could make all the changes in your Cloud Functions while keeping the client interface the same to avoid an app update. Take the average stars Cloud Function example from before, calling it from a client SDK would look like this:
$rating = ParseCloud::run("averageStars", ["movie" => "The Matrix" ]);
// rating is 4.5
If later on, you need to modify the underlying data model, your client call can remain the same, as long as you return back a number that represents the ratings result.
When counting objects frequently, instead consider storing a count variable in the database that is incremented each time an object is added. Then, the count can quickly be retrieved by simply retrieving the variable stored.
Suppose you are displaying movie information in your app and your data model consists of a Movie class and a Review class that contains a pointer to the corresponding movie. You might want to display the review count for each movie on the top-level navigation screen using a query like this:
$query = new ParseQuery("Review");
// $movieId corresponds to a given movie's id
$query->equalTo("movie", $movieId);
$count = $query.count();
If you run the count query for each of the UI elements, they will not run efficiently on large data sets. One approach to avoid using the count()
operator could be to add a field to the Movie class that represents the review count for that movie. When saving an entry to the Review class you could increment the corresponding movie’s review count field. This can be done in an afterSave
handler:
Parse.Cloud.afterSave("Review", function(request) {
// Get the movie id for the Review
var movieId = request.object.get("movie").id;
// Query the Movie represented by this review
var Movie = Parse.Object.extend("Movie");
var query = new Parse.Query(Movie);
query.get(movieId).then(function(movie) {
// Increment the reviews field on the Movie object
movie.increment("reviews");
movie.save();
}, function(error) {
throw "Got an error " + error.code + " : " + error.message;
});
});
Your new optimized query would not need to look at the Review class to get the review count:
$query = new ParseQuery("Movie");
$results = $query.find();
// Results include the reviews count field
You could also use a separate Parse Object to keep track of counts for each review. Whenever a review gets added or deleted, you can increment or decrement the counts in an afterSave
or afterDelete
Cloud Code handler. The approach you choose depends on your use case.
As mentioned previously, MongoDB is not efficient for doing partial string matching. However, this is an important use case when implementing search functionality that scales well in production.
Simplistic search algorithms simply scan through all the class data and executes the query on each entry. The key to making searches run efficiently is to minimize the number of data that has to be examined when executing each query by using an index as we’ve outlined earlier. You’ll need to build your data model in a way that it’s easy for us to build an index for the data you want to be searchable. For example, string matching queries that don’t match an exact prefix of the string won’t be able to use an index leading to timeout errors as the data set grows.
Let’s walk through an example of how you could build an efficient search. You can apply the concepts you learn in this example to your use case. Say your app has users making posts, and you want to be able to search those posts for hashtags or particular keywords. You’ll want to pre-process your posts and save the list of hashtags and words into array fields. You can do this processing either in your app before saving the posts, or you can use a Cloud Code beforeSave
hook to do this on the fly:
var _ = require("underscore");
Parse.Cloud.beforeSave("Post", request => {
var post = request.object;
var toLowerCase = function(w) { return w.toLowerCase(); };
var words = post.get("text").split(/\b/);
words = _.map(words, toLowerCase);
var stopWords = ["the", "in", "and"]
words = _.filter(words, function(w) {
return w.match(/^\w+$/) && ! _.contains(stopWords, w);
});
var hashtags = post.get("text").match(/#.+?\b/g);
hashtags = _.map(hashtags, toLowerCase);
post.set("words", words);
post.set("hashtags", hashtags);
});
This saves your words and hashtags in array fields, which MongoDB will store with a multi-key index. There are some important things to notice about this. First of all it’s converting all words to lower case so that we can look them up with lower case queries, and get case insensitive matching. Secondly, it’s filtering out common words like ‘the’, ‘in’, and ‘and’ which will occur in a lot of posts, to additionally reduce useless scanning of the index when executing the queries.
Once you’ve got the keywords set up, you can efficiently look them up using “All” constraint on your query:
$query = new ParseQuery("Post");
$query->containsAll("hashtags", ["#parse", "#ftw"]);
$posts = $query->find();
// posts containing all the given hash tags
There are some limits in place to ensure the API can provide the data you need in a performant manner. We may adjust these in the future. Please take a moment to read through the following list:
Objects
Queries
limit
parameter to change this.equalTo
constraints over the same key with two different values, which contradicts itself (perhaps you’re looking for ‘contains’).$exists: false
is not advised.each
query method in the JavaScript SDK cannot be used in conjunction with queries using geo-point constraints.containsAll
query constraint can only take up to 9 items in the comparison array.Push Notifications
Cloud Code
params
payload that is passed to a Cloud Function is limited to 50 MB.The following is a list of all the error codes that can be returned by the Parse API. You may also refer to RFC2616 for a list of http error codes. Make sure to check the error message for more details.
Name | Code | Description |
---|---|---|
UserInvalidLoginParams |
101 | Invalid login parameters. Check error message for more details. |
ObjectNotFound |
101 | The specified object or session doesn’t exist or could not be found. Can also indicate that you do not have the necessary permissions to read or write this object. Check error message for more details. |
InvalidQuery |
102 | There is a problem with the parameters used to construct this query. This could be an invalid field name or an invalid field type for a specific constraint. Check error message for more details. |
InvalidClassName |
103 | Missing or invalid classname. Classnames are case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the only valid characters. |
MissingObjectId |
104 | An unspecified object id. |
InvalidFieldName |
105 | An invalid field name. Keys are case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the only valid characters. Some field names may be reserved. Check error message for more details. |
InvalidPointer |
106 | A malformed pointer was used. You would typically only see this if you have modified a client SDK. |
InvalidJSON |
107 | Badly formed JSON was received upstream. This either indicates you have done something unusual with modifying how things encode to JSON, or the network is failing badly. Can also indicate an invalid utf-8 string or use of multiple form encoded values. Check error message for more details. |
CommandUnavailable |
108 | The feature you tried to access is only available internally for testing purposes. |
NotInitialized |
109 | You must call Parse.initialize before using the Parse library. Check the Quick Start guide for your platform. |
ObjectTooLarge |
116 | The object is too large. |
ExceededConfigParamsError |
116 | You have reached the limit of 100 config parameters. |
InvalidLimitError |
117 | An invalid value was set for the limit. Check error message for more details. |
InvalidSkipError |
118 | An invalid value was set for skip. Check error message for more details. |
OperationForbidden |
119 | The operation isn’t allowed for clients due to class-level permissions. Check error message for more details. |
CacheMiss |
120 | The result was not found in the cache. |
InvalidNestedKey |
121 | An invalid key was used in a nested JSONObject. Check error message for more details. |
InvalidACL |
123 | An invalid ACL was provided. |
InvalidEmailAddress |
125 | The email address was invalid. |
DuplicateValue |
137 | Unique field was given a value that is already taken. |
InvalidRoleName |
139 | Role’s name is invalid. |
ReservedValue |
139 | Field value is reserved. |
ExceededCollectionQuota |
140 | You have reached the quota on the number of classes in your app. Please delete some classes if you need to add a new class. |
ScriptFailed |
141 | Cloud Code script failed. Usually points to a JavaScript error. Check error message for more details. |
FunctionNotFound |
141 | Cloud function not found. Check that the specified Cloud function is present in your Cloud Code script and has been deployed. |
JobNotFound |
141 | Background job not found. Check that the specified job is present in your Cloud Code script and has been deployed. |
ValidationFailed |
142 | Cloud Code validation failed. |
WebhookError |
143 | Webhook error. |
InvalidImageData |
150 | Invalid image data. |
UnsavedFileError |
151 | An unsaved file. |
InvalidPushTimeError |
152 | An invalid push time was specified. |
HostingError |
158 | Hosting error. |
InvalidEventName |
160 | The provided analytics event name is invalid. |
ClassNotEmpty |
255 | Class is not empty and cannot be dropped. |
AppNameInvalid |
256 | App name is invalid. |
MissingAPIKeyError |
902 | The request is missing an API key. |
InvalidAPIKeyError |
903 | The request is using an invalid API key. |
Name | Code | Description |
---|---|---|
IncorrectType |
111 | A field was set to an inconsistent type. Check error message for more details. |
InvalidChannelName |
112 | Invalid channel name. A channel name is either an empty string (the broadcast channel) or contains only a-zA-Z0-9_ characters and starts with a letter. |
InvalidSubscriptionType |
113 | Bad subscription type. Check error message for more details. |
InvalidDeviceToken |
114 | The provided device token is invalid. |
PushMisconfigured |
115 | Push is misconfigured in your app. Check error message for more details. |
PushWhereAndChannels |
115 | Can’t set channels for a query-targeted push. You can fix this by moving the channels into your push query constraints. |
PushWhereAndType |
115 | Can’t set device type for a query-targeted push. You can fix this by incorporating the device type constraints into your push query. |
PushMissingData |
115 | Push is missing a ‘data’ field. |
PushMissingChannels |
115 | Non-query push is missing a ‘channels’ field. Fix by passing a ‘channels’ or ‘query’ field. |
ClientPushDisabled |
115 | Client-initiated push is not enabled. Check your Parse app’s push notification settings. |
RestPushDisabled |
115 | REST-initiated push is not enabled. Check your Parse app’s push notification settings. |
ClientPushWithURI |
115 | Client-initiated push cannot use the “uri” option. |
PushQueryOrPayloadTooLarge |
115 | Your push query or data payload is too large. Check error message for more details. |
InvalidExpirationError |
138 | Invalid expiration value. |
MissingPushIdError |
156 | A push id is missing. Deprecated. |
MissingDeviceTypeError |
157 | The device type field is missing. Deprecated. |
Name | Code | Description |
---|---|---|
InvalidFileName |
122 | An invalid filename was used for Parse File. A valid file name contains only a-zA-Z0-9_. characters and is between 1 and 128 characters. |
MissingContentType |
126 | Missing content type. |
MissingContentLength |
127 | Missing content length. |
InvalidContentLength |
128 | Invalid content length. |
FileTooLarge |
129 | File size exceeds maximum allowed. |
FileSaveError |
130 | Error saving a file. |
FileDeleteError |
153 | File could not be deleted. |
FileDeleteUnnamedError |
161 | Unnamed file could not be deleted. |
Name | Code | Description |
---|---|---|
InvalidInstallationIdError |
132 | Invalid installation id. |
InvalidDeviceTypeError |
133 | Invalid device type. |
InvalidChannelsArrayError |
134 | Invalid channels array value. |
MissingRequiredFieldError |
135 | Required field is missing. |
ChangedImmutableFieldError |
136 | An immutable field was changed. |
Name | Code | Description |
---|---|---|
ReceiptMissing |
143 | Product purchase receipt is missing. |
InvalidPurchaseReceipt |
144 | Product purchase receipt is invalid. |
PaymentDisabled |
145 | Payment is disabled on this device. |
InvalidProductIdentifier |
146 | The product identifier is invalid. |
ProductNotFoundInAppStore |
147 | The product is not found in the App Store. |
InvalidServerResponse |
148 | The Apple server response is not valid. |
ProductDownloadFilesystemError |
149 | The product fails to download due to file system error. |
Name | Code | Description |
---|---|---|
UsernameMissing |
200 | The username is missing or empty. |
PasswordMissing |
201 | The password is missing or empty. |
UsernameTaken |
202 | The username has already been taken. |
UserEmailTaken |
203 | Email has already been used. |
UserEmailMissing |
204 | The email is missing, and must be specified. |
UserWithEmailNotFound |
205 | A user with the specified email was not found. |
SessionMissing |
206 | A user object without a valid session could not be altered. |
MustCreateUserThroughSignup |
207 | A user can only be created through signup. |
AccountAlreadyLinked |
208 | An account being linked is already linked to another user. |
InvalidSessionToken |
209 | The device’s session token is no longer valid. The application should ask the user to log in again. |
Name | Code | Description |
---|---|---|
LinkedIdMissing |
250 | A user cannot be linked to an account because that account’s id could not be found. |
InvalidLinkedSession |
251 | A user with a linked (e.g. Facebook or Twitter) account has an invalid session. Check error message for more details. |
InvalidGeneralAuthData |
251 | Invalid auth data value used. |
BadAnonymousID |
251 | Anonymous id is not a valid lowercase UUID. |
FacebookBadToken |
251 | The supplied Facebook session token is expired or invalid. |
FacebookBadID |
251 | A user with a linked Facebook account has an invalid session. |
FacebookWrongAppID |
251 | Unacceptable Facebook application id. |
TwitterVerificationFailed |
251 | Twitter credential verification failed. |
TwitterWrongID |
251 | Submitted Twitter id does not match the id associated with the submitted access token. |
TwitterWrongScreenName |
251 | Submitted Twitter handle does not match the handle associated with the submitted access token. |
TwitterConnectFailure |
251 | Twitter credentials could not be verified due to problems accessing the Twitter API. |
UnsupportedService |
252 | A service being linked (e.g. Facebook or Twitter) is unsupported. Check error message for more details. |
UsernameSigninDisabled |
252 | Authentication by username and password is not supported for this application. Check your Parse app’s authentication settings. |
AnonymousSigninDisabled |
252 | Anonymous users are not supported for this application. Check your Parse app’s authentication settings. |
FacebookSigninDisabled |
252 | Authentication by Facebook is not supported for this application. Check your Parse app’s authentication settings. |
TwitterSigninDisabled |
252 | Authentication by Twitter is not supported for this application. Check your Parse app’s authentication settings. |
InvalidAuthDataError |
253 | An invalid authData value was passed. Check error message for more details. |
LinkingNotSupportedError |
999 | Linking to an external account not supported yet with signup_or_login. Use update instead. |
Name | Code | Description |
---|---|---|
ConnectionFailed |
100 | The connection to the Parse servers failed. |
AggregateError |
600 | There were multiple errors. Aggregate errors have an “errors” property, which is an array of error objects with more detail about each error that occurred. |
FileReadError |
601 | Unable to read input for a Parse File on the client. |
XDomainRequest |
602 | A real error code is unavailable because we had to use an XDomainRequest object to allow CORS requests in Internet Explorer, which strips the body from HTTP responses that have a non-2XX status code. |
Name | Code | Description |
---|---|---|
RequestTimeout |
124 | The request was slow and timed out. Typically this indicates that the request is too expensive to run. You may see this when a Cloud function did not finish before timing out, or when a Parse.Cloud.httpRequest connection times out. |
InefficientQueryError |
154 | An inefficient query was rejected by the server. Refer to the Performance Guide and slow query log. |
Name | Code | Description |
---|---|---|
OtherCause |
-1 | An unknown error or an error unrelated to Parse occurred. |
InternalServerError |
1 | Internal server error. No information available. |
ServiceUnavailable |
2 | The service is currently unavailable. |
ClientDisconnected |
4 | Connection failure. |
In order to provide better organization and avoid conflicts with Parse Platform’s built-in Parse.Error
codes, the following ranges are defined:
<= 4999
(including negative numbers)>= 5000 and <= 8999
>= 9000 and <= 9999
1
and add any specific information in the error message, or use another pre-defined error code of Parse Platform.