Project Versions

Table Of Contents

Previous topic

教程 2: 解读分析 INVO 项目

Next topic

使用依赖注入

This Page

教程 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环境来开发它。在这种情况下,我们只要使用 micro application 就可以了。

下面的文件结构足够了:

my-rest-api/
    models/
        Robots.php
    index.php
    .htaccess

首先,我们需要创建一个.htaccess的文件,包含index.php文件的全部重写规则,下面示例就是此文件的全部:

译者注:使用.htaccess文件,前提是指定了你使用的是Apache WEB Sever.

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

然后,我们按以下方式创建 index.php 文件:

<?php

$app = new \Phalcon\Mvc\Micro();

//define the routes here

$app->handle();

现在,我们按我们上面的定义创建路由规则:

<?php

$app = new Phalcon\Mvc\Micro();

//Retrieves all robots
$app->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的相关信息,这些数据都存储在数据库中。下面的模型使我们以一种面向对象的方式访问数据表。我们需要使用内置的验证器实现一些业务规则。这样做,会使我们对数据更安全的存储放心,以达到我们想要实现的目的:

<?php

use \Phalcon\Mvc\Model\Message;
use \Phalcon\Mvc\Model\Validator\InclusionIn;
use \Phalcon\Mvc\Model\Validator\Uniqueness;

class Robots extends \Phalcon\Mvc\Model
{

    public function validation()
    {
        //Type must be: droid, mechanical or virtual
        $this->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;
        }
    }

}

现在,我们来创建数据库连接以便使用这个模型:

<?php

$di = new \Phalcon\DI\FactoryDefault();

//Set up the database service
$di->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数据格式:

<?php

//Retrieves all robots
$app->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);

});

PHQL,根据我们使用的数据库系统,允许我们使用面向对象的SQL方言,在内部将其转化为普通的SQL语言,此例使用”use”关键词的匿名函数,允许从整体到局部传递变量。

译者注:不了解匿名函数及use语法的,请查看PHP 5.4版本的文档(具体是5.3开始,还是5.4开始我也不太清楚,就不查证了)。

处理程序看起来像这样:

<?php

//Searches for robots with $name in their name
$app->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”检索与上例相当类似,在这种情况下,如果没有检索到,会提示未找到。

<?php

//Retrieves robots based on primary key
$app->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插入:

<?php

//Adds a new robot
$app->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将被更新:

<?php

//Updates robots based on primary key
$app->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被删除:

<?php

//Deletes robots based on primary key
$app->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:

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:

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:

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:

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:

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:

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数据:

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)以及如何使用 PHQL