教程3:创建简单的 REST API

在本教程中,我们将解释如何创建一个简单的应用程序,它提供了一个使用不同HTTP方法的 RESTful API:

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

  • GET去获取并搜索数据
  • POST 去添加数据
  • PUT去更新数据
  • DELETE 去删除数据
  • GET to retrieve and search data
  • POST to add data
  • PUT to update data
  • DELETE to delete data

定义 API

API包含以下的方法:

The API consists of the following methods:

MethodURLAction
GET/api/robotsRetrieves all robots
GET/api/robots/search/AstroSearches for robots with ‘Astro’ in their name
GET/api/robots/2Retrieves robots based on primary key
POST/api/robotsAdds a new robot
PUT/api/robots/2Updates robots based on primary key
DELETE/api/robots/2Deletes robots based on primary key

创建应用

因为这个应用很简单,我们不会去开发一个全栈的MVC应用,在这个例子中我们使用:doc:micro application <micro> 去满足我们的需求。

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

首页我们需要.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 ^(.*)$ index.php?_url=/$1 [QSA,L]
  5. </IfModule>

然后在index.php中我们输入如下代码:

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

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

现在像我们上面定义的那样创建路由:

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('/api/robots', function() {
  6. });
  7. //Searches for robots with $name in their name
  8. $app->get('/api/robots/search/{name}', function($name) {
  9. });
  10. //Retrieves robots based on primary key
  11. $app->get('/api/robots/{id:[0-9]+}', function($id) {
  12. });
  13. //Adds a new robot
  14. $app->post('/api/robots', function() {
  15. });
  16. //Updates robots based on primary key
  17. $app->put('/api/robots/{id:[0-9]+}', function() {
  18. });
  19. //Deletes robots based on primary key
  20. $app->delete('/api/robots/{id:[0-9]+}', function() {
  21. });
  22. $app->handle();

每个路由定义和HTTP方法名称相同,,我们通过路由匹配模式作为第一个参数,其次是一个处理程序。在这种情况下,处理程序是一个匿名函数。例如路由: ‘/api/robots/{id:[0-9]+}’中显式地设置“id”参数必须是一个数字格式的。

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.

当一个定义好路由匹配请求的URI时,应用程序执行相应的处理程序。

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

创建数据模型

我们的API提供了“机器人”的信息,这些数据是存储在数据库中。下面的模型允许我们以面向对象的方式访问数据表。我们使用内置的验证器和简单的验证实现一些业务规则。这样做可以更加简单的保存数据并满足应用程序要求:

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:

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

现在,我们必须建立一个应用程序中数据模型使用的连接,并加载好它:

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

  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->registerDirs(array(
  9. __DIR__ . '/models/'
  10. ))->register();
  11. $di = new FactoryDefault();
  12. //Set up the database service
  13. $di->set('db', function(){
  14. return new PdoMysql(array(
  15. "host" => "localhost",
  16. "username" => "asimov",
  17. "password" => "zeroth",
  18. "dbname" => "robotics"
  19. ));
  20. });
  21. //Create and bind the DI to the application
  22. $app = new Micro($di);

获取数据

第一个“处理程序”,实现返回所有可用的机器人的方法。让我们用PHQL执行这个简单的查询返回JSON格式的结果:

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:

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

PHQL 让我们使用高级的、面向对象的SQL语言编写查询,程序内部取决于我们正在使用的数据库系统转换成正确的SQL语句。下面代码匿名函数中的“use”允许我们将一些变量从全球传到局部作用域。

PHQL, 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:

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

按字段“id”搜索很相似,在这种情况下,我们也返回结果是否找到相关机器人:

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

插入数据

将请求中的数据作为JSON串,我们使用PHQL插入数据:

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

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

更新数据

数据更新和插入类似,id被传递过去指明哪个机器人必须被更新。

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

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

删除数据

数据删除与更新相近。“id”作为参数传递过去指明哪个机器人必须被删除:

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

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

测试应用

使用curl_ 测试我们的路由是否正常工作:

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. HTTP/1.1 200 OK
  3. Date: Wed, 12 Sep 2012 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 http://localhost/my-rest-api/api/robots/search/Astro
  2. HTTP/1.1 200 OK
  3. Date: Wed, 12 Sep 2012 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"}]

通过id获得机器人:

Obtain a robot by its id:

  1. curl -i -X GET http://localhost/my-rest-api/api/robots/3
  2. HTTP/1.1 200 OK
  3. Date: Wed, 12 Sep 2012 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. http://localhost/my-rest-api/api/robots
  3. HTTP/1.1 201 Created
  4. Date: Wed, 12 Sep 2012 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. http://localhost/my-rest-api/api/robots
  3. HTTP/1.1 409 Conflict
  4. Date: Wed, 12 Sep 2012 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 PUT -d '{"name":"ASIMO","type":"humanoid","year":2000}'
  2. http://localhost/my-rest-api/api/robots/4
  3. HTTP/1.1 409 Conflict
  4. Date: Wed, 12 Sep 2012 08:48:01 GMT
  5. Server: Apache/2.2.22 (Unix) DAV/2
  6. Content-Length: 104
  7. Content-Type: text/html; charset=UTF-8
  8. {"status":"ERROR","messages":["Value of field 'type' must be part of
  9. list: droid, mechanical, virtual"]}

最后删除一个机器人:

Finally, delete a robot:

  1. curl -i -X DELETE http://localhost/my-rest-api/api/robots/4
  2. HTTP/1.1 200 OK
  3. Date: Wed, 12 Sep 2012 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"}

结论

就像我们上面看到的一样使用phalcon创建一个RESTful API非常的简单。接下来我们会讲解到如何使用微应用和:doc:`PHQL <phql>`语言。

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