This sponsored post features a product relevant to our readers while meeting our editorial guidelines for being objective and educational. 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.
1. Basic Setup
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.
2.
Base Model
The 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.
Step 1: MongoDB Configuration
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.
Step 2: Database Connection
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.
Step 3: Helper Methods
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; }
Step 4: CRUD Operations
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.
CREATE Operation
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.
READ Operation
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.
UPDATE Operation
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.
DELETE Operation
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.
3.
Session Model
The
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.
4.
User Model
The 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
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
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.
5.
BaseController Class
Just 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.
6.
SessionController Class
The 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.
Step 1: Session Removal
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.
Step 2: Session Creation
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:
- starting a session for a user
- creating the user when it doesn't exists and then starting the session
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
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
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
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.
7.
UserController Class
The 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
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.
8. Application Router
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 ] -
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.
Conclusion
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.
Envato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!Translate this post
