教程:创建简单的 REST API(Tutorial: Creating a Simple REST API)

在这个教程中,我们会学习如何创建一个拥有 RESTful API 的应用程序,它将会使用如下的几个 HTTP 方法:

  • GET - 接受、查找数据
  • POST - 添加数据
  • PUT - 更新数据
  • DELETE - 删除数据

定义 API(Defining the API)

这个 API 包含如下方法(Methods)

Method URL Action
GET /api/robots Retrieves all robots
GET /api/robots/search/Astro Searches for robots with ‘Astro’ in their name
GET /api/robots/2 Retrieves robots based on primary key
POST /api/robots Adds a new robot
PUT /api/robots/2 Updates robots based on primary key
DELETE /api/robots/2 Deletes robots based on primary key

创建应用(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 micro application to meet our goal.

The following file structure is more than enough:

  1. my-rest-api/
  2. models/
  3. Robots.php
  4. index.php
  5. .htaccess

First, we need an .htaccess file that contains all the rules to rewrite the URIs to the index.php file,that is our application:

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

Then, in the index.php file we create the following:

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

Now we will create the routes as we defined above:

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

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.

When a defined route matches the requested URI then the application executes the corresponding handler.

创建模型(Creating a Model)

Our API provides information about ‘robots’, these data are stored in a database. The following model allows us toaccess that table in an object-oriented way. We have implemented some business rules using built-in validatorsand simple validations. Doing this will give us the peace of mind that saved data meet the requirements of ourapplication:

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

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

  1. <?php
  2.  
  3. use Phalcon\Loader;
  4. use Phalcon\Mvc\Micro;
  5. use Phalcon\Di\FactoryDefault;
  6. use Phalcon\Db\Adapter\Pdo\Mysql as PdoMysql;
  7.  
  8. // Use Loader() to autoload our model
  9. $loader = new Loader();
  10.  
  11. $loader->registerDirs(
  12. array(
  13. __DIR__ . '/models/'
  14. )
  15. )->register();
  16.  
  17. $di = new FactoryDefault();
  18.  
  19. // Set up the database service
  20. $di->set('db', function () {
  21. return new PdoMysql(
  22. array(
  23. "host" => "localhost",
  24. "username" => "asimov",
  25. "password" => "zeroth",
  26. "dbname" => "robotics"
  27. )
  28. );
  29. });
  30.  
  31. // Create and bind the DI to the application
  32. $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 toperform this simple query returning the results as JSON:

  1. <?php
  2.  
  3. // Retrieves all robots
  4. $app->get('/api/robots', function () use ($app) {
  5.  
  6. $phql = "SELECT * FROM Robots ORDER BY name";
  7. $robots = $app->modelsManager->executeQuery($phql);
  8.  
  9. $data = array();
  10. foreach ($robots as $robot) {
  11. $data[] = array(
  12. 'id' => $robot->id,
  13. 'name' => $robot->name
  14. );
  15. }
  16.  
  17. echo json_encode($data);
  18. });

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

The searching by name handler would look like:

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

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

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

插入数据(Inserting Data)

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

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

更新数据(Updating Data)

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

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

删除数据(Deleting Data)

The data delete is similar to update. The “id” passed as parameter indicates what robot must be deleted:

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

测试应用(Testing our Application)

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

Obtain all the robots:

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

Search a robot by its name:

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

Obtain a robot by its id:

  1. curl -i -X GET http://localhost/my-rest-api/api/robots/3
  2.  
  3. HTTP/1.1 200 OK
  4. Date: Tue, 21 Jul 2015 07:12:18 GMT
  5. Server: Apache/2.2.22 (Unix) DAV/2
  6. Content-Length: 56
  7. Content-Type: text/html; charset=UTF-8
  8.  
  9. {"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. http://localhost/my-rest-api/api/robots
  3.  
  4. HTTP/1.1 201 Created
  5. Date: Tue, 21 Jul 2015 07:15:09 GMT
  6. Server: Apache/2.2.22 (Unix) DAV/2
  7. Content-Length: 75
  8. Content-Type: text/html; charset=UTF-8
  9.  
  10. {"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. http://localhost/my-rest-api/api/robots
  3.  
  4. HTTP/1.1 409 Conflict
  5. Date: Tue, 21 Jul 2015 07:18:28 GMT
  6. Server: Apache/2.2.22 (Unix) DAV/2
  7. Content-Length: 63
  8. Content-Type: text/html; charset=UTF-8
  9.  
  10. {"status":"ERROR","messages":["The robot name must be unique"]}

Or update a robot with an unknown type:

  1. curl -i -X PUT -d '{"name":"ASIMO","type":"humanoid","year":2000}'
  2. http://localhost/my-rest-api/api/robots/4
  3.  
  4. HTTP/1.1 409 Conflict
  5. Date: Tue, 21 Jul 2015 08:48:01 GMT
  6. Server: Apache/2.2.22 (Unix) DAV/2
  7. Content-Length: 104
  8. Content-Type: text/html; charset=UTF-8
  9.  
  10. {"status":"ERROR","messages":["Value of field 'type' must be part of
  11. list: droid, mechanical, virtual"]}

Finally, delete a robot:

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

结束语(Conclusion)

As we have seen, develop a RESTful API with Phalcon is easy. Later in the documentation we’ll explain in detail how touse micro applications and the PHQL language.

原文: http://www.myleftstudio.com/reference/tutorial-rest.html