In this tutorial, we're going to create a dating application for iOS similar to Tinder. For voice and messaging, we will leverage the Sinch platform, making use of its powerful SDK.
In the first part, we will focus on the development of a RESTful API to store and retrieve user information. In the second part, the iOS client will hook into this API to find nearby users based on the user's current location.
We will use Laravel 5.0 for the RESTful service and will be covering basic concepts, such as routes and controllers. We are also going to define custom models to support MongoDB integration in an ActiveRecord-like manner. Let's get started.
I'm going to assume that you've already installed Composer and the latest Laravel installer. If you haven't, then follow the official Laravel 5.0 documentation. The installation process shouldn't take longer than a couple of minutes.
From the command line, navigate to the location where you want to create the application for the RESTful service and execute the following command:
laravel new mobilesinch
After a couple of seconds, the command will tell you that the mobilesinch application has been successfully created. Navigate to the new folder and execute the following command:
php artisan fresh
Any Laravel 5.0 application, by default, ships with some basic scaffolding for user registration and authentication. This command takes care of removing this since we want to start with a clean slate.
There's one other thing that we have to take care of before writing the actual code for our RESTful service. By default, Laravel 5.0 ships with a middleware for cross-site request forgery (CSRF) protection. However, since we are not building a website but a RESTful API, it makes no sense to have this in place. Also, it can cause some problems along the way.
For this application, it's best to remove it. In the root of the application folder, navigate to app/Http. Inside that folder, there's a file named Kernel.php. Open it and remove the following line:
'App\Http\Middleware\VerifyCsrfToken',
You can also remove WelcomeController.php, located inside app/Http/Controllers, as well as the default welcome.blade.php view inside the resources/views folder. We won't be using them, but you can leave them there if you want. Just make sure that you leave the 503.blade.php view in place as it's useful to debug the application.
Base ModelThe dating application this tutorial is aiming to create has a Tinder-like functionality in which you can find users near your current location. For this to work, the API needs to perform a search based on the user's location, known as a geospatial query. While we could do this with MySQL, the industry standard leans toward MongoDB, and I personally like it much more.
Instead of using Laravel's DB facade, we will create our own class that the application's models will extend to perform queries in MongoDB.
This will be a simple class and won't be integrated into Laravel's Eloquent model, even though we could, I'd like to keep it simple for the time being.
Before writing the class to perform MongoDB queries, we need to set up the database information, just as we would do for MySQL or PostgreSQL, or any other database server.
Inside the root config folder, create a new file and name it mongodb.php. Add the following code to it:
<?php
return [
'host' => 'localhost',
'port' => 27017,
'user' => '',
'pass' => '',
'db' => 'mobilesinch'
];We set the host and port for our MongoDB server, if any, set the user and password for the connection, and define the database that we will be using, mobilesinch.
Since MongoDB is a document-oriented database and it is schemaless, we need no further configuration, no migrations definition or anything else to structure the tables. It just works.
We have the configuration file in place and it's now time to create the actual class that will handle the interactions with the database.
This class will perform queries to MongoDB using ActiveRecord-like syntax. Inside the app/Http folder, create a new one, Models, and add a Base.php file inside it. Append the following code to it:
<?php namespace App\Http\Models;
use Illuminate\Support\Facades\Config;
class Base {
private $_config = null;
private $_conn = null;
private $_db = null;
public function __construct() {
$this->_config = Config::get( 'mongodb' );
$this->_connect();
}
private function _connect() {}
}These are the bare-bones for our Base model class. It does not extend from anything and only relies on Laravel's Config facade to retrieve the configuration parameters we created earlier.
Next, we need to create a connection with the database. Add the following code to the private _connect method:
$conn = 'mongodb://'.$this->_config['host'];
if( ! empty( $this->_config['port'] ) ) {
$conn .= ":{$this->_config['port']}";
}
$options = array();
if( ! empty( $this->_config['user'] ) && ! empty( $this->_config['pass'] ) ) {
$options['username'] = $this->_config['user'];
$options['password'] = $this->_config['pass'];
}
try {
$this->_conn = new \MongoClient( $conn, $options );
$this->_db = $this->_conn->{$this->_config['db']};
return true;
} catch( \MongoConnectionException $e ) {
$this->_conn = null;
return false;
}In this method, we create a connection string and set the username and password if any are given. We then use PHP's MongoDB driver to create a connection and set the database to the one specified in the configuration file.
If you are familiar with MongoDB syntax from the command line, this method is the equivalent to entering the mongo console and typing use mobilesinch. Refer to the official PHP MongoDB documentation for more information.
Before continuing with the database CRUD operations, there are some methods that our Base class must implement. These are used to set filters, select statements, and other query variables that are used to perform database operations. Let's start with adding the necessary member variables. Above the class constructor, add the following code:
private $_ws = array(); private $_sls = array(); private $_lmt = 99999; private $_ost = 0;
These are holders for the where, select, limit and offset of the database queries. To set these member variables, create the following setter methods:
protected function _limit( $limit, $offset = null ) {}
protected function _select( $select = "" ) {}
protected function _where( $key, $value = null ) {}The _limit method will be useful for paginating the results of a READ operation. The user can set the limit parameter to specify the number of records to retrieve and optionally an offset parameter to specify the page to read from. Add the following code to the _limit method:
if ( $limit !== NULL && is_numeric( $limit ) && $limit >= 1 ) {
$this->_lmt = $limit;
}
if ( $offset !== NULL && is_numeric( $offset ) && $offset >= 1 ) {
$this->_ost = $offset;
}The _select method will be used to determine which fields of a record a READ query must return. The select statement must be provided as a comma separated string.
$fields = explode( ',', $select );
foreach ( $fields as $field ) {
$this->_sls[trim( $field )] = true;
}Finally, the _where method will be used to filter the query results and can either be an array or a key/value pair.
if ( is_array( $key ) ) {
foreach( $key as $k => $v ) {
$this->_ws[$k] = $v;
}
} else {
$this->_ws[$key] = $value;
}We now have support in place to limit and filter queries, but we have to add some other helper methods. The first one will be used to combine any where statement set before issuing a query with the query's where parameter.
In a moment, when we write our CRUD methods, this will make more sense. At the bottom of the class, add the following private method:
private function _set_where( $where = null ) {
if ( is_array( $where ) ) {
$where = array_merge( $where, $this->_ws );
foreach ( $where as $k => $v ) {
if ( $k == "_id" && ( gettype( $v ) == "string" ) ) {
$this->_ws[$k] = new \MongoId( $v );
} else {
$this->_ws[$k] = $v;
}
}
} else if( is_string( $where ) ) {
$wheres = explode( ',', $where );
foreach ( $wheres as $wr ) {
$pair = explode( '=', trim( $wr ) );
if ( $pair[0] == "_id" ) {
$this->_ws[trim( $pair[0] )] = new \MongoId( trim( $pair[1] ) );
} else {
$this->_ws[trim( $pair[0] )] = trim( $pair[1] );
}
}
}
}It looks a little intimidating, but it's quite simple actually. It first checks if the where parameter is an array. If it is, it combines the given values with the existing ones using the _where helper method.
This method, however, also supports a string to set what is returned by a READ operation. This string should have the following format:
name=John,last_name=Smith
This example will run a query and return the fields where the name field is set to John and the last_name field is set to Smith.
Note, however, that for both an array or a string, we check if an _id field is present. If this is the case and it's a string, we create a new MongoId object from it. Ids are objects in MongoDB and comparing them to a string will return false, which is why this conversion is necessary.
Another thing we have to do, is reset all the query parameters once an operation has been performed so they won't affect subsequent queries. The _flush method will take care of this.
private function _flush() {
$this->_ws = array();
$this->_sls = array();
$this->_lmt = 99999;
$this->_ost = 0;
}We now have all the required functionality in place to filter and limit our query results. It's time for the actual database operations, which will rely on PHP's MongoDB driver. If you are uncertain about something, refer to the documentation.
The first operation that we are going to support is the one to create a record in the system. Add the following code to the Base class:
protected function _insert( $collection, $data ) {
if ( is_object( $data ) ) {
$data = ( array ) $data;
}
$result = false;
try {
if ( $this->_db->{$collection}->insert( $data ) ) {
$data['_id'] = ( string ) $data['_id'];
$result = ( object ) $data;
}
} catch( \MongoCursorException $e ) {
$result = new \stdClass();
$result->error = $e->getMessage();
}
$this->_flush();
return $result;
}Even though the PHP driver expects the inserted data to be an array, our class will support both arrays and objects. We first verify which is passed to us and cast it accordingly. We then attempt to insert the record into the database and return the inserted record as an object, including the _id.
We're going to implement two read methods, one will be used to retrieve a single record while the other will be used to fetch a list of records. Let's begin with the former.
protected function _findOne( $collection, $where = array() ) {
$this->_set_where( $where );
$row = $this->_db->{$collection}->findOne( $this->_ws, $this->_sls );
$this->_flush();
return ( object ) $row;
}We define the where clause for the query and use PHP's MongoDB driver to perform a findOne operation. We then flush the query parameters and return the record as an object.
PHP's MongoDB driver returns the result as an array while I personally prefer objects. That's the true reason for the cast.
Next, we implement the _find method to fetch a list of records.
protected function _find( $collection, $where = array() ) {
$this->_set_where( $where );
$docs = $this->_db->{$collection}
->find( $this->_ws, $this->_sls )
->limit( $this->_lmt )
->skip( $this->_ost );
$this->_flush();
$result = array();
foreach( $docs as $row ) {
$result[] = ( object ) $row;
}
return $result;
}In the _find method, we use the driver's find method, setting the query limit and skip parameters to support pagination.
This method, however, returns a MongoCursor object, which we then iterate over to obtain the actual records. As before, we cast each record as an object, appending it to the result array.
We already have support to create and read records from the database. We now need to be able to edit those records and add or modify a record's data. Create a new method _update and implement it as follows:
protected function _update( $collection, $data, $where = array() ) {
if ( is_object( $data ) ) {
$data = ( array ) $data;
}
$this->_set_where( $where );
if ( array_key_exists( '$set', $data ) ) {
$newdoc = $data;
} else {
$newdoc = array( '$set' => $data );
}
$result = false;
try {
if( $this->_db->{$collection}->update( $this->_ws, $newdoc ) ) {
$result = ( object ) $data;
}
} catch( \MongoCursorException $e ) {
$result = new \stdClass();
$result->error = $e->getMessage();
}
$this->_flush();
return $result;
}As with the CREATE operation, we support both arrays and objects, which means we check and cast accordingly. We combine the where clauses passed to the method using the helper method. The rest is no different than the already created _insert method.
There is a special thing to note though. When we update a MongoDB document and pass the data to the _update method, the document will be replaced. If we are only updating one field and pass the data for that field, the document will become that field. This is why we need to create an array with the $set key and the added information. The result is that our record won't be replaced with the new information.
Finally, the driver must support the DELETE operation to remove documents from the database.
protected function _remove( $collection, $where = array() ) {
$this->_set_where( $where );
$result = false;
try {
if ( $this->_db->{$collection}->remove( $this->_ws ) ) {
$result = true;
}
} catch( \MongoCursorException $e ) {
$result = new \stdClass();
$result->error = $e->getMessage();
}
$this->_flush();
return $result;
}As before, we set the where clause for the delete operation and rely on PHP's MongoDB driver to perform a remove operation on the database.
And that's it for our Base model. It's a lot of code, but we can now perform operations in MongoDB for the models that inherit from the Base class.
Session ModelThe Session model will be in charge of creating, removing, and finding a session in the database. Create a new file inside the application's Models folder, name it Session.php, and add the following code to it:
<?php namespace App\Http\Models;
class Session extends Base {
private $_col = "sessions";
public function create( $user ) {
$this->_where( 'user_id', ( string ) $user->_id );
$existing = $this->_findOne( $this->_col );
if ( !empty( ( array ) $existing ) ) {
$this->_where( 'user_id', ( string ) $user->_id );
$this->_remove( $this->_col );
}
$session = new \stdClass();
$session->user_id = ( string ) $user->_id;
$session->user_name = $user->name;
$session = $this->_insert( $this->_col, $session );
return $session;
}
public function find( $token ) {
$this->_where( '_id', $token );
return $this->_findOne( $this->_col );
}
public function remove( $token ) {
$this->_where( '_id', $token );
return $this->_remove( $this->_col );
}
}This model extends from the Base class we created earlier to support MongoDB operations. It also sets the collection to be used to sessions.
The create method is used to create a user session record. Before attempting to create it, the method verifies if the user already has an active session. If it does, it removes it from the database and creates the new record with the passed in user information.
The find method is used to retrieve a session record from the database using a session token. Note that it simply sets the where clause for the query and delegates the task of finding the record to the _findOne method of the Base class.
To end a user session, we implement the remove method. Using the session token, it delegates the heavy lifting to the _remove method of the Base class. Note that the model makes no check for the session token that's passed in. This should be handled by the controller. The only concern for the model is data manipulation.
User ModelThe other model that our REST API needs is one to handle user related interactions. Inside the application's Models folder, create a new User.php file, and add the following code to it:
<?php namespace App\Http\Models;
use App\Http\Models\Base as Model;
class User extends Model {
private $_col = "users";
private $_error = null;
public function get( $where ) {}
public function get_error() {}
public function create( $user ) {}
public function remove( $id ) {}
public function retrieve( $id, $distance, $limit = 9999, $page = 1 ) {}
public function update( $id, $data ) {}
}The User model is a little more complicated. Let's begin with the methods to retrieve users. The get method will be in charge of retrieving a single record, using the user's id. Add the following code to the get method:
if ( is_array( $where ) ) {
return $this->_findOne( $this->_col, $where );
} else {
$this->_where( '_id', $where );
return $this->_findOne( $this->_col );
}We are assuming that in the case the where parameter isn't an array, it's the user's id. The get method then delegates the task of finding the record to the _findOne method of the Base class.
The get_error method is a helper method that will give the controller more information about failure in the model.
return $this->_error;
The last read operation in the User model is the one for the retrieve method. This will fetch a list of users. Add the following code to the retrieve method:
if ( !empty( $id ) && !empty( $distance ) ) {
$this->_where( '_id', $id );
$this->_select( 'location' );
$user = $this->_findOne( $this->_col );
if ( empty( ( array ) $user ) ) {
$this->_error = "ERROR_INVALID_USER";
return false;
}
$this->_where( '$and', array(
array(
'_id' => array( '$ne' => new \MongoId( $id ) )
),
array(
'location' => array(
'$nearSphere' => array(
'$geometry' => array(
'type' => "Point",
'coordinates' => $user->location['coordinates']
),
'$maxDistance' => ( float ) $distance
)
)
)
) );
}
$this->_limit( $limit, ( $limit * --$page ) );
return $this->_find( $this->_col );This method supports pagination and geospatial queries. If the id and distance parameters are passed in, it attempts to search for nearby users based on the user's location.
If the id does not match any record, it returns false. If the user does exist, it prepares a geospatial query using a MongoDB 2dsphere index.
Note that we are also setting the query not to return the user matching the _id of the user performing the search. Finally, it sets the query limit and offset parameters, delegating the task to the _find method of the Base class.
To remove users, we need to implement the remove method. Add the following code to the remove method:
$this->_where( '_id', $id );
$user = $this->_findOne( $this->_col );
if ( empty( ( array ) $user ) ) {
$this->_error = "ERROR_INVALID_ID";
return false;
} else {
$this->_where( '_id', $id );
if ( !$this->_remove( $this->_col ) ) {
$this->_error = "ERROR_REMOVING_USER";
return false;
}
}
return $user;We check that the given _id corresponds to an existing user and attempt to remove it using the _remove method of the Base class. If something went wrong, we set the model's _error property and return false.
Another operation our model should support is the creation of user records. Add the following code to the create method:
if ( is_array( $user ) ) {
$user = ( object ) $user;
}
$this->_where( '$or', array(
array(
"email" => $user->email
),
array(
"mobile" => $user->mobile
)
)
);
$existing = $this->_findOne( $this->_col );
if ( empty( ( array ) $existing ) ) {
$user = $this->_insert( $this->_col, $user );
} else {
$user = $existing;
}
$user->_id = ( string ) $user->_id;
return $user;In this method, we make sure there isn't already a user associated with the given email or mobile. If that's true, we return the corresponding user. If it isn't, we delegate the task to create a user to the _insert method of the Base class.
Before we return the user record, we cast the _id to a string. Why is that? The object that's returned to us defines the _id field as a MongoId object. The client application, however, doesn't need this object.
The User model also needs to support updating user records. Add the following code to the update method:
if ( is_array( $data ) ) {
$data = ( object ) $data;
}
if ( isset( $data->email ) || isset( $data->mobile ) ) {
$this->_where( '$and', array(
array(
'_id' => array( '$ne' => new \MongoId( $id ) )
),
array(
'$or' => array(
array(
'email' => ( isset( $data->email ) ) ? $data->email : ""
),
array(
'mobile' => ( isset( $data->mobile ) ) ? $data->mobile : ""
)
)
)
)
);
$existing = $this->_findOne( $this->_col );
if ( !empty( ( array ) $existing ) && $existing->_id != $id ) {
$this->_error = "ERROR_EXISTING_USER";
return false;
}
}
$this->_where( '_id', $id );
return $this->_update( $this->_col, ( array ) $data );As in the Base class, the update method accepts both arrays and objects as the data for the user. This make the method much more flexible.
Before we update the user record, we make sure that the user's email and mobile aren't already in use by another user. If that's the case, we set the error to EXISTING_USER and return false. Otherwise, we delegate the update operation to the Base class.
BaseController ClassJust like the application's models inherit from a Base class, the controllers also inherit from a common parent class, other than Laravel's Controller class. This BaseController class won't be anywhere near the complexity of the Base model though.
The class will only be used to handle a few simple tasks. To create the class, we use Laravel's artisan command. From the command line, navigate to the root of your application and execute the following command:
php artisan make:controller BaseController --plain
This will create a file named BaseController.php inside the application's Controllers folder in the app/Http folder. Since we are using the --plain flag, the controller will not have any methods, which is what we want.
This controller won't be using the Request class so you can go ahead and remove the following line:
use Illuminate\Http\Request;
Because we need access to the Session model, add the following line to the declaration of the BaseController class:
use App\Http\Models\Session as SessionModel;
We're now ready to implement the methods of the BaseController class. Start by adding the following methods inside the class declaration:
protected function _check_session( $token = "", $id = "" ) {
$result = false;
if ( !empty( $token ) ) {
$SessionModel = new SessionModel();
$session = $SessionModel->find( $token );
if ( !empty( ( array ) $session ) ) {
if ( !empty( $id ) ) {
if ( $session->user_id == $id ) {
$result = $session;
}
} else {
$result = $session;
}
}
}
return $result;
}
protected function _response( $result ) {
if ( is_object( $result ) && property_exists( $result, "status" ) ) {
return response()->json( $result, $result->status );
} else {
return response()->json( $result );
}
}The _check_session method is used to verify the session token, which is passed as the first argument. Some tasks in the application require the user to be logged in. For instance, when updating a user record, the user corresponding with the active session needs to match the _id of the record that needs to be updated.
The implementation is pretty straightforward. We fetch the session for the session token and, if the id of the user that corresponds with the session matches the id that is passed in as the second argument, we return the session. Otherwise, we return false.
The other helper method takes care of sending a result back to the client that consumes the API. At the moment, we only support JSON. If the result to return is an object and has a status parameter, we set it using Laravel's response helper method. Otherwise, we simply return the result.
SessionController ClassThe next controller we'll implement is the one that handles requests for the Sessions resource. From the command line, navigate to the root of your application and execute the following command:
php artisan make:controller SessionController --plain
This will create a new file named SessionController.php inside the application's Controllers folder in the app/Http folder. Before we implement this class, we need to take care of a few things.
The SessionController class currently inherits from Laravel's Controller class. We need to set this to use our BaseController class. This means we need to replace
use App\Http\Controllers\Controller;
with
use App\Http\Controllers\BaseController;
We also need to change the extends clause of the class. Instead of extending from Controller, make sure that your class is extending the BaseController class. We also need to include the models used in the controller. Below the last declaration, add the following lines:
use App\Http\Models\Session as SessionModel; use App\Http\Models\User as UserModel;
Normally, we would just use the SessionModel, but you'll see why we are also using the UserModel in just a moment. As for the controller class itself, add the following code:
private $_model = null;
public function __construct() {
$this->_model = new SessionModel();
}
public function create( Request $request ) {}
public function destroy( $token ) {}We set the controller's model object in the constructor and declare a couple of methods, which are the actions supported by the Sessions resource.
For the removal of a user session, we simply use the session token, which is given as a parameter in the resource URL. We will declare this later in the application routes. Inside the destroy method, add the following code:
$result = new \stdClass();
if ( !$this->_model->remove( $token ) ) {
$result->error = "ERROR_REMOVING_SESSION";
$result->status = 403;
}
return $this->_response( $result );The method uses the SessionModel's remove method and returns the result using the _response method of the BaseController class. If removing the session is successful, we return an empty object. If an error occurred, we return an error with a 403 status code.
The method for creating a session is a little more complicated. Note that in the method declaration we are using Laravel's Request object. We use this object to access the POST parameters of the request. Inside the create method, add the following code:
$email = $request->get( 'email' );
$mobile = $request->get( 'mobile' );
$fbId = $request->get( 'fbId' );
$result = new \stdClass();
if ( ( empty( $email ) && empty( $mobile ) ) || empty( $fbId ) ) {
$result->error = "ERROR_INVALID_PARAMETERS";
$result->status = 403;
} else {}
return $this->_response( $result );We haven't created the session object yet, because there's something we need to discuss first. The application is going to be using Facebook Login only. From the Facebook SDK, we obtain the user's information when performing a login operation. In the API's Session resource POST handler, we need to support two things:
This is also the reason for including the UserModel in the controller. In the above empty else clause, add the following code:
$UserModel = new UserModel();
$where = ( !empty( $email ) ) ? array( 'email' => $email ) : array( 'mobile' => $mobile );
$user = $UserModel->get( $where );
if ( empty( ( array ) $user ) ) {
} else {
if ( $fbId != $user->fbId ) {
$result->error = "ERROR_INVALID_CREDENTIALS";
$result->status = 403;
}
}
if ( !property_exists( $result, "error" ) ) {
$result = $this->_model->create( $user );
$result->token = $result->_id;
unset( $result->_id );
}We first check for an existing user with the passed in email or mobile. If the user exists, we verify that the given Facebook ID matches the Facebook ID for the user record. If that's the case, we create the session object. If it isn't, the method returns a INVALID_CREDENTIALS error with a 403 status code.
Starting a session is now complete. Note that this isn't extra secure. However, for the purposes of this tutorial, it will work just fine.
For the case when there is no user associated with the passed in email or mobile, we want to create a new record. In the above empty if clause, add the following code:
name = $request->get( 'name' );
$gender = $request->get( 'gender' );
$location = $request->get( 'location' );
if ( empty( $name ) || empty( ( array ) $location ) || empty( $gender ) ) {
$result->error = "ERROR_INVALID_PARAMETERS";
$result->status = 403;
} else {
if ( gettype( $location ) == "string" ) {
$location = json_decode( $location );
}
$locObj = new \stdClass();
$locObj->type = "Point";
$locObj->coordinates = array( $location->lon, $location->lat );
$user->name = $name;
$user->fbId = $fbId;
$user->email = $email;
$user->mobile = $mobile;
$user->gender = $gender;
$user->location = $locObj;
$user = $UserModel->create( $user );
}We first retrieve the rest of the required parameters from the request and then check if the location parameter is given as a JSON object or an encoded JSON object (string). The method is expecting this parameter to be in the following format:
{
"lat" : 37.427208696456866,
"lon" : -122.17097282409668
}We then transform this location into a MongoDB 2dSphere location. To execute geospatial queries, this field needs to have the following format:
{
"type" : "Point"
"coordinates" : [ -122.17097282409668, 37.427208696456866 ]
}We could ask the client to send the location in this format. However, it is better that we don't burden the client with reformatting the user location since this is specific to our implementation.
After setting the location object, we check that the user required parameters exist and, if that's the case, we create a new user object using the create method of the UserModel class.
That's it. Even though we could start a session by sending only the email and fbId parameters or the mobile and fbId parameters, if the rest of the user information is given, our handler will take care of creating a new user when necessary and starting a session.
UserController ClassThe last controller that the application needs is the one in charge of handling the Users resource. Once again, we use Laravel's artisan command. From the command line, navigate to the root of your application and execute the following command:
php artisan make:controller UserController --plain
This will create a UserController.php file inside the application's Controllers folder in the app/Http folder. As with the SessionController class, make sure that the UserController class inherits from BaseController and that it includes the UserModel class. Inside the class declaration, add the following code:
private $_model = null;
public function __construct() {
$this->_model = new UserModel();
}
public function create( Request $request ) {}
public function get( Request $request, $id ) {}
public function remove( Request $request, $id ) {}
public function retrieve( Request $request ) {}
public function update( Request $request, $id ) {}As with the SessionController class, we initialize the model object and declare the methods that will be supported by the Users resource. Let's begin with the ones for GET operations. In the get method, add the following code:
$token = $request->get( 'token' );
$result = new \stdClass();
if ( !$this->_check_session( $token ) ) {
$result->error = "PERMISSION_DENIED";
$result->status = 403;
} else {
$result = $this->_model->get( $id );
}
return $this->_response( $result );To retrieve a record from the system, we require that the user has an active session. It doesn't have to match the id of the retrieved user. If the user doesn't have a valid session, we return a PERMISSION_DENIED error with a 403 status code. Otherwise we return the user record as a JSON object.
Next, for a list of users, we need to implement the retrieve method. Add the following code to the retrieve method:
$token = $request->get( 'token' );
$distance = $request->get( 'distance' );
$session = $this->_check_session( $token );
$result = $this->_model->retrieve( ( isset( $session->user_id ) ? $session->user_id : "" ), $distance, $request->get( 'limit' ), $request->get( 'page' ) );
if ( !is_array( $result ) && !$result ) {
$result = new \stdClass();
$result->error = $this->_model->get_error();
$result->status = 403;
}
return $this->_response( $result );We start by fetching the request parameters, the user's session token and distance parameters in particular. This method, however, does not requires an active session. If a session is valid, we pass the user id to the retrieve method of the UserModel class.
If a distance parameter is passed in, a geospatial query is executed. If not, a regular find query is performed. In case of errors, we retrieve the error from the model and return it to the user with a 403 status code. Otherwise, we return an array containing the found users.
User creation will map to the Users resource POST operation. Add the following code to the create method:
$email = $request->get( 'email' );
$fbId = $request->get( 'fbId' );
$gender = $request->get( 'gender' );
$location = $request->get( 'location' );
$mobile = $request->get( 'mobile' );
$name = $request->get( 'name' );
if ( gettype( $location ) == "string" ) {
$location = json_decode( $location );
}
$locObj = new \stdClass();
$locObj->type = "Point";
$locObj->coordinates = array( $location->lon, $location->lat );
$result = new \stdClass();
if ( empty( $name ) || empty( ( array ) $location ) || empty( $fbId ) || empty( $gender ) || ( empty( $email ) && empty( $mobile ) ) ) {
$result->error = "ERROR_INVALID_PARAMETERS";
$result->status = 403;
} else {
$user = array(
"email" => $email,
"fbId" => $fbId,
"gender" => $gender,
"location" => $locObj,
"mobile" => $mobile,
"name" => $name
);
$result = $this->_model->create( $user );
}
return $this->_response( $result );We first retrieve the necessary information for the user and, as in the session creation handler, we convert the user's location to the appropriate format.
After this, we check that the required information is passed in. Note that even though both the email and mobile fields are optional, at least one must be present.
After these checks, we invoke the create method of the UserModel class to insert the new user in the database. Finally, we return the new user or an error.
To remove a user, we need to implement the remove method. Add the following code to the remove method:
$token = $request->get( 'token' );
$result = new \stdClass();
if ( !$this->_check_session( $token, $id ) ) {
$result->error = "PERMISSION_DENIED";
$result->status = 403;
} else {
$result = $this->_model->remove( $id );
if ( !$result ) {
$result = new \stdClass();
$result->error = $this->_model->get_error();
$result->status = 403;
}
}
return $this->_response( $result );This is one of those methods in which we want the _id of the user to be removed to match the _id of the user with the active session. This is the first thing we verify. If that's the case, we delegate to the model's remove method. Otherwise, we set the error to PERMISSION_DENIED and send the result back to the user.
Finally, let's implement the user's update operation. Inside the update method, add the following code:
$token = $request->get( 'token' );
$data = new \stdClass();
if ( !empty( $email = $request->get( 'email' ) ) ) {
$data->email = $email;
}
if ( !empty( $fbId = $request->get( 'fbId' ) ) ) {
$data->fbId = $fbId;
}
if ( !empty( $gender = $request->get( 'gender' ) ) ) {
$data->gender = $gender;
}
if ( !empty( $location = $request->get( 'location' ) ) ) {
if ( gettype( $location ) == "string" ) {
$location = json_decode( $location );
}
$locObj = new \stdClass();
$locObj->type = "Point";
$locObj->coordinates = array( $location->lon, $location->lat );
$data->location = $locObj;
}
if ( !empty( $mobile = $request->get( 'mobile' ) ) ) {
$data->mobile = $mobile;
}
if ( !empty( $name = $request->get( 'name' ) ) ) {
$data->name = $name;
}
$result = new \stdClass();
if ( !$this->_check_session( $token, $id ) ) {
$result->error = "PERMISSION_DENIED";
$result->status = 403;
} else {
$result = $this->_model->update( $id, $data );
if ( !$result ) {
$result = new \stdClass();
$result->error = $this->_model->get_error();
$result->status = 403;
}
}
return $this->_response( $result );We validate the data that's passed in and set the appropriate object for what to update. In the case of the location parameter, we reformat it first.
Again, this method should only be accessible by users with an active session that corresponds to their own _id. This means that we first check that's the case.
We then invoke the update method of the UserModel class and return the result to the client.
With that last piece of code our API is complete. We have our controllers and models in place. The last thing we have to do is map incoming requests to the appropriate endpoints.
For that, we need to edit our application's routes.php file. It's located inside the app/Http folder. If you open it, you should see something like this:
Route::get( '/', 'WelcomeController@index' );
When the application receives a GET request without any resource specified, the index method of the WelcomeController class should handle it. However, you probably already removed the WelcomeController at the start of this tutorial. If you try to navigate to this endpoint in a browser, you will get an error. Let's replace that last line with the following code:
Route::post( 'sessions', 'SessionController@create' );
Route::delete( 'sessions/{token}', 'SessionController@destroy' );
Route::delete( 'users/{id}', 'UserController@remove' );
Route::get( 'users', 'UserController@retrieve' );
Route::get( 'users/{id}', 'UserController@get' );
Route::post( 'users', 'UserController@create' );
Route::put( 'users/{id}', 'UserController@update' );We map API requests to the methods previously added to our controllers. For instance, the following call
[ DELETE ] - http://YOUR_API_URL/sessions/abc
translates to a DELETE request to the given URL. This means that in the SessionController the delete method will be called with a token of abc.
That's it for the RESTful API using Laravel 5.0. We have support for user and session management, which is exactly what we need to implement the iOS client.
In the next part of this tutorial, Jordan will be showing you how to integrate this API in an iOS application. He will also show you how to integrate the Sinch SDK for messaging and voice calls.
The Best Small Business Web Designs by DesignRush
/
2Create Modern Vue Apps Using Create-Vue and Vite
/
8Pros and Cons of Using WordPress
/
-1How to Fix the “There Has Been a Critical Error in Your Website” Error in WordPress
/
2How To Fix The “There Has Been A Critical Error in Your Website” Error in WordPress
How to Create a Privacy Policy Page in WordPress
/
8WordPress Website Maintenance Guide For Beginners
/
8How Long Does it Take to Learn JavaScript
/
1The Best Way to Deep Copy an Object in JavaScript
/
-3
4
-5Adding and Removing Elements From Arrays in JavaScript
/
-2Create a JavaScript AJAX Post Request: With and Without jQuery
/
95 Real-Life Uses for the JavaScript reduce() Method
/
-8How to Enable or Disable a Button With JavaScript: jQuery vs. Vanilla
/
4How to Enable or Disable a Button With JavaScript: jQuery vs Vanilla
/
-3Confirm Yes or No With JavaScript
/
-4How to Change the URL in JavaScript: Redirecting
/
-127 Best Tab and Accordion Widget Plugins for WordPress (Free & Premium)
/
1221 Best Tab and Accordion Widget Plugins for WordPress (Free & Premium)
/
-1130 HTML Best Practices for Beginners
/
-731 Best WordPress Calendar Plugins and Widgets (With 5 Free Plugins)
/
325 Ridiculously Impressive HTML5 Canvas Experiments
/
-4
1How to Implement Email Verification for New Members
/
3How to Create a Simple Web-Based Chat Application
/
730 Popular WordPress User Interface Elements
/
-3Top 18 Best Practices for Writing Super Readable Code
/
1Best Affiliate WooCommerce Plugins Compared
/
-1118 Best WordPress Star Rating Plugins
/
1510+ Best WordPress Twitter Widgets
/
220+ Best WordPress Booking and Reservation Plugins
/
5Working With Tables in React: Part Two
/
-2Best CSS Animations and Effects on CodeCanyon
/
-1730 CSS Best Practices for Beginners
/
4How to Create a Custom WordPress Plugin From Scratch
/
310 Best Responsive HTML5 Sliders for Images and Text… and 3 Free Options
/
-616 Best Tab and Accordion Widget Plugins for WordPress
/
-718 Best WordPress Membership Plugins and 5 Free Plugins
/
325 Best WooCommerce Plugins for Products, Pricing, Payments and More
/
210 Best WordPress Twitter Widgets
1 /
1112 Best Contact Form PHP Scripts for 2020
/
-220 Popular WordPress User Interface Elements
/
-410 Best WordPress Star Rating Plugins
/
-412 Best CSS Animations on CodeCanyon
/
-212 Best WordPress Booking and Reservation Plugins
/
120 Best WordPress Calendar Plugins and Widgets
/
-312 Elegant CSS Pricing Tables for Your Latest Web Project
/
824 Best WordPress Form Plugins for 2020
/
-2Getting Started With Django: Newly Updated Course
/
-2Create a Blog for Each Category or Department in Your WooCommerce Store
/
38 Best WordPress Booking and Reservation Plugins
/
-15Best Exit Popups for WordPress Compared
/
19Best Exit Popups for WordPress Compared
/
-311 Best Tab & Accordion WordPress Widgets & Plugins
/
-1012 Best Tab & Accordion WordPress Widgets & Plugins
1New Course: Practical React Fundamentals
/
2Short Course: Better Angular App Architecture With Modules
/
3Preview Our New Course on Angular Material
/
4Build Your Own CAPTCHA and Contact Form in PHP
/
-3Object-Oriented PHP With Classes and Objects
/
1Best Practices for ARIA Implementation
/
-5Accessible Apps: Barriers to Access and Getting Started With Accessibility
/
-4Dramatically Speed Up Your React Front-End App Using Lazy Loading
/
1715 Best Modern JavaScript Admin Templates for React, Angular, and Vue.js
/
-1615 Best Modern JavaScript Admin Templates for React, Angular and Vue.js
/
319 Best JavaScript Admin Templates for React, Angular, and Vue.js
/
3New Course: Build an App With JavaScript and the MEAN Stack
/
10Hands-on With ARIA: Accessibility Recipes for Web Apps
/
610 Best WordPress Facebook Widgets
13Hands-on With ARIA: Accessibility for eCommerce
/
6New eBooks Available for Subscribers
/
-9Hands-on With ARIA: Homepage Elements and Standard Navigation
/
11
-5Site Accessibility: Getting Started With ARIA
/
5How Secure Are Your JavaScript Open-Source Dependencies?
/
-4New Course: Secure Your WordPress Site With SSL
/
-3Testing Components in React Using Jest and Enzyme
/
-8Testing Components in React Using Jest: The Basics
/
-515 Best PHP Event Calendar and Booking Scripts
/
-3Create Interactive Gradient Animations Using Granim.js
/
11How to Build Complex, Large-Scale Vue.js Apps With Vuex
1 /
-2Examples of Dependency Injection in PHP With Symfony Components
/
-5Set Up Routing in PHP Applications Using the Symfony Routing Component
1 /
8A Beginner’s Guide to Regular Expressions in JavaScript
/
9Introduction to Popmotion: Custom Animation Scrubber
/
-6Introduction to Popmotion: Pointers and Physics
/
-10New Course: Connect to a Database With Laravel’s Eloquent ORM
/
-6How to Create a Custom Settings Panel in WooCommerce
/
-15Building the DOM faster: speculative parsing, async, defer and preload
1 /
420 Useful PHP Scripts Available on CodeCanyon
3 /
51Introduction to the Stimulus Framework
/
-12Single-Page React Applications With the React-Router and React-Transition-Group Modules
612 Best Contact Form PHP Scripts
1 /
-3Getting Started With the Mojs Animation Library: The ShapeSwirl and Stagger Modules
/
1Getting Started With the Mojs Animation Library: The Shape Module
/
-2Getting Started With the Mojs Animation Library: The HTML Module
/
5Project Management Considerations for Your WordPress Project
/
-88 Things That Make Jest the Best React Testing Framework
/
6Creating an Image Editor Using CamanJS: Layers, Blend Modes, and Events
/
1New Short Course: Code a Front-End App With GraphQL and React
/
1Creating an Image Editor Using CamanJS: Applying Basic Filters
/
-12Creating an Image Editor Using CamanJS: Creating Custom Filters and Blend Modes
/
-1Modern Web Scraping With BeautifulSoup and Selenium
/
1Challenge: Create a To-Do List in React
1 /
-2Deploy PHP Web Applications Using Laravel Forge
/
-3Getting Started With the Mojs Animation Library: The Burst Module
/
4
-910 Things Men Can Do to Support Women in Tech
/
-2A Gentle Introduction to Higher-Order Components in React: Best Practices
/
-5A Gentle Introduction to HOC in React: Learn by Example
/
3A Gentle Introduction to Higher-Order Components in React
/
-6Creating Pretty Popup Messages Using SweetAlert2
/
7Creating Stylish and Responsive Progress Bars Using ProgressBar.js
/
1618 Best Contact Form PHP Scripts for 2022
/
-2How to Make a Real-Time Sports Application Using Node.js
/
-2Creating a Blogging App Using Angular & MongoDB: Delete Post
/
-17Set Up an OAuth2 Server Using Passport in Laravel
/
1Creating a Blogging App Using Angular & MongoDB: Edit Post
/
1Introduction to Mocking in Python
/
1Creating a Blogging App Using Angular & MongoDB: Show Post
/
10Creating a Blogging App Using Angular & MongoDB: Home
/
6
3Creating a Blogging App Using Angular & MongoDB: Login
/
12Creating Your First Angular App: Implement Routing
/
12Persisted WordPress Admin Notices: Part 4
/
-3Creating Your First Angular App: Components, Part 2
/
3Persisted WordPress Admin Notices: Part 3
/
4How Laravel Broadcasting Works
/
-1
7Persisted WordPress Admin Notices: Part 2
/
-2Create Your First Angular App: Storing and Accessing Data
/
6Persisted WordPress Admin Notices: Part 1
/
-1Error and Performance Monitoring for Web & Mobile Apps Using Raygun
/
6Using Luxon for Date and Time in JavaScript
7 /
-3How to Create an Audio Oscillator With the Web Audio API
/
-7How to Cache Using Redis in Django Applications
/
220 Essential WordPress Utilities to Manage Your Site
/
-6Introduction to API Calls With React and Axios
/
15Beginner’s Guide to Angular 4: HTTP
/
13Rapid Web Deployment for Laravel With GitHub, Linode, and RunCloud.io
/
8Beginners Guide to Angular 4: Routing
/
-10Beginner’s Guide to Angular 4: Services
/
1Beginner’s Guide to Angular 4: Components
/
1Creating a Drop-Down Menu for Mobile Pages
/
9Introduction to Forms in Angular 4: Writing Custom Form Validators
/
6Getting Started With Redux: Connecting Redux With React
/
15Getting Started With Redux: Learn by Example
/
14Getting Started With Redux: Why Redux?
/
11How to Auto Update WordPress Salts
/
-4How to Download Files in Python
/
-2
-20Eloquent Mutators and Accessors in Laravel
1 /
15
-11
1310 Best HTML5 Sliders for Images and Text
/
8Site Authentication in Node.js: User Signup
/
-1Creating a Task Manager App Using Ionic: Part 2
/
2Creating a Task Manager App Using Ionic: Part 1
/
1Introduction to Forms in Angular 4: Reactive Forms
/
2
4Introduction to Forms in Angular 4: Template-Driven Forms
/
424 Essential WordPress Utilities to Manage Your Site
/
-125 Essential WordPress Utilities to Manage Your Site
/
-10Get Rid of Bugs Quickly Using BugReplay
1 /
-16Manipulating HTML5 Canvas Using Konva: Part 1, Getting Started
/
610 Must-See Easy Digital Downloads Extensions for Your WordPress Site
/
-222 Best WordPress Booking and Reservation Plugins
/
-5Understanding ExpressJS Routing
/
-615 Best WordPress Star Rating Plugins
/
12Creating Your First Angular App: Basics
/
-1Inheritance and Extending Objects With JavaScript
/
-10Introduction to the CSS Grid Layout With Examples
1 /
4
5Performant Animations Using KUTE.js: Part 5, Easing Functions and Attributes
/
-6Performant Animations Using KUTE.js: Part 4, Animating Text
/
-10Performant Animations Using KUTE.js: Part 3, Animating SVG
/
-8New Course: Code a Quiz App With Vue.js
/
-2Performant Animations Using KUTE.js: Part 2, Animating CSS Properties
/
2
8Performant Animations Using KUTE.js: Part 1, Getting Started
/
110 Best Responsive HTML5 Sliders for Images and Text (Plus 3 Free Options)
/
-3Single-Page Applications With ngRoute and ngAnimate in AngularJS
/
-14Deferring Tasks in Laravel Using Queues
/
-3
2Site Authentication in Node.js: User Signup and Login
/
10Working With Tables in React, Part Two
/
-17Working With Tables in React, Part One
/
1How to Set Up a Scalable, E-Commerce-Ready WordPress Site Using ClusterCS
/
-2New Course on WordPress Conditional Tags
/
3
-10Building With Vue.js 2 and Firebase
6 /
11
-8
1Best Unique Bootstrap JavaScript Plugins
/
-6
-1Essential JavaScript Libraries and Frameworks You Should Know About
/
6
3Vue.js Crash Course: Create a Simple Blog Using Vue.js
/
-14Build a React App With a Laravel RESTful Back End: Part 1, Laravel 5.5 API
/
2API Authentication With Node.js
/
-4Beginner’s Guide to Angular: HTTP
/
8Beginner’s Guide to Angular: Routing
/
-4Beginners Guide to Angular: Routing
/
-4Beginner’s Guide to Angular: Services
/
2Beginner’s Guide to Angular: Components
/
1How to Create a Custom Authentication Guard in Laravel
/
5Learn Computer Science With JavaScript: Part 3, Loops
/
-4Build Web Applications Using Node.js
/
1Learn Computer Science With JavaScript: Part 4, Functions
/
4Learn Computer Science With JavaScript: Part 2, Conditionals
/
13Create Interactive Charts Using Plotly.js, Part 5: Pie and Gauge Charts
/
-8Create Interactive Charts Using Plotly.js, Part 4: Bubble and Dot Charts
/
15Create Interactive Charts Using Plotly.js, Part 3: Bar Charts
/
-9Awesome JavaScript Libraries and Frameworks You Should Know About
/
-6Create Interactive Charts Using Plotly.js, Part 2: Line Charts
/
-8Bulk Import a CSV File Into MongoDB Using Mongoose With Node.js
/
-6Build a To-Do API With Node, Express, and MongoDB
/
-8Getting Started With End-to-End Testing in Angular Using Protractor
/
11TypeScript for Beginners, Part 4: Classes
/
-16Object-Oriented Programming With JavaScript
/
410 Best Affiliate WooCommerce Plugins Compared
/
6Stateful vs. Stateless Functional Components in React
/
8Make Your JavaScript Code Robust With Flow
/
-2Build a To-Do API With Node and Restify
/
7Testing Components in Angular Using Jasmine: Part 2, Services
/
3Testing Components in Angular Using Jasmine: Part 1
/
-1Creating a Blogging App Using React, Part 6: Tags
/
-10React Crash Course for Beginners, Part 3
/
3React Crash Course for Beginners, Part 2
/
5React Crash Course for Beginners, Part 1
/
3Set Up a React Environment, Part 4
1 /
9Set Up a React Environment, Part 3
/
-9New Course: Get Started With Phoenix
/
-7Set Up a React Environment, Part 2
/
2Command Line Basics and Useful Tricks With the Terminal
/
8How to Create a Real-Time Feed Using Phoenix and React
/
-3Build a React App With a Laravel Back End: Part 2, React
/
2Build a React App With a Laravel RESTful Back End: Part 1, Laravel 9 API
/
-4Creating a Blogging App Using React, Part 5: Profile Page
/
-2Pagination in CodeIgniter: The Complete Guide
/
23JavaScript-Based Animations Using Anime.js, Part 4: Callbacks, Easings, and SVG
JavaScript-Based Animations Using Anime.js, Part 3: Values, Timeline, and Playback
Learn to Code With JavaScript: Part 1, The Basics
/
510 Elegant CSS Pricing Tables for Your Latest Web Project
/
-9Getting Started With the Flux Architecture in React
/
-3Getting Started With Matter.js: The Composites and Composite Modules
/
2Getting Started With Matter.js: The Engine and World Modules
/
1410 More Popular HTML5 Projects for You to Use and Study
/
-22Understand the Basics of Laravel Middleware
/
-1Iterating Fast With Django & Heroku
/
10Creating a Blogging App Using React, Part 4: Update & Delete Posts
/
5Creating a jQuery Plugin for Long Shadow Design
/
-11How to Register & Use Laravel Service Providers
2 /
-13Unit Testing in React: Shallow vs. Static Testing
/
4Creating a Blogging App Using React, Part 3: Add & Display Post
/
14Creating a Blogging App Using React, Part 2: User Sign-Up
20 /
-4Creating a Blogging App Using React, Part 1: User Sign-In
/
69 More Popular HTML5 Projects for You to Use and Study
/
16Creating a Grocery List Manager Using Angular, Part 2: Managing Items
/
89 Elegant CSS Pricing Tables for Your Latest Web Project
/
-3Dynamic Page Templates in WordPress, Part 3
/
-4Angular vs. React: 7 Key Features Compared
/
6Creating a Grocery List Manager Using Angular, Part 1: Add & Display Items
/
1New eBooks Available for Subscribers in June 2017
/
7Create Interactive Charts Using Plotly.js, Part 1: Getting Started
/
10The 5 Best IDEs for WordPress Development (And Why)
/
-233 Popular WordPress User Interface Elements
/
2New Course: How to Hack Your Own App
/
-11How to Install Yii on Windows or a Mac
/
3What Is a JavaScript Operator?
/
-7How to Register and Use Laravel Service Providers
/
9
waly Good blog post. I absolutely love this…