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
/Create Modern Vue Apps Using Create-Vue and Vite
/How to Fix the “There Has Been a Critical Error in Your Website” Error in WordPress
How To Fix The “There Has Been A Critical Error in Your Website” Error in WordPress
/How Long Does It Take to Learn JavaScript?
/The Best Way to Deep Copy an Object in JavaScript
/Adding and Removing Elements From Arrays in JavaScript
/Create a JavaScript AJAX Post Request: With and Without jQuery
/5 Real-Life Uses for the JavaScript reduce() Method
/How to Enable or Disable a Button With JavaScript: jQuery vs. Vanilla
/How to Enable or Disable a Button With JavaScript: jQuery vs Vanilla
/Confirm Yes or No With JavaScript
/How to Change the URL in JavaScript: Redirecting
/15+ Best WordPress Twitter Widgets
/27 Best Tab and Accordion Widget Plugins for WordPress (Free & Premium)
/21 Best Tab and Accordion Widget Plugins for WordPress (Free & Premium)
/30 HTML Best Practices for Beginners
/31 Best WordPress Calendar Plugins and Widgets (With 5 Free Plugins)
/25 Ridiculously Impressive HTML5 Canvas Experiments
/How to Implement Email Verification for New Members
/How to Create a Simple Web-Based Chat Application
/30 Popular WordPress User Interface Elements
/Top 18 Best Practices for Writing Super Readable Code
/Best Affiliate WooCommerce Plugins Compared
/18 Best WordPress Star Rating Plugins
/10+ Best WordPress Twitter Widgets
/20+ Best WordPress Booking and Reservation Plugins
/Working With Tables in React: Part Two
/Best CSS Animations and Effects on CodeCanyon
/30 CSS Best Practices for Beginners
/How to Create a Custom WordPress Plugin From Scratch
/10 Best Responsive HTML5 Sliders for Images and Text… and 3 Free Options
/16 Best Tab and Accordion Widget Plugins for WordPress
/18 Best WordPress Membership Plugins and 5 Free Plugins
/25 Best WooCommerce Plugins for Products, Pricing, Payments and More
/10 Best WordPress Twitter Widgets
1 /12 Best Contact Form PHP Scripts for 2020
/20 Popular WordPress User Interface Elements
/10 Best WordPress Star Rating Plugins
/12 Best CSS Animations on CodeCanyon
/12 Best WordPress Booking and Reservation Plugins
/12 Elegant CSS Pricing Tables for Your Latest Web Project
/24 Best WordPress Form Plugins for 2020
/14 Best PHP Event Calendar and Booking Scripts
/Create a Blog for Each Category or Department in Your WooCommerce Store
/8 Best WordPress Booking and Reservation Plugins
/Best Exit Popups for WordPress Compared
/Best Exit Popups for WordPress Compared
/11 Best Tab & Accordion WordPress Widgets & Plugins
/12 Best Tab & Accordion WordPress Widgets & Plugins
1New Course: Practical React Fundamentals
/Preview Our New Course on Angular Material
/Build Your Own CAPTCHA and Contact Form in PHP
/Object-Oriented PHP With Classes and Objects
/Best Practices for ARIA Implementation
/Accessible Apps: Barriers to Access and Getting Started With Accessibility
/Dramatically Speed Up Your React Front-End App Using Lazy Loading
/15 Best Modern JavaScript Admin Templates for React, Angular, and Vue.js
/15 Best Modern JavaScript Admin Templates for React, Angular and Vue.js
/19 Best JavaScript Admin Templates for React, Angular, and Vue.js
/New Course: Build an App With JavaScript and the MEAN Stack
/Hands-on With ARIA: Accessibility Recipes for Web Apps
/10 Best WordPress Facebook Widgets
13 /Hands-on With ARIA: Accessibility for eCommerce
/New eBooks Available for Subscribers
/Hands-on With ARIA: Homepage Elements and Standard Navigation
/Site Accessibility: Getting Started With ARIA
/How Secure Are Your JavaScript Open-Source Dependencies?
/New Course: Secure Your WordPress Site With SSL
/Testing Components in React Using Jest and Enzyme
/Testing Components in React Using Jest: The Basics
/15 Best PHP Event Calendar and Booking Scripts
/Create Interactive Gradient Animations Using Granim.js
/How to Build Complex, Large-Scale Vue.js Apps With Vuex
1 /Examples of Dependency Injection in PHP With Symfony Components
/Set Up Routing in PHP Applications Using the Symfony Routing Component
1 /A Beginner’s Guide to Regular Expressions in JavaScript
/Introduction to Popmotion: Custom Animation Scrubber
/Introduction to Popmotion: Pointers and Physics
/New Course: Connect to a Database With Laravel’s Eloquent ORM
/How to Create a Custom Settings Panel in WooCommerce
/Building the DOM faster: speculative parsing, async, defer and preload
1 /20 Useful PHP Scripts Available on CodeCanyon
3 /How to Find and Fix Poor Page Load Times With Raygun
/Introduction to the Stimulus Framework
/Single-Page React Applications With the React-Router and React-Transition-Group Modules
12 Best Contact Form PHP Scripts
1 /Getting Started With the Mojs Animation Library: The ShapeSwirl and Stagger Modules
/Getting Started With the Mojs Animation Library: The Shape Module
Getting Started With the Mojs Animation Library: The HTML Module
/Project Management Considerations for Your WordPress Project
/8 Things That Make Jest the Best React Testing Framework
/Creating an Image Editor Using CamanJS: Layers, Blend Modes, and Events
/New Short Course: Code a Front-End App With GraphQL and React
/Creating an Image Editor Using CamanJS: Applying Basic Filters
/Creating an Image Editor Using CamanJS: Creating Custom Filters and Blend Modes
/Modern Web Scraping With BeautifulSoup and Selenium
/Challenge: Create a To-Do List in React
1Deploy PHP Web Applications Using Laravel Forge
/Getting Started With the Mojs Animation Library: The Burst Module
/10 Things Men Can Do to Support Women in Tech
/A Gentle Introduction to Higher-Order Components in React: Best Practices
/Challenge: Build a React Component
/A Gentle Introduction to HOC in React: Learn by Example
/A Gentle Introduction to Higher-Order Components in React
/Creating Pretty Popup Messages Using SweetAlert2
/Creating Stylish and Responsive Progress Bars Using ProgressBar.js
/18 Best Contact Form PHP Scripts for 2022
/How to Make a Real-Time Sports Application Using Node.js
/Creating a Blogging App Using Angular & MongoDB: Delete Post
/Set Up an OAuth2 Server Using Passport in Laravel
/Creating a Blogging App Using Angular & MongoDB: Edit Post
/Creating a Blogging App Using Angular & MongoDB: Add Post
/Introduction to Mocking in Python
/Creating a Blogging App Using Angular & MongoDB: Show Post
/Creating a Blogging App Using Angular & MongoDB: Home
/Creating a Blogging App Using Angular & MongoDB: Login
/Creating Your First Angular App: Implement Routing
/Persisted WordPress Admin Notices: Part 4
/Creating Your First Angular App: Components, Part 2
/Persisted WordPress Admin Notices: Part 3
/Creating Your First Angular App: Components, Part 1
/How Laravel Broadcasting Works
/Persisted WordPress Admin Notices: Part 2
/Create Your First Angular App: Storing and Accessing Data
/Persisted WordPress Admin Notices: Part 1
/Error and Performance Monitoring for Web & Mobile Apps Using Raygun
Using Luxon for Date and Time in JavaScript
7 /How to Create an Audio Oscillator With the Web Audio API
/How to Cache Using Redis in Django Applications
/20 Essential WordPress Utilities to Manage Your Site
/Introduction to API Calls With React and Axios
/Beginner’s Guide to Angular 4: HTTP
/Rapid Web Deployment for Laravel With GitHub, Linode, and RunCloud.io
/Beginners Guide to Angular 4: Routing
/Beginner’s Guide to Angular 4: Services
/Beginner’s Guide to Angular 4: Components
/Creating a Drop-Down Menu for Mobile Pages
/Introduction to Forms in Angular 4: Writing Custom Form Validators
/10 Best WordPress Booking & Reservation Plugins
/Getting Started With Redux: Connecting Redux With React
/Getting Started With Redux: Learn by Example
/Getting Started With Redux: Why Redux?
/How to Auto Update WordPress Salts
/How to Download Files in Python
/Eloquent Mutators and Accessors in Laravel
1 /10 Best HTML5 Sliders for Images and Text
/Site Authentication in Node.js: User Signup
/Creating a Task Manager App Using Ionic: Part 2
/Creating a Task Manager App Using Ionic: Part 1
/Introduction to Forms in Angular 4: Reactive Forms
/Introduction to Forms in Angular 4: Template-Driven Forms
/24 Essential WordPress Utilities to Manage Your Site
/25 Essential WordPress Utilities to Manage Your Site
/Get Rid of Bugs Quickly Using BugReplay
1 /Manipulating HTML5 Canvas Using Konva: Part 1, Getting Started
/10 Must-See Easy Digital Downloads Extensions for Your WordPress Site
22 Best WordPress Booking and Reservation Plugins
/Understanding ExpressJS Routing
/15 Best WordPress Star Rating Plugins
/Creating Your First Angular App: Basics
/Inheritance and Extending Objects With JavaScript
/Introduction to the CSS Grid Layout With Examples
1Performant Animations Using KUTE.js: Part 5, Easing Functions and Attributes
Performant Animations Using KUTE.js: Part 4, Animating Text
/Performant Animations Using KUTE.js: Part 3, Animating SVG
/New Course: Code a Quiz App With Vue.js
/Performant Animations Using KUTE.js: Part 2, Animating CSS Properties
Performant Animations Using KUTE.js: Part 1, Getting Started
/10 Best Responsive HTML5 Sliders for Images and Text (Plus 3 Free Options)
/Single-Page Applications With ngRoute and ngAnimate in AngularJS
/Deferring Tasks in Laravel Using Queues
/Site Authentication in Node.js: User Signup and Login
/Working With Tables in React, Part Two
/Working With Tables in React, Part One
/How to Set Up a Scalable, E-Commerce-Ready WordPress Site Using ClusterCS
/New Course on WordPress Conditional Tags
/TypeScript for Beginners, Part 5: Generics
/Building With Vue.js 2 and Firebase
6 /Best Unique Bootstrap JavaScript Plugins
/Essential JavaScript Libraries and Frameworks You Should Know About
/Vue.js Crash Course: Create a Simple Blog Using Vue.js
/Build a React App With a Laravel RESTful Back End: Part 1, Laravel 5.5 API
/API Authentication With Node.js
/Beginner’s Guide to Angular: HTTP
/Beginner’s Guide to Angular: Routing
/Beginners Guide to Angular: Routing
/Beginner’s Guide to Angular: Services
/Beginner’s Guide to Angular: Components
/How to Create a Custom Authentication Guard in Laravel
/Learn Computer Science With JavaScript: Part 3, Loops
/Build Web Applications Using Node.js
/Learn Computer Science With JavaScript: Part 4, Functions
/Learn Computer Science With JavaScript: Part 2, Conditionals
/Create Interactive Charts Using Plotly.js, Part 5: Pie and Gauge Charts
/Create Interactive Charts Using Plotly.js, Part 4: Bubble and Dot Charts
Create Interactive Charts Using Plotly.js, Part 3: Bar Charts
/Awesome JavaScript Libraries and Frameworks You Should Know About
/Create Interactive Charts Using Plotly.js, Part 2: Line Charts
/Bulk Import a CSV File Into MongoDB Using Mongoose With Node.js
/Build a To-Do API With Node, Express, and MongoDB
/Getting Started With End-to-End Testing in Angular Using Protractor
/TypeScript for Beginners, Part 4: Classes
/Object-Oriented Programming With JavaScript
/10 Best Affiliate WooCommerce Plugins Compared
/Stateful vs. Stateless Functional Components in React
/Make Your JavaScript Code Robust With Flow
/Build a To-Do API With Node and Restify
/Testing Components in Angular Using Jasmine: Part 2, Services
/Testing Components in Angular Using Jasmine: Part 1
/Creating a Blogging App Using React, Part 6: Tags
/React Crash Course for Beginners, Part 3
/React Crash Course for Beginners, Part 2
/React Crash Course for Beginners, Part 1
/Set Up a React Environment, Part 4
1 /Set Up a React Environment, Part 3
/New Course: Get Started With Phoenix
/Set Up a React Environment, Part 2
/Set Up a React Environment, Part 1
/Command Line Basics and Useful Tricks With the Terminal
/How to Create a Real-Time Feed Using Phoenix and React
/Build a React App With a Laravel Back End: Part 2, React
/Build a React App With a Laravel RESTful Back End: Part 1, Laravel 9 API
/Creating a Blogging App Using React, Part 5: Profile Page
/Pagination in CodeIgniter: The Complete Guide
/JavaScript-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
/10 Elegant CSS Pricing Tables for Your Latest Web Project
/Getting Started With the Flux Architecture in React
/Getting Started With Matter.js: The Composites and Composite Modules
Getting Started With Matter.js: The Engine and World Modules
/10 More Popular HTML5 Projects for You to Use and Study
/Understand the Basics of Laravel Middleware
/Iterating Fast With Django & Heroku
/Creating a Blogging App Using React, Part 4: Update & Delete Posts
/Creating a jQuery Plugin for Long Shadow Design
/How to Register & Use Laravel Service Providers
2 /Unit Testing in React: Shallow vs. Static Testing
/Creating a Blogging App Using React, Part 3: Add & Display Post
/Creating a Blogging App Using React, Part 2: User Sign-Up
20 /Creating a Blogging App Using React, Part 1: User Sign-In
/Creating a Grocery List Manager Using Angular, Part 2: Managing Items
/9 Elegant CSS Pricing Tables for Your Latest Web Project
/Dynamic Page Templates in WordPress, Part 3
/Angular vs. React: 7 Key Features Compared
/Creating a Grocery List Manager Using Angular, Part 1: Add & Display Items
New eBooks Available for Subscribers in June 2017
/Create Interactive Charts Using Plotly.js, Part 1: Getting Started
/The 5 Best IDEs for WordPress Development (And Why)
/33 Popular WordPress User Interface Elements
/New Course: How to Hack Your Own App
/How to Install Yii on Windows or a Mac
/What Is a JavaScript Operator?
/How to Register and Use Laravel Service Providers
/
waly Good blog post. I absolutely love this…