教程 3: 创建 RESTful风格 API ====================================== 在本节教程中,我们将展示如何使用不同的HTTP方法创建一个简单的 RESTful_ 风格的API。 * 使用HTTP GET方法获取以及检索数据 * 使用HTTP POST方法添加数据 * 使用HTTP PUT方法更新数据 * 使用HTTP DELETE方法删除数据 Defining the API ---------------- API包括以下方法: +--------+----------------------------+----------------------------------------------------------+ | 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 | +--------+----------------------------+----------------------------------------------------------+ 创建应用 ------------------------ RESTful风格的应用程序非常简单,我们用不着使用完整的MVC环境来开发它。在这种情况下,我们只要使用 :doc:`micro application ` 就可以了。 下面的文件结构足够了: .. code-block:: php my-rest-api/ models/ Robots.php index.php .htaccess 首先,我们需要创建一个.htaccess的文件,包含index.php文件的全部重写规则,下面示例就是此文件的全部: 译者注:使用.htaccess文件,前提是指定了你使用的是Apache WEB Sever. .. code-block:: apacheconf RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.php?_url=/$1 [QSA,L] 然后,我们按以下方式创建 index.php 文件: .. code-block:: php handle(); 现在,我们按我们上面的定义创建路由规则: .. code-block:: php get('/api/robots', function() { }); //Searches for robots with $name in their name $app->get('/api/robots/search/{name}', function($name) { }); //Retrieves robots based on primary key $app->get('/api/robots/{id:[0-9]+}', function($id) { }); //Adds a new robot $app->post('/api/robots', function() { }); //Updates robots based on primary key $app->put('/api/robots/{id:[0-9]+}', function() { }); //Deletes robots based on primary key $app->delete('/api/robots/{id:[0-9]+}', function() { }); $app->handle(); 每个API方法都需要定义一个与定义的HTTP方法相同名称的路由规则,第一个参数传递路由规则,第二个是处理程序,在这种情况下,处理程序是一个匿名函数。路由规则 '/api/robots/{id:[0-9]+}',明确设置'id'参数必须是一个数字。 当用户请求匹配上已定义的路由时,应用程序将执行相应的处理程序。 创建模型(Model) ---------------- API需要提供robots的相关信息,这些数据都存储在数据库中。下面的模型使我们以一种面向对象的方式访问数据表。我们需要使用内置的验证器实现一些业务规则。这样做,会使我们对数据更安全的存储放心,以达到我们想要实现的目的: .. code-block:: php validate(new InclusionIn( array( "field" => "type", "domain" => array("droid", "mechanical", "virtual") ) )); //Robot name must be unique $this->validate(new Uniqueness( array( "field" => "name", "message" => "The robot name must be unique" ) )); //Year cannot be less than zero if ($this->year < 0) { $this->appendMessage(new Message("The year cannot be less than zero")); } //Check if any messages have been produced if ($this->validationHasFailed() == true) { return false; } } } 现在,我们来创建数据库连接以便使用这个模型: .. code-block:: php set('db', function(){ return new \Phalcon\Db\Adapter\Pdo\Mysql(array( "host" => "localhost", "username" => "asimov", "password" => "zeroth", "dbname" => "robotics" )); }); $app = new \Phalcon\Mvc\Micro(); //Bind the DI to the application $app->setDI($di); 获取数据 --------------- 第一个"handler"实现通过HTTP GET获取所有可用的robots。让我们使用PHQL执行一个简单的数据查询,并返回JSON数据格式: .. code-block:: php get('/api/robots', function() use ($app) { $phql = "SELECT * FROM Robots ORDER BY name"; $robots = $app->modelsManager->executeQuery($phql); $data = array(); foreach($robots as $robot){ $data[] = array( 'id' => $robot->id, 'name' => $robot->name, ); } echo json_encode($data); }); :doc:`PHQL `,根据我们使用的数据库系统,允许我们使用面向对象的SQL方言,在内部将其转化为普通的SQL语言,此例使用"use"关键词的匿名函数,允许从整体到局部传递变量。 译者注:不了解匿名函数及use语法的,请查看PHP 5.4版本的文档(具体是5.3开始,还是5.4开始我也不太清楚,就不查证了)。 处理程序看起来像这样: .. code-block:: php get('/api/robots/search/{name}', function($name) use ($app) { $phql = "SELECT * FROM Robots WHERE name LIKE :name: ORDER BY name"; $robots = $app->modelsManager->executeQuery($phql, array( 'name' => '%'.$name.'%' )); $data = array(); foreach($robots as $robot){ $data[] = array( 'id' => $robot->id, 'name' => $robot->name, ); } echo json_encode($data); }); 通过字段"id"检索与上例相当类似,在这种情况下,如果没有检索到,会提示未找到。 .. code-block:: php get('/api/robots/{id:[0-9]+}', function($id) use ($app) { $phql = "SELECT * FROM Robots WHERE id = :id:"; $robot = $app->modelsManager->executeQuery($phql, array( 'id' => $id ))->getFirst(); if ($robot==false) { $response = array('status' => 'NOT-FOUND'); } else { $response = array( 'status' => 'FOUND', 'data' => array( 'id' => $robot->id, 'name' => $robot->name ) ); } echo json_encode($response); }); 插入数据 -------------- 客户端提交JSON包装的字符串,我们也使用PHQL插入: .. code-block:: php post('/api/robots', function() use ($app) { $robot = json_decode($app->request->getRawBody()); $phql = "INSERT INTO Robots (name, type, year) VALUES (:name:, :type:, :year:)"; $status = $app->modelsManager->executeQuery($phql, array( 'name' => $robot->name, 'type' => $robot->type, 'year' => $robot->year )); //Check if the insertion was successfull if($status->success()==true){ $robot->id = $status->getModel()->id; $response = array('status' => 'OK', 'data' => $robot); } else { //Change the HTTP status $this->response->setStatusCode(500, "Internal Error")->sendHeaders(); //Send errors to the client $errors = array(); foreach ($status->getMessages() as $message) { $errors[] = $message->getMessage(); } $response = array('status' => 'ERROR', 'messages' => $errors); } echo json_encode($response); }); 更新数据 ------------- 更新数据非常类似于插入数据。传递的"id"参数指明哪个robots将被更新: .. code-block:: php put('/api/robots/{id:[0-9]+}', function($id) use($app) { $robot = json_decode($app->request->getRawBody()); $phql = "UPDATE Robots SET name = :name:, type = :type:, year = :year: WHERE id = :id:"; $status = $app->modelsManager->executeQuery($phql, array( 'id' => $id, 'name' => $robot->name, 'type' => $robot->type, 'year' => $robot->year )); //Check if the insertion was successfull if($status->success()==true){ $response = array('status' => 'OK'); } else { //Change the HTTP status $this->response->setStatusCode(500, "Internal Error")->sendHeaders(); $errors = array(); foreach ($status->getMessages() as $message) { $errors[] = $message->getMessage(); } $response = array('status' => 'ERROR', 'messages' => $errors); } echo json_encode($response); }); 删除数据 ------------- 删除数据非常类似于更新数据。传递的"id"参数指明哪个robot被删除: .. code-block:: php delete('/api/robots/{id:[0-9]+}', function($id) use ($app) { $phql = "DELETE FROM Robots WHERE id = :id:"; $status = $app->modelsManager->executeQuery($phql, array( 'id' => $id )); if($status->success()==true){ $response = array('status' => 'OK'); } else { //Change the HTTP status $this->response->setStatusCode(500, "Internal Error")->sendHeaders(); $errors = array(); foreach ($status->getMessages() as $message) { $errors[] = $message->getMessage(); } $response = array('status' => 'ERROR', 'messages' => $errors); } echo json_encode($response); }); 测试应用 ----------------------- 使用 curl_ 可以测试应用程序中每个操作的正确性: 获取所有robots: .. code-block:: bash curl -i -X GET http://localhost/my-rest-api/api/robots HTTP/1.1 200 OK Date: Wed, 12 Sep 2012 07:05:13 GMT Server: Apache/2.2.22 (Unix) DAV/2 Content-Length: 117 Content-Type: text/html; charset=UTF-8 [{"id":"1","name":"Robotina"},{"id":"2","name":"Astro Boy"},{"id":"3","name":"Terminator"}] 通过名称查找robot: .. code-block:: bash curl -i -X GET http://localhost/my-rest-api/api/robots/search/Astro HTTP/1.1 200 OK Date: Wed, 12 Sep 2012 07:09:23 GMT Server: Apache/2.2.22 (Unix) DAV/2 Content-Length: 31 Content-Type: text/html; charset=UTF-8 [{"id":"2","name":"Astro Boy"}] 通过 id 查找 robot: .. code-block:: bash curl -i -X GET http://localhost/my-rest-api/api/robots/3 HTTP/1.1 200 OK Date: Wed, 12 Sep 2012 07:12:18 GMT Server: Apache/2.2.22 (Unix) DAV/2 Content-Length: 56 Content-Type: text/html; charset=UTF-8 {"status":"FOUND","data":{"id":"3","name":"Terminator"}} 插入一个新的robot: .. code-block:: bash curl -i -X POST -d '{"name":"C-3PO","type":"droid","year":1977}' http://localhost/my-rest-api/api/robots HTTP/1.1 200 OK Date: Wed, 12 Sep 2012 07:15:09 GMT Server: Apache/2.2.22 (Unix) DAV/2 Content-Length: 75 Content-Type: text/html; charset=UTF-8 {"status":"OK","data":{"name":"C-3PO","type":"droid","year":1977,"id":"4"}} 尝试插入一个与存在的robot相同名称的robot: .. code-block:: bash curl -i -X POST -d '{"name":"C-3PO","type":"droid","year":1977}' http://localhost/my-rest-api/api/robots HTTP/1.1 500 Internal Error Date: Wed, 12 Sep 2012 07:18:28 GMT Server: Apache/2.2.22 (Unix) DAV/2 Content-Length: 63 Content-Type: text/html; charset=UTF-8 {"status":"ERROR","messages":["The robot name must be unique"]} 或者使用错误的type值更新一个robot: .. code-block:: bash curl -i -X PUT -d '{"name":"ASIMO","type":"humanoid","year":2000}' http://localhost/my-rest-api/api/robots/4 HTTP/1.1 500 Internal Error Date: Wed, 12 Sep 2012 08:48:01 GMT Server: Apache/2.2.22 (Unix) DAV/2 Content-Length: 104 Content-Type: text/html; charset=UTF-8 {"status":"ERROR","messages":["Value of field 'type' must be part of list: droid, mechanical, virtual"]} 最后,测试删除一个robot数据: .. code-block:: bash curl -i -X DELETE http://localhost/my-rest-api/api/robots/4 HTTP/1.1 200 OK Date: Wed, 12 Sep 2012 08:49:29 GMT Server: Apache/2.2.22 (Unix) DAV/2 Content-Length: 15 Content-Type: text/html; charset=UTF-8 {"status":"OK"} 结论 ---------- 正如你所看到的那样,使用Phalcon开发RESTful风格的API相当容易。在接下来的文档中,我们会具体讲解如何开发微应用(micro applications)以及如何使用 :doc:`PHQL ` 。 .. _curl : http://en.wikipedia.org/wiki/CURL .. _RESTful : http://en.wikipedia.org/wiki/Representational_state_transfer