We created the application will be simple billboard, where users can register, post assignments, and offered a reward for its completion. other users can view existing tasks, assign a task for themselves, and get a reward is offered.
Tasks will have basic data such as title, description and reward (as the necessary parameters) and due date and an optional note. User profile will only consist of the name, email users and web sites. So let's get started.
database Settings
Firstly, to the application data we will use MongoDB as the database server. MongoDB is a document-oriented database and leading NoSQL database out there. This is truly scalable and fast, which makes it great for managing large amounts of data.
MongoDB is a document-oriented database and the leading NoSQL database.
To use MongoDB in this application, I would use MongoDB driver CodeIgniter I wrote some time ago, it's just a wrapper of MongoDB PHP driver to emulate SQL ActiveRecord framework. You can find the source file for this driver in my public repository. For this driver to work properly, make sure that you have the MongoDB PHP driver is installed, if you do not, follow the steps to make it work.
Please note that describes the driver in CodeIgniter and the like out of the scope of this tutorial, see the documentation if you have any doubts. You just need to move "mongo_db.php" in the folder "config" folder to the "config" folder of your application and "Mongo_db" in the folder "Library" folder to the "library" in your application.
configuration database
The only file we need to edit at this time is "mongo_db.php" files under the folder "config", due to the installation mongo I have all default parameters, I'm just going to edit the line 40 and provide the database name I want to use:
$config['mongo_db'] = 'billboard';
That's it for the database, one of the many advantages of MongoDB is that the document does not have a structure that has been set, so it works without our need to set anything before using it, our database does not even have to exist, MongoDB will create it quickly when we needed it.
global configuration
In addition to the usual configuration options, which should include index_page base_url and if there is, we need to set the string and date helpers to autoload. I will not walk you through this, because we have a lot more to cover, when in doubt refer to the documentation.
In addition to the maid, we need to set up the encryption class because we will use for our applications.
URL handling
It will be a quiet service and we need a way to take on request comes to the server and handle them accordingly. We can use an existing library (which was great by the way) but for demonstration purposes, I'll create a function that I need to use the core features of CodeIgniter.
Handling Requests calm
Specifically, we will use the ability to extend core classes. We will start with the Controller, for the main part of this extension we use the method "_remap" at the bottom of the controller so that all of us can use the controller applications. Start by making a MY_Controller.php file in the folder "core" in the folder "application", we make the same as other controllers CodeIgniter, as follows:
<?php
if( !defined( 'BASEPATH' ) ) exit( 'No direct script access allowed' );
class MY_Controller extends CI_Controller {
}
Now we will use this controller remap CodeIgniter method to preprocess any request made to the server. In class we just created, add the following method:
public function _remap( $param ) {
$request = $_SERVER['REQUEST_METHOD'];
switch( strtoupper( $request ) ) {
case 'GET':
$method = 'read';
break;
case 'POST':
$method = 'save';
break;
case 'PUT':
$method = 'update';
break;
case 'DELETE':
$method = 'remove';
break;
case 'OPTIONS':
$method = '_options';
break;
}
$this->$method( $id );
}
Some things to note here, first, there are some verbs SISA that we ignore (like PATCH), because I show builds REST applications, I do not want to add things that might make this more complex than it needs to be. Secondly, we do not take into account cases where the controller did not implement certain methods, which is very possible that this could happen. Now, we can add a standard method for dealing with such requests, but that we do not add too much complexity, let's leave it like this. Thirdly, we accept param variable in a method declaration, let us discuss it, and then I will explain the OPTIONS request. On a switch statement, add the following code:
if ( preg_match( "/^(?=.*[a-zA-Z])(?=.*[0-9])/", $param ) ) {
$id = $param;
} else {
$id = null;
}
This regular expression match any string composed of uppercase and lowercase letters and numbers whatsoever. It is used to check if a string _id MongoDB is being given as a parameter, once again, this is not the safest way and most thorough checks, but for the sake of simplicity, we will remain as such.
OPTIONS request
Because we are building applications and web services client as a separate item, it makes sense that both will be hosted on a different domain, so we will enable CORS in the back-end and this means, among other things, that our application will respond well demand SELECTION.
When a web application created with BackboneJS (and several other frameworks) try to make an asynchronous request to a remote server, it sends the OPTIONS request before sending the actual demand is supposed to send. Among other things, the client tells the server where it sends a request, what kind of demand it is about to send, and the content was expecting. After that, it is up to the server to send the client a response which recognizes the request or reject it.
Because our back-end services, no matter which controller is called, will receive the OPTIONS request, it makes sense to apply the method to respond to our basic controller. Add the following method below (or above) _remap method in our controller.
private function _options() {
$this->output->set_header( 'Access-Control-Allow-Origin: *' );
$this->output->set_header( "Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS" );
$this->output->set_header( 'Access-Control-Allow-Headers: content-type' );
$this->output->set_content_type( 'application/json' );
$this->output->set_output( "*" );
}
Ideally, we will only allow multiple domains to make a request to us, we will examine the header request_headers to see if we accept it and we will check the type of content expected by the client to see if we support it, but again, this is an application that is not too complex and we skipped these edge cases.
manage output
To finish our base controller, let's create a method that each controller will be used to send results back to the client. At the base class controller, add the following method:
protected function _format_output( $output = null ) {
$this->output->set_header( 'Access-Control-Allow-Origin: *' );
if( isset( $output->status ) && $output->status == 'error' ) {
$this->output->set_status_header( 409, $output->desc );
}
$this->_parse_data( $output );
$this->output->set_content_type( 'application/json' );
$this->output->set_output( json_encode( $output ) );
}
Again, in order to process the server response BackboneJS should know that the host is received by the server, the Allow-Origin header, then, if the result is a broken one, we set the head of the show's status. This status will become clearer when we made the back-end models. Furthermore, we use a helper parse_data, which will be a private method (which we will write in a moment) but let me skip it for now, then we set the content type as JSON and finally we encode the response as JSON objects. Again, here we can (and should) supports more output formats (such as XML).
Now let's create a method parse_data helper (and I'll explain later), add the following code to the bottom of the controller:
private function _parse_data( &$data ) {
if ( ! is_array( $data ) && ! is_object( $data ) )
return $data;
foreach ( $data as $key => $value ) {
if ( is_object( $value ) || is_array( $value ) ) {
if( is_object( $data ) ) {
$data->{$key} = $this->_parse_data( $value );
} else {
$data[ $key ] = $this->_parse_data( $value );
}
}
if ( isset( $value->sec ) ) {
if( is_object( $data ) ) {
$data->{$key} = date( 'd.m.Y', $value->sec );
} else {
$data[ $key ] = date( 'd.m.Y', $value->sec );
}
}
if ( is_object( $value ) && isset( $value->{'$id'} ) ) {
if( is_object( $data ) ) {
$data->{$key} = $value->__toString();
} else {
$data[ $key ] = $value->__toString();
}
}
}
return $data;
}
First, note that we only analyzed data for arrays and objects, and we do it recursively. This pre-parsing to do with the fact that MongoDB uses the date and ID as an object, but our clients do not need this information. Now for the case ID, we just need to string its value, then the toString method call, then the value of having a '$ id' property. After that we changed the date to day.month.year format, this is being done for convenience in the design of the client application, once again, not the most flexible approach but it works for this example.
Input handling
Because we send JSON back to the client application, it is only logical that we receive data in JSON format as well. CodeIgniter does not support this by default as laravel not, as a matter of fact, even CodeIgniter does not support placing and removing params. This is mainly because of this framework is not intended for RESTful services, but the effort required to adapt minimal compared with the benefits, at least from my point of view.
So we'll start by supporting the BackboneJS send JSON data. Create a new file in the folder "core", this time will be named "MY_Input.php" and will have the following basic structure:
<?php
if( !defined( 'BASEPATH' ) ) exit( 'No direct script access allowed' );
class MY_Input extends CI_Input {
}
Now every time we use $ this-> entries in our application we will refer to this class, we will make some new methods and overwrite the existing few. First, we will add support for JSON data, add the following methods for the new class.
public function json() {
if ( !self::$request_params ) {
$payload = file_get_contents( 'php://input' );
if ( is_array( $payload ) ) {
self::$request_params = $payload;
} else if ( ( substr( $payload, 0, 1 ) == "{" ) && ( substr( $payload, ( strlen( $payload ) - 1 ), 1 ) == "}" ) ) {
self::$request_params = json_decode( $payload );
} else {
parse_str( $payload, self::$request_params );
}
}
return (object) self::$request_params;
}
$ Request_params is a static variable used to store the request string / data sent by the client. It is static in order to make it independent object so that we can access it from any controller at any given time. The Data is Obtained from the php: // input stream rather than the $ _POST global. This is done in order to Obtain the data is sent in via PUT and DELETE requests as well. Finally, the payload is Obtained Inspected to check if it's an array, a JSON encoded object, or a query string, and it's processed accordingly. The result is then Returned as an object.
For this method to work, we need to create the static $ request_params variable, add its declaration to the top of the class.
private static $request_params = null;
Handling Requests Regular
Furthermore, we need to override the post of class regular input of new payload using JSON instead of $ _POST global, add the following method to the new Input class.
public function post( $index = NULL, $xss_clean = FALSE ) {
$request_vars = ( array ) $this->json();
if ( $index === NULL && !empty( $request_vars ) ) {
$post = array();
foreach( array_keys( $request_vars ) as $key ) {
$post[$key] = $this->_fetch_from_array( $request_vars, $key, $xss_clean );
}
return $post;
}
return $this->_fetch_from_array( $request_vars, $index, $xss_clean );
}
This is almost the same as the method of class CI_Input original post, the difference is that it uses the method of our new JSON instead of $ _POST global to retrieve the data posting. Now let's do the same thing for the PUT method.
public function put( $index = NULL, $xss_clean = FALSE ) {
$request_vars = ( array ) $this->json();
if ( $index === NULL && !empty( $request_vars ) ) {
$put = array();
foreach( array_keys( $request_vars ) as $key ) {
$put[$key] = $this->_fetch_from_array( $request_vars, $key, $xss_clean );
}
return $put;
}
return $this->_fetch_from_array( $request_vars, $index, $xss_clean );
}
And then we also need the DELETE method:
public function delete( $index = NULL, $xss_clean = FALSE ) {
$request_vars = ( array ) $this->json();
if ( $index === NULL && !empty( $request_vars ) ) {
$delete = array();
foreach( array_keys( $request_vars ) as $key ) {
$delete[$key] = $this->_fetch_from_array( $request_vars, $key, $xss_clean );
}
return $delete;
}
return $this->_fetch_from_array( $request_vars, $index, $xss_clean );
}
Now technically, there is really no need for this additional method, since the method can handle the post params in the PUT and DELETE requests, but semantically better (in my opinion).
This is all we need for our input custom class. Once again we ignore edge case here, such as multi request, although it was not too difficult to handle them and still maintain the function obtained here, but, for the sake of simplicity we will stay just the way it is.
The basic model
To end an extension of core classes, let's create a basic model that each model in the application will extend over, this is just to avoid repeating common tasks for each model. Such as an extension of other core classes, this is the basic model of barebones us:
<?php
if( !defined( 'BASEPATH' ) ) exit( 'No direct script access allowed' );
class MY_Model extends CI_Model {
}
This basic model will only serve the purpose of setting up and take the blame. Add the following method to assign fault Model:
protected function _set_error( $desc, $data = null ) {
$this->_error = new stdClass();
$this->_error->status = 'error';
$this->_error->desc = $desc;
if ( isset( $data ) ) {
$this->_error->data = $data;
}
}
As you can see, this method uses an instance variable $ error, so let's add up the declaration of our basic model class.
protected $_error;
Finally, to keep it structured, let's make a getter method for this property.
public function get_error() {
return $this->_error;
}
Session handling
Control sessions
For the last part of this tutorial, we'll create a controller and a model for handling user sessions.
Controller for the session we will respond to the POST request made to the resources of our session, because the session can not be retrieved after creation, or updated directly, this controller will only respond to POST and DELETE requests. Please note that submitting another request for the resource will result in a server error, we are not dealing with edge cases here but this can be easily avoided by checking if a method is called in the file MY_Controller us, and the default name of the method if the resource does not support requests ,
Below you will find the structure for our session controller:
<?php
if ( !defined( 'BASEPATH' ) ) exit( 'No direct script access allowed' );
class Session extends MY_Controller {
public function __construct() {}
public function save() {}
public function remove( $id = null ) {}
}
Note that this controller expands MY_Controller class is not a class CI_Controller usual, we do have to use the methods _remap and other functions that we created earlier. OK, so now let's start with the constructor.
public function __construct() {
parent::__construct();
$this->load->model( 'session_model', 'model' );
}
This simple constructor just calls its parent constructor (as every controller in CodeIgniter must do) and then loads the controller's model. The code for the save method is as follows.
public function save() {
$result = $this->model->create();
if ( !$result ) {
$result = $this->model->get_error();
}
$this->_format_output( $result );
}
Both methods simply delegate the task at hand with the model, which handles the actual data manipulation. In real world applications, the necessary validation data and session examinations will be conducted in the controller, and common tasks such as checking the session should be held at the base of the controller.
Session Model
Now let's move on to the session model. Here is its basic structure:
<?php
if ( !defined( 'BASEPATH' ) ) exit( 'No direct script access allowed' );
class Session_Model extends MY_Model {
public function __construct() {}
public function create() {}
public function destroy( $id ) {}
}
Like the controller, this model extends the MY_Model class instead of the regular CI_Model class, this is being done in order to use the common methods that we've created earlier. Again, let's start with the constructor.
public function __construct() {
$this->load->driver( 'mongo_db' );
}
In this case, we just load the Mongo_db driver, that we discussed earlier. Now we'll continue with the method in charge of destroying the session.
public function destroy( $id ) {
$filters = array( '_id' => $this->mongo_db->gen_id( $id ) );
$query = $this->mongo_db->get_where( 'sessions', $filters );
if ( $query->num_rows() == 0 ) {
$this->_set_error( 'INVALID_CREDENTIALS' );
return false;
}
$this->mongo_db->remove( 'sessions', $filters );
return 'SESSION_TERMINATED';
}
In this method, we examine whether there is a session for session_id granted, and if so we tried to remove it, send a message of success if everything goes OK, or setting errors and returns false if an error occurs. Note that when using session_id we use a special method $ this-> mongo_db-> gen_id, this is because, as I mentioned earlier, in MongoDB is an object ID, so we use the id string to create it.
Finally, let's write a method that will make the wrapping part one of this tutorial series.
public function create() {
$query = $this->mongo_db->get_where( 'users', array( 'email' => $this->input->post( 'email' ) ) );
if ( $query->num_rows() != 1 ) {
$this->_set_error( 'INVALID_CREDENTIALS' );
return false;
}
$this->load->library( 'encrypt' );
$user = $query->row();
$salt = $this->encrypt->decode( $user->salt );
if ( $user->pass != sha1( $this->input->post( 'pass' ) . $salt ) ) {
$this->_set_error( 'INVALID_CREDENTIALS' );
return false;
}
$this->mongo_db->remove( 'sessions', array( 'user_id' => $user->_id->__toString() ) );
$session = array(
'timestamp' => now(),
'user_id' => $user->_id->__toString(),
'persistent' => $this->input->post( 'persistent' )
);
if ( !$this->mongo_db->insert( 'sessions', $session ) ) {
$this->_set_error( 'ERROR_REGISTERING_SESSION' );
return false;
}
$result = new stdClass();
$result->id = $this->mongo_db->insert_id();
$result->user_id = $user->_id->__toString();
return $result;
}
First of all, we check that there is a user associated with the given email. Then we decode the associated salt (which I'll explain in part two of this series when we close the user registration) and check that the password given suitable stored user passwords.
We then remove the previous session associated with the user and create a new session object. If we carefully examine this session, we will add things like USER_AGENT, ip_address, last_activity field and so for this object. Finally, we send back to the client sessions and user ID for the new session.
Conclusion
It has become a rather lengthy tutorial, we discussed a lot of topics, and we have more to cover yet. Hopefully by now you have a better understanding of the quiet service or stateless and how to make those services with CodeIgniter, and perhaps, you might as well pick up some new ideas that you can give to the core functions of this framework.
Tidak ada komentar:
Posting Komentar