Tutorial - REST


    In this tutorial, we will explain how to create a simple application that provides a RESTful API using the different HTTP methods:

    • to retrieve and search data
    • POST to add data
    • PUT to update data
    • DELETE to delete data

    Defining the API

    The API consists of the following methods:

    Creating the Application

    As the application is so simple, we will not implement any full MVC environment to develop it. In this case, we will use a to meet our goal.

    The following file structure is more than enough:

    First, we need a .htaccess file that contains all the rules to rewrite the request URIs to the index.php file (application entry-point):

    1. <IfModule mod_rewrite.c>
    2. RewriteEngine On
    3. RewriteCond %{REQUEST_FILENAME} !-f
    4. RewriteRule ^((?s).*)$ index.php?_url=/$1 [QSA,L]
    5. </IfModule>

    The bulk of our code will be placed in index.php. The file is created as follows:

    1. <?php
    2. use Phalcon\Mvc\Micro;
    3. $app = new Micro();
    4. // Define the routes here
    5. $app->handle(
    6. $_SERVER["REQUEST_URI"]
    7. );

    Now we will create the routes as we defined above:

    1. <?php
    2. use Phalcon\Mvc\Micro;
    3. $app = new Micro();
    4. // Retrieves all robots
    5. $app->get(
    6. '/api/robots',
    7. function () {
    8. // Operation to fetch all the robots
    9. }
    10. );
    11. // Searches for robots with $name in their name
    12. $app->get(
    13. '/api/robots/search/{name}',
    14. function ($name) {
    15. // Operation to fetch robot with name $name
    16. }
    17. );
    18. // Retrieves robots based on primary key
    19. $app->get(
    20. '/api/robots/{id:[0-9]+}',
    21. function ($id) {
    22. // Operation to fetch robot with id $id
    23. }
    24. );
    25. // Adds a new robot
    26. $app->post(
    27. '/api/robots',
    28. function () {
    29. // Operation to create a fresh robot
    30. }
    31. );
    32. // Updates robots based on primary key
    33. $app->put(
    34. '/api/robots/{id:[0-9]+}',
    35. function ($id) {
    36. // Operation to update a robot with id $id
    37. }
    38. );
    39. // Deletes robots based on primary key
    40. $app->delete(
    41. '/api/robots/{id:[0-9]+}',
    42. function ($id) {
    43. // Operation to delete the robot with id $id
    44. }
    45. );
    46. $app->handle(
    47. $_SERVER["REQUEST_URI"]
    48. );

    Each route is defined with a method with the same name as the HTTP method, as first parameter we pass a route pattern, followed by a handler. In this case, the handler is an anonymous function. The following route: /api/robots/{id:[0-9]+}, by example, explicitly sets that the id parameter must have a numeric format.

    Our API provides information about robots, these data are stored in a database. The following model allows us to access that table in an object-oriented way. We have implemented some business rules using built-in validators and simple validations. Doing this will give us the peace of mind that saved data meet the requirements of our application. This model file should be placed in your Models folder.

    1. <?php
    2. namespace Store\Toys;
    3. use Phalcon\Mvc\Model;
    4. use Phalcon\Mvc\Model\Message;
    5. use Phalcon\Validation;
    6. use Phalcon\Validation\Validator\Uniqueness;
    7. use Phalcon\Validation\Validator\InclusionIn;
    8. class Robots extends Model
    9. {
    10. public function validation()
    11. {
    12. $validator = new Validation();
    13. // Type must be: droid, mechanical or virtual
    14. $validator->add(
    15. "type",
    16. new InclusionIn(
    17. [
    18. 'message' => 'Type must be "droid", "mechanical", or "virtual"',
    19. 'domain' => [
    20. 'droid',
    21. 'mechanical',
    22. 'virtual',
    23. ],
    24. ]
    25. )
    26. );
    27. // Robot name must be unique
    28. $validator->add(
    29. 'name',
    30. new Uniqueness(
    31. [
    32. 'field' => 'name',
    33. 'message' => 'The robot name must be unique',
    34. ]
    35. )
    36. );
    37. // Year cannot be less than zero
    38. if ($this->year < 0) {
    39. $this->appendMessage(
    40. new Message('The year cannot be less than zero')
    41. );
    42. }
    43. // Check if any messages have been produced
    44. if ($this->validationHasFailed() === true) {
    45. return false;
    46. }
    47. }
    48. }

    Now, we must set up a connection to be used by this model and load it within our app index.php:

    1. <?php
    2. use Phalcon\Loader;
    3. use Phalcon\Mvc\Micro;
    4. use Phalcon\Di\FactoryDefault;
    5. use Phalcon\Db\Adapter\Pdo\Mysql as PdoMysql;
    6. // Use Loader() to autoload our model
    7. $loader = new Loader();
    8. $loader->registerNamespaces(
    9. [
    10. );
    11. $loader->register();
    12. $di = new FactoryDefault();
    13. // Set up the database service
    14. $di->set(
    15. 'db',
    16. function () {
    17. return new PdoMysql(
    18. [
    19. 'host' => 'localhost',
    20. 'username' => 'asimov',
    21. 'password' => 'zeroth',
    22. 'dbname' => 'robotics',
    23. ]
    24. );
    25. }
    26. );
    27. // Create and bind the DI to the application
    28. $app = new Micro($di);

    Retrieving Data

    The first handler that we will implement is which by method GET returns all available robots. Let’s use PHQL to perform this simple query returning the results as JSON. index.php

    , allow us to write queries using a high-level, object-oriented SQL dialect that internally translates to the right SQL statements depending on the database system we are using. The clause use in the anonymous function allows us to pass some variables from the global to local scope easily.

    The searching by name handler would look like index.php:

    1. <?php
    2. // Searches for robots with $name in their name
    3. $app->get(
    4. '/api/robots/search/{name}',
    5. function ($name) use ($app) {
    6. $phql = 'SELECT * FROM Store\Toys\Robots WHERE name LIKE :name: ORDER BY name';
    7. $robots = $app->modelsManager->executeQuery(
    8. $phql,
    9. [
    10. 'name' => '%' . $name . '%'
    11. ]
    12. );
    13. $data = [];
    14. foreach ($robots as $robot) {
    15. $data[] = [
    16. 'id' => $robot->id,
    17. 'name' => $robot->name,
    18. ];
    19. }
    20. echo json_encode($data);
    21. }
    22. );

    Searching by the field id it’s quite similar, in this case, we’re also notifying if the robot was found or not index.php:

    1. <?php
    2. use Phalcon\Http\Response;
    3. // Retrieves robots based on primary key
    4. $app->get(
    5. '/api/robots/{id:[0-9]+}',
    6. function ($id) use ($app) {
    7. $phql = 'SELECT * FROM Store\Toys\Robots WHERE id = :id:';
    8. $robot = $app->modelsManager->executeQuery(
    9. $phql,
    10. [
    11. 'id' => $id,
    12. ]
    13. )->getFirst();
    14. // Create a response
    15. $response = new Response();
    16. if ($robot === false) {
    17. $response->setJsonContent(
    18. [
    19. 'status' => 'NOT-FOUND'
    20. ]
    21. );
    22. } else {
    23. $response->setJsonContent(
    24. [
    25. 'status' => 'FOUND',
    26. 'data' => [
    27. 'id' => $robot->id,
    28. 'name' => $robot->name
    29. ]
    30. ]
    31. );
    32. }
    33. return $response;
    34. }
    35. );

    Inserting Data

    Taking the data as a JSON string inserted in the body of the request, we also use PHQL for insertion index.php:

    1. <?php
    2. use Phalcon\Http\Response;
    3. // Adds a new robot
    4. $app->post(
    5. '/api/robots',
    6. function () use ($app) {
    7. $robot = $app->request->getJsonRawBody();
    8. $phql = 'INSERT INTO Store\Toys\Robots (name, type, year) VALUES (:name:, :type:, :year:)';
    9. $status = $app->modelsManager->executeQuery(
    10. $phql,
    11. [
    12. 'name' => $robot->name,
    13. 'type' => $robot->type,
    14. 'year' => $robot->year,
    15. ]
    16. );
    17. // Create a response
    18. $response = new Response();
    19. // Check if the insertion was successful
    20. if ($status->success() === true) {
    21. // Change the HTTP status
    22. $response->setStatusCode(201, 'Created');
    23. $robot->id = $status->getModel()->id;
    24. $response->setJsonContent(
    25. [
    26. 'status' => 'OK',
    27. 'data' => $robot,
    28. ]
    29. );
    30. } else {
    31. // Change the HTTP status
    32. $response->setStatusCode(409, 'Conflict');
    33. // Send errors to the client
    34. $errors = [];
    35. foreach ($status->getMessages() as $message) {
    36. $errors[] = $message->getMessage();
    37. }
    38. $response->setJsonContent(
    39. [
    40. 'status' => 'ERROR',
    41. 'messages' => $errors,
    42. ]
    43. );
    44. }
    45. return $response;
    46. }

    The data update is similar to insertion. The id passed as parameter indicates what robot must be updated index.php:

    1. <?php
    2. use Phalcon\Http\Response;
    3. // Updates robots based on primary key
    4. $app->put(
    5. '/api/robots/{id:[0-9]+}',
    6. function ($id) use ($app) {
    7. $robot = $app->request->getJsonRawBody();
    8. $phql = 'UPDATE Store\Toys\Robots SET name = :name:, type = :type:, year = :year: WHERE id = :id:';
    9. $status = $app->modelsManager->executeQuery(
    10. $phql,
    11. [
    12. 'id' => $id,
    13. 'name' => $robot->name,
    14. 'type' => $robot->type,
    15. 'year' => $robot->year,
    16. ]
    17. );
    18. // Create a response
    19. $response = new Response();
    20. // Check if the insertion was successful
    21. if ($status->success() === true) {
    22. $response->setJsonContent(
    23. [
    24. 'status' => 'OK'
    25. ]
    26. );
    27. } else {
    28. // Change the HTTP status
    29. $response->setStatusCode(409, 'Conflict');
    30. $errors = [];
    31. foreach ($status->getMessages() as $message) {
    32. $errors[] = $message->getMessage();
    33. }
    34. $response->setJsonContent(
    35. [
    36. 'status' => 'ERROR',
    37. 'messages' => $errors,
    38. ]
    39. );
    40. }
    41. return $response;
    42. }
    43. );

    Deleting Data

    1. <?php
    2. use Phalcon\Http\Response;
    3. // Deletes robots based on primary key
    4. $app->delete(
    5. '/api/robots/{id:[0-9]+}',
    6. function ($id) use ($app) {
    7. $phql = 'DELETE FROM Store\Toys\Robots WHERE id = :id:';
    8. $status = $app->modelsManager->executeQuery(
    9. $phql,
    10. [
    11. 'id' => $id,
    12. ]
    13. );
    14. // Create a response
    15. $response = new Response();
    16. if ($status->success() === true) {
    17. $response->setJsonContent(
    18. [
    19. 'status' => 'OK'
    20. ]
    21. );
    22. } else {
    23. // Change the HTTP status
    24. $response->setStatusCode(409, 'Conflict');
    25. $errors = [];
    26. foreach ($status->getMessages() as $message) {
    27. $errors[] = $message->getMessage();
    28. }
    29. $response->setJsonContent(
    30. [
    31. 'status' => 'ERROR',
    32. 'messages' => $errors,
    33. ]
    34. );
    35. }
    36. return $response;
    37. }
    38. );

    Creating database

    Now we will create database for our application.Run SQL queries as follows:

    Using we’ll test every route in our application verifying its proper operation.

    Obtain all the robots:

    1. curl -i -X GET https://localhost/my-rest-api/api/robots
    2. HTTP/1.1 200 OK
    3. Date: Tue, 21 Jul 2015 07:05:13 GMT
    4. Server: Apache/2.2.22 (Unix) DAV/2
    5. Content-Length: 117
    6. Content-Type: text/html; charset=UTF-8
    7. [{"id":"1","name":"Robotina"},{"id":"2","name":"Astro Boy"},{"id":"3","name":"Terminator"}]

    Search a robot by its name:

    1. curl -i -X GET https://localhost/my-rest-api/api/robots/search/Astro
    2. HTTP/1.1 200 OK
    3. Date: Tue, 21 Jul 2015 07:09:23 GMT
    4. Server: Apache/2.2.22 (Unix) DAV/2
    5. Content-Length: 31
    6. Content-Type: text/html; charset=UTF-8
    7. [{"id":"2","name":"Astro Boy"}]

    Obtain a robot by its id:

    1. curl -i -X GET https://localhost/my-rest-api/api/robots/3
    2. HTTP/1.1 200 OK
    3. Date: Tue, 21 Jul 2015 07:12:18 GMT
    4. Server: Apache/2.2.22 (Unix) DAV/2
    5. Content-Length: 56
    6. Content-Type: text/html; charset=UTF-8
    7. {"status":"FOUND","data":{"id":"3","name":"Terminator"}}

    Insert a new robot:

    1. curl -i -X POST -d '{"name":"C-3PO","type":"droid","year":1977}'
    2. https://localhost/my-rest-api/api/robots
    3. HTTP/1.1 201 Created
    4. Date: Tue, 21 Jul 2015 07:15:09 GMT
    5. Server: Apache/2.2.22 (Unix) DAV/2
    6. Content-Length: 75
    7. Content-Type: text/html; charset=UTF-8
    8. {"status":"OK","data":{"name":"C-3PO","type":"droid","year":1977,"id":"4"}}

    Try to insert a new robot with the name of an existing robot:

    1. curl -i -X POST -d '{"name":"C-3PO","type":"droid","year":1977}'
    2. https://localhost/my-rest-api/api/robots
    3. HTTP/1.1 409 Conflict
    4. Date: Tue, 21 Jul 2015 07:18:28 GMT
    5. Server: Apache/2.2.22 (Unix) DAV/2
    6. Content-Length: 63
    7. Content-Type: text/html; charset=UTF-8
    8. {"status":"ERROR","messages":["The robot name must be unique"]}

    Or update a robot with an unknown type:

    1. curl -i -X DELETE https://localhost/my-rest-api/api/robots/4
    2. HTTP/1.1 200 OK
    3. Date: Tue, 21 Jul 2015 08:49:29 GMT
    4. Server: Apache/2.2.22 (Unix) DAV/2
    5. Content-Length: 15
    6. Content-Type: text/html; charset=UTF-8
    7. {"status":"OK"}