在 Codename: BlueMix 上使用 PHP 和 MySQL 构建和部署 REST API

REST API 越来越受与库无关的数据访问和操作的欢迎,因为它们易于理解,可以快速编码,并且可以通过内置 HTTP 支持在所有编程语言中使用。本文展示了在 Codename: BlueMix 上如何使用 PHP 和 MySQL 构建并立即部署 REST API。本文使用 Bullet 微型框架和 Eloquent ORM 以及 PHP Cloud Foundry 构建包和绑定的 MySQL 服务实例来实现 API。

简介

如果您正在构建与在线产品或服务交互的应用程序,那么很可能您正在使用 REST API 提取数据或推入数据。在过去几年里,REST API 越来越受欢迎,因为它们易于理解,可以快速编码,并且可以通过内置 HTTP 支持在所有编程语言中使用。

如果您正在开发自己的云服务,那么 REST API 是一种促进数据共享和重用的出色方式。如果允许外部开发人员通过 REST 访问您的数据,他们就可以轻松地构建酷炫的新应用程序,并以一些有趣的方式利用您的产品或数据。考虑一下围绕 Facebook、Twitter 或 Instagram 开发的应用程序生态系统,好处显而易见。

“如果允许外部开发人员通过 REST 访问您的数据,他们就可以轻松地构建酷炫的新应用程序,并以一些有趣的方式利用您的产品或数据。”

本文提供了一个速成课程,介绍了如何使用 PHP 微型框架 Bullet 创建 REST API。除了解释如何实现 4 种基本的 REST 方法之外,本文还介绍了如何为常见的功能(比如 API 身份验证和多种数据格式支持)添加支持,然后将 API 部署到 BlueMix。来吧,让我们开始吧!


了解 REST API 基础知识

阅读:关于 REST 的 Wikipedia 页面

首先,我们需要花费几分钟时间了解一下 REST,即 Representational State Transfer(具象状态传输)。REST 是一种基于 “资源” 和 “操作” 的 API 开发风格。资源是引用想要执行操作的对象或实体的 URL(例如,/users 或 /photos),而操作是 4 个 HTTP 动词之一:

  • GET(检索)
  • POST(创建)
  • PUT (更新)
  • DELETE(删除)

下面的示例明确说明了这一点。假设您有一个专为跟踪软件缺陷设计的应用程序,而且您想轻松地重用和操作应用程序数据库中的数据。您已经显示了一个 URL 端点(将其称为 /bugs),并允许外部开发人员使用不同的 HTTP 方法和内容访问此端点(例如,使用 GET /bugs 列出所有缺陷,或者是使用 DELETE /bugs/78 删除编号为 78 的缺陷)。

基于 HTTP 方法和内容,可以推导出正在请求的操作,并对数据采取相应的操作。下面是一些示例:

  • GET /documents:检索一个文档列表。
  • GET /bugs/123:检索编号为 123 的缺陷。
  • POST /photos:使用 POST 请求正文创建一个新照片。
  • PUT /photos/123:使用 PUT 请求正文更新编号为 123 的照片。
  • DELETE /orders/123:删除编号为 123 的订单。

您需要的内容

本文中开发的示例 REST API 假设一个产品数据库,并侧重于使用常规 REST 协定支持您检索、添加、删除和更新这些产品。为简单起见,Product 资源仅有三个属性:惟一的标识符、名称和价格。我还进一步假设产品数据保存在一个 MySQL 数据库中,并且 API 请求和响应是使用 JSON 编码的(尽管在后面的部分中,我还介绍了使用 XML 作为一种替代方法)。

下面是您需要的内容:


步骤 1:设置应用程序数据库

使用下列 MySQL 表定义和示例数据设置应用程序数据库。

  • 如果您只在本地进行开发和部署,那么可以使用这种方法针对要连接的 API 初始化 MySQL 数据库表。
  • 如果在 BlueMix 上进行部署,那么暂时可以跳过这一步;完成了步骤 7 在 BlueMix 上初始化和绑定 MySQL 服务实例后,再返回到这一步。
CREATE TABLE IF NOT EXISTS `products` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `price` decimal(5,2) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

INSERT INTO `products` (`id`, `name`, `price`) VALUES
(1, 'Garden spade', 15.99),
(2, 'Cotton hammock', 54.50),
(3, 'Single airbed', 35.49);

步骤 2:安装 Bullet 和 Eloquent

下一步是下载并设置 Bullet 微型框架。为什么要使用 Bullet 呢?因为它包含一个灵活的 URL 路由器,可以轻松地在自定义 URL 上创建和响应不同的 HTTP 方法,它还包含一个嵌套路由回调,可以简化常见重复任务(比如 API 身份验证)的执行。

为了简化数据库访问,我将使用 Eloquent,这是一个受欢迎的对象关系映射器(Object Relational Mapper,ORM),它使用 ActiveRecord 实现来简化数据库记录的使用。Eloquent 是 Laravel PHP 框架的一部分,但它也可以单独使用。

我将使用 Composer(PHP 依赖关系管理器)来下载和设置 Bullet 与 Eloquent。下面是 Composer 配置文件,可以将它保存到 $APP_ROOT/composer.json。在本文中,$APP_ROOT 指应用程序工作目录。

{
    "require": {
        "vlucas/bulletphp": "*",
        "illuminate/database": "*"
    }
}

现在,可以使用 Composer 的下列命令安装 Bullet 和 Eloquent:

shell> php composer.phar install

为了简化应用程序访问,还可以在开发环境中定义新的虚拟主机,并将其文档根目录指定为 $APP_ROOT。尽管此步骤是可选的,但建议使用它,因为它在 BlueMix 上创建了一个更接近目标部署环境的副本。

  • 要在 Apache 中为应用程序设置一个命名的虚拟主机,请打开 Apache 配置文件(httpd.conf 或 httpd-vhosts.conf)并添加下列行。
	NameVirtualHost 127.0.0.1
	<VirtualHost 127.0.0.1>
	    DocumentRoot "/usr/local/apache/htdocs/api"
	    ServerName api.localhost
	</VirtualHost>
  • 要在 nginx 中为应用程序设置一个命名的虚拟主机,请打开 nginx 配置文件 (nginx.conf) 并添加下列行。
	server {
	    server_name api.localhost;
	     root /usr/local/apache/htdocs/api;
	     try_files $uri /index.php;
	     
	     location ~ \.php$ {
	        try_files $uri =404;            
	        include fastcgi_params;
	fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        # assumes you are using php-fcgi
        fastcgi_pass 127.0.0.1:90;
	    }        
	}

这些行定义一个新的虚拟主机,http://api.localhost/,它的文档根目录对应于 $APP_ROOT 目录(记住更新此目录以反映您自己的本地设置)。重启 Web 服务器以激活这些新的设置。请注意,您可能需要更新网络的本地 DNS 服务器,让它知道新的主机。


步骤 3:处理资源集合的 GET 请求

Bullet 每次解析一个 URL 段并使用回调响应特定的路径或参数模式。可以在每个 HTTP 方法上定义回调,这允许您对 GET 请求而不是 POST 请求执行不同的操作。

典型的 REST API 支持两种 GET 请求:第一种请求针对资源集合 (GET /products) ,而第二种请求针对具体资源 (GET /products/123)。首先编写代码来处理第一种场景:使用数据库的所有可用产品列表响应 GET /products 请求。使用下列代码更新 $APP_ROOT/index.php 文件。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';


// initialize application
$app = new Bullet\App();
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
    // GET /v1/products
    // list all products
    $app->get(function() use ($app)  {
      $products = Product::all();
      return $products->toArray();
    });

  });
    
});

echo $app->run(new Bullet\Request());

第一步是初始化 Composer 自动加载器,它负责根据需要加载所需的类。然后,创建一个新的 Bullet\App 对象来表示应用程序,并使用此应用程序对象的 path() 方法来设置端点 /v1/products 的嵌套 URL 路径。最里面的 path() 回调定义此端点支持的方法 — 在本例中,使用应用程序对象的 get() 方法来定义 GET 请求的处理程序。

路由处理程序使用 Eloquent 模型返回数据库的所有产品。由于此处理程序返回的是一个数组,因此 Bullet 自动假设响应格式应该是 JSON,使用 'Content-Type: application/json' 标头和 200 OK 成功代码来转换此数组,并将它发送回客户端。如果有任何错误,异常处理程序会捕捉错误,并使用 500 Internal Server Error 和一个包含错误消息的 JSON 正文将错误返回给客户端。

当收到传入请求时,通过执行 Bullet 应用程序,应用程序对象的 run() 方法(如上一个清单的最后一行所示)将所有内容聚集在一起。

此时,您可能会认为这一切都是有意义的,除了一个问题:此 Eloquent 模型来自何处?下一个清单会将会说明这一点,使用 GET 处理程序添加使用 Eloquent 所需的代码,如上所述。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// use Eloquent ORM
use Illuminate\Database\Capsule\Manager as Capsule;  
use Illuminate\Database\Schema\Blueprint as Schema;  
 
// create model for Eloquent ORM mapped to REST API resource
class Product extends Illuminate\Database\Eloquent\Model {
  public $timestamps = false;
}

// get MySQL service configuration from BlueMix
$services = getenv("VCAP_SERVICES");
$services_json = json_decode($services, true);
$mysql_config = $services_json["mysql-5.5"][0]["credentials"];
$db = $mysql_config["name"];
$host = $mysql_config["host"];
$port = $mysql_config["port"];
$username = $mysql_config["user"];
$password = $mysql_config["password"];

// initialize Eloquent ORM
$capsule = new Capsule;
 
$capsule->addConnection(array(
  'driver'    => 'mysql',
  'host'      => $host,
  'port'      => $port,
  'database'  => $db,
  'username'  => $username,
  'password'  => $password,
  'charset'   => 'utf8',
  'collation' => 'utf8_unicode_ci',
  'prefix'    => ''
));

$capsule->setAsGlobal();
$capsule->bootEloquent();


// initialize application
$app = new Bullet\App();
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
    // GET /v1/products
    // list all products
    $app->get(function() use ($app)  {
      $products = Product::all();
      return $products->toArray();
    });

  });
    
});

echo $app->run(new Bullet\Request());

Eloquent 随附提供了一个 Capsule 管理器实例,旨在简化 Laravel 外部的 Eloquent 使用。上一个清单演示了实际的 Capsule 实例,建立了一个到 MySQL 数据库的新连接,并使 Capsule 实例在全局可用。

由于本文的最终目的是将应用程序部署到 BlueMix,因此会从特殊的 BlueMix VCAP_SERVICES 环境变量提取 MySQL 连接的凭据;但是,如果您打算在本地进行部署,那么可以使用特定于本地数据库服务器的值取代这些值。

您还会注意到扩展 Illuminate\Database\Eloquent\Model 类的 Product 模型。此 Product 模型提供了许多预定义的方法,可以简化相应数据库记录的使用。您已经看到了其中的一个实际方法 —Product::all()—,它负责在内部生成对 "return all products in the database" 的 SELECT 查询。

下面是成功的 /v1/products GET 请求的结果。

/v1/products 的 GET 请求

步骤 4:处理新资源的 POST 请求

阅读:HTTP 响应状态代码

可以类似的方式处理 POST 请求并将它们转换为新产品记录。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize Eloquent ORM and model
// snipped

// initialize application
$app = new Bullet\App();
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
    // POST /v1/products
    // create new product
    $app->post(function($request) use ($app) {
      $product = new Product();
      $product->name = trim(htmlentities($request->name));
      $product->price = round(trim(htmlentities($request->price)), 2);
      if ($product->name && $product->price) {
        $product->save();
        return $app->response(201, $product->toArray());
      } else {
        return 400;
      }

    });

  });
    
});

echo $app->run(new Bullet\Request());

此清单为 /v1/products 的 POST 请求添加了一个新处理程序。当应用程序收到 POST 请求时,会创建一个空白的 Product 对象,根据请求对象设置 Product 'name' 和 'price',并调用模型的 save() 对象来为数据库添加新记录。

如果成功添加了记录,那么处理程序将会返回 201 Created 状态代码和新 Product 资源的 JSON 表示形式。如果没有成功,则会返回 400 Bad Request 或 500 Internal Server Error 错误代码和一条解释错误的 JSON 消息。在下面查看成功和失败的 POST 请求和响应示例。

/v1/products 的成功 POST 请求/v1/products 的失败 POST 请求

值得注意的一个有趣现象是:在 POST 正文内容中,已创建的新 Product 资源被作为 JSON 对象传递给 API。但是,在 POST 处理程序中,您会看到没有使用 PHP 的 json_decode() 函数来解码 POST 有效负载。这是因为在 POST 或 PUT 请求中遇到 JSON 有效负载时 Bullet 会自动进行解码。然后,可以直接使用 JSON 对象属性作为请求对象的属性。


步骤 5:处理各个资源的 GET、PUT 和 DELETE 请求

阅读:HTTP 响应状态代码

在上一部分中,我介绍了如何处理资源集合的 GET 请求 (GET /v1/products)。但是,REST API 应该还支持使用惟一资源标识符直接访问资源(GET /v1/products/123DELETE /v1/products/123)。这使得可以使用 API 检索、更新或删除各个资源。

与之前看到的 path() 方法(针对静态 URL 路径)类似,Bullet 还包含一种 param() 方法,它旨在处理包含变量的 URL 路径。下列清单在具体资源的 GET 请求处理程序上下文中演示了这一方法。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize Eloquent ORM and model
// snipped

// initialize application
$app = new Bullet\App();
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
      
    $app->param('int', function($request, $id) use($app) {

      $product = Product::find($id);
      if(!$product) {
        return 404;
      }
           
      // GET /v1/products/:id
      // list single product by id
      $app->get(function($request) use($product, $app) {
        return $product->toArray();
      });
           
      
    });        

  });
    
});

echo $app->run(new Bullet\Request());

此清单以 /v1/products/[id] 的形式设置了 URL 的回调处理程序,其中 [id] 是一个变量路径段,表示惟一资源标识符。'int' 参数进一步指定了此变量是一个分配给 $id 变量的整数。

在此清单中,从 URL 路径捕获的资源标识符被传递给了 Product 模型,并且此模型的 find() 方法用于返回相应的数据库记录。然后,Bullet 将记录转换为 JSON 格式并将它返回给客户端。如果没有找到匹配的记录,处理程序会返回 404 Not Found 错误代码。如果存在错误,则异常处理程序会捕获错误并将它传递回 500 Internal Server Error 错误代码。

下面是成功的 GET 请求和响应的结果。

/v1/products/:id 的 GET 请求

有了此结构,针对具体资源添加 DELETE 和 PUT 请求的支持就会非常简单。查看下列清单,此清单包含了此额外功能。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize Eloquent ORM and model
// snipped

// initialize application
$app = new Bullet\App();
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
      
    $app->param('int', function($request, $id) use($app) {

      $product = Product::find($id);
      if(!$product) {
        return 404;
      }
                
      // GET /v1/products/:id
      // list single product by id
      $app->get(function($request) use($product, $app) {
        return $product->toArray();
      });

      // PUT /v1/products/:id
      // update product by id
      $app->put(function($request) use($product, $app) {
        $product->name = trim(htmlentities($request->name));
        $product->price = round(trim(htmlentities($request->price)), 2);
        if ($product->name && $product->price) {
          $product->save();
          return $app->response(200, $product->toArray());
        } else {
          return 400;          
        }
      });
          
      // DELETE /v1/products/:id
      // delete product by id
      $app->delete(function($request) use($product) {
        $product->delete();
        return 204;
      });
      
    });        

  });
    
});

echo $app->run(new Bullet\Request());
  • PUT 请求意味着使用 PUT 请求正文的更新信息更新现有资源。此清单定义 PUT 处理程序,该程序读取 PUT 请求中的 JSON 文档,并使用 Eloquent 更新检索的 Product 对象的属性。然后,会使用此对象的 save() 方法将更新资源保存回数据库,并会将 200 Accepted 服务器响应代码返回给客户端,以此表示操作获得了成功并更新了资源。
  • DELETE 请求意味着要从数据存储区删除 DELETE URL 中引用的资源,而上面的 DELETE 处理程序正在进行这样的操作:它调用检索 Product 对象的 delete() 方法从 MySQL 数据库删除产品记录。在本例中,我们使用了 204 No Content 响应代码和一个空白的响应正文来表示成功。

下列图像显示了成功的 PUT 和 DELETE 请求。

/v1/products/:id 的 PUT 请求/v1/products/:id 的 DELETE 请求

在这里需要注意的一个有趣现象是:上面的三个嵌套路由处理程序都使用了父处理程序中创建的 $product 实例。在父处理程序中定义常见例程或对象,然后在所有子闭包或块中使用它们,这样做可以使得重复的代码变得更少,使维护和更新变得更简单。


步骤 6:支持多种格式

大多数情况下,使用 JSON 请求和响应正文就可以了。但是,如果您想要支持另一种格式(比如 XML)会怎么样?使用 Bullet,您可以为不同格式定义自定义响应器,这样您就可以轻松处理此需求。

为了说明这点,请考虑下一个清单,它为 /v1/products GET 端点的 XML 输出添加了支持:

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize Eloquent ORM and model
// snipped

// function to convert array to XML
function convert_array_to_xml($data) {
  $xml = new SimpleXMLElement('<root/>');
  foreach ($data as $r) {
    $item = $xml->addChild('product');
    $item->addChild('id', $r['id']);
    $item->addChild('name', $r['name']);
    $item->addChild('price', $r['price']);
  }
  return $xml->asXml();
}

// initialize application
$app = new Bullet\App();

$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
    // GET /v1/products[.xml|.json]
    // list all products
    $app->get(function() use ($app)  {

      $products = Product::all();         
      
      // handle requests for XML content
      $app->format('xml', function($request) use($app, $products) {
        return $app->response(200, convert_array_to_xml($products->toArray()))
                      ->header('Content-Type', 'application/xml');
      });
        
      // handle requests for JSON content
      $app->format('json', function($request) use($app, $products) {
        return $products->toArray();
      });  
            
    });
        
  });    
  
});

echo $app->run(new Bullet\Request());

应用程序对象的 format() 方法用于针对 XML 和 JSON 输出定义单独的回调处理程序。当 Bullet 遇到 URL /v1/products.xml 的请求,或者具有 'Accept: application/xml' 标头的 URL /v1/products 请求时,它会自动调用 XML 回调处理程序。在后一种情况下,Bullet 使用了自动内容协商功能,以便了解请求格式并提供相应的输出。

从上一个清单中可以看出,此处理程序仅将从数据库检索到的一系列产品信息转换为 XML 文档,并将它返回给客户端。下列图像显示了输出。

/v1/products.xml 的 GET 请求

要以 JSON 格式获得输出,客户端应该使用 'Accept: application/json' 标头请求 URL /v1/products.json 或 URL /v1/products,如下列输出所示。

/v1/products.json 的 GET 请求

可以类似的方式更新 POST 处理程序,因此它可以接受 XML 和 JSON 格式的输入。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize Eloquent ORM and model
// snipped

// function to convert array to XML
function convert_array_to_xml($data) {
  $xml = new SimpleXMLElement('<root/>');
  foreach ($data as $r) {
    $item = $xml->addChild('product');
    $item->addChild('id', $r['id']);
    $item->addChild('name', $r['name']);
    $item->addChild('price', $r['price']);
  }
  return $xml->asXml();
}

// initialize application
$app = new Bullet\App();

$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
  
    // POST /v1/products[.xml|.json]
    // create new product
    $app->post(function($request) use ($app) {

      // handle requests for XML content
      $app->format('xml', function($request) use($app) {
        $input = simplexml_load_string($request->raw());
        $product = new Product();
        $product->name = trim(htmlentities((string)$input->name));
        $product->price = round(trim(htmlentities((string)$input->price)), 2);
        if ($product->name && $product->price) {
          $product->save();
          return $app->response(201, convert_array_to_xml(array($product->toArray())))
                    ->header('Content-Type', 'application/xml');          
        } else {
          return 400;
        }
      });
        
      // handle requests for JSON content
      $app->format('json', function($request) use($app) {          
        $product = new Product();
        $product->name = trim(htmlentities($request->name));
        $product->price = round(trim(htmlentities($request->price)), 2);
        if ($product->name && $product->price) {
          $product->save();
          return $app->response(201, $product->toArray());
        } else {
          return 400;
        }
      });   

    });   
        
  });    
  
});

echo $app->run(new Bullet\Request());

在这里,使用了 simplexml_load_string() 方法将发送给 /v1/products.xml 的基于 XML 的 POST 对象转换为 PHP 对象,并且使用此对象的属性填充新的 Product 实例。保存之后,会将新创建的 Product 资源的 XML 表示返回给客户端。

下列图像显示了 XML POST 请求和响应的一个示例。

/v1/products.xml 的 POST 请求

如果客户端想使用 JSON,那么可以将 JSON 请求正文发送给 URL /v1/products.json。在本例中,Bullet 自动将 JSON 请求正文解码为对象,此对象可以转换为 Product 实例并保存到数据库。客户端接收所保存 Product 资源的 JSON 表示,如下所示。如前所述,通过将 'Accept: application/json' 标头作为 POST 请求的一部分发送给 URL /v1/products,可以获得相同的结果。

/v1/products.json 的 POST 请求

步骤 7:处理错误并添加身份验证

BulletPHP 随附提供了一个强大的事件处理系统,该系统可以基于特定的服务器代码、响应格式或异常类型来执行自定义操作。为了说明这点,请考虑下一个清单,该清单使用事件处理程序自动捕获异常,并将它们作为用 JSON 编码的数据包发送给客户端。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize application
$app = new Bullet\App();

// global 'Exception' handler event
$app->on('Exception', function($request, $response, Exception $e) use ($app) {
  // send 500 error with JSON info about exception
  $response->status(500);
  $response->content(array(
    'exception' => get_class($e),
    'message' => $e->getMessage()
  ));
});

// route handlers
// snipped

echo $app->run(new Bullet\Request());

下面是 API 如何响应异常的一个示例。

API 异常处理

在处理请求之前或之后,还可以使用此事件处理系统执行操作或自定义事件。一种常见用法是身份验证:可以针对受保护的路由触发自定义的 'authentication required' 事件,然后为此事件编写一个处理程序,以检查发送请求的客户端是否具有访问这些路由所需的凭据。

下一个清单演示了一个简单的示例。在受保护的路由上触发了自定义 'auth' 事件;这将控制转交给了一个事件处理函数,它在进一步处理请求之前检查身份验证凭据(在本例中是 plaintext cookies)。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize application
$app = new Bullet\App();

// runs when 'auth' event is triggered on protected API routes to check that
// credentials (here, plaintext cookies) are present
// replace with more complex authentication function in production
$app->on('auth', function($request, $response) use ($app) {

  if (!$request->cookie('uid') == 'demo' || !$request->cookie('pass') == 'demo') {
    $response->status(401);
    $response->send();
    exit;
  }
  
});

// unprotected API route to set authentication cookies
$app->path('set-auth-cookies', function($request) use ($app) {

  $app->get(function() use ($app)  {
    setcookie('uid', 'demo');
    setcookie('pass', 'demo');
    return 200;
  });
  
});

// protected API route
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
  
    // trigger authentication event
    $app->filter('auth');

    // route handler code
    // snipped
    }
}

echo $app->run(new Bullet\Request());

在此示例场景中,用户可以创建 cookies,方法是请求特殊的 /set-auth-cookies 路径;在生产环境中,用户可能需要通过注册来获得一个 API 密钥,并为每个请求提供它。

下面的图像显示了 API 如何使用 401 Unauthorized 代码响应未授权的请求。

未授权请求的 API 响应

本文顶部链接的 API 演示版本可在未授权的情况下使用。要启用授权,从 Devops Services 下载 API 的源代码,取消相关部分的注释并部署一个新实例。


步骤 8:部署到 BlueMix

阅读:将 Hello World PHP 应用程序部署到 BlueMix 上的 Zend Server

现在已经完成了 API 的所有编码,最后一步是部署它。

  • 如果在本地部署,则已经完成了相应的操作。跳到本部分末尾,因为末尾部分列出了可与 API 交互的一些有用工具。
  • 如果在 BlueMix 上部署,则需要使用 BlueMix 帐户,并需要下载和安装 Cloud Foundry 命令行客户端。按照下面的步骤完成部署过程。

a. 创建应用程序清单

阅读:将最小的 Node.js 应用程序部署到 BlueMix

应用程序清单文件告诉 BlueMix 如何部署应用程序。具体地讲,它指定要使用的 PHP 运行时环境("构建包")。在 $APP_ROOT/manifest.yml 中创建此文件,使用下列信息填充它。

---
applications:
- name: products-api-[random-number]
memory: 256M
instances: 1
host: products-api-[random-number]
buildpack: https://github.com/dmikusa-pivotal/cf-php-build-pack.git

记住更新主机和应用程序名,让它们保持惟一,方法是更改它或为它添加一个随机数字。我使用的是 Cloud Foundry PHP 构建包,但还有其他选项可用。

b. 为 Bullet 设置 URL 路由

阅读:在 IBM BlueMix 上为基于框架的 PHP 应用程序配置 URL 重写

默认情况下,Cloud Foundry PHP 构建包使用 Apache 作为其 Web 服务器。Nginx 是一种轻型替代方法,在本步骤中,我们需要覆盖构建包的默认设置,以便使用 nginx 作为 Web 服务器。在开始之前,记住您可以从项目的 DevOps Services 源代码库获得此部分的所有文件。

首先,需要创建一个 $APP_ROOT/.bp-config 目录,然后使用下列内容创建 $APP_ROOT/.bp-config/options.json。

{
    "WEB_SERVER": "nginx"
}

此时,还必须为 nginx 设置 URL 重写规则,这样才能将 API 路径正确地传递给 Bullet 的 URL 路由器。首先,使用下列内容创建 $APP_ROOT/.bp-config/nginx/server-defaults.conf。

        listen @{VCAP_APP_PORT};
        server_name _;

        fastcgi_temp_path @{TMPDIR}/nginx_fastcgi 1 2;
        client_body_temp_path @{TMPDIR}/nginx_client_body 1 2;
        proxy_temp_path @{TMPDIR}/nginx_proxy 1 2;

        real_ip_header x-forwarded-for;
        set_real_ip_from 10.0.0.0/8;
        real_ip_recursive on;

        try_files $uri /index.php;

然后,使用下列内容创建 $APP_ROOT/.bp-config/nginx/server-locations.conf。

        # Some basic cache-control for static files to be sent to the browser
        location ~* \.(?:ico|css|js|gif|jpeg|jpg|png)$ {
            expires max;
            add_header Pragma public;
            add_header Cache-Control "public, must-revalidate, proxy-revalidate";
        }

        # Deny hidden files (.htaccess, .htpasswd, .DS_Store).
        location ~ /\. {
            deny all;
            access_log off;
            log_not_found off;
        }

        # pass .php files to fastcgi
        location ~ .*\.php$ {
            try_files $uri =404;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_pass php_fpm;
        }

c. 连接到 BlueMix 并部署应用程序

凭借 'cf' 命令行工具使用 IBM id 和密码登录到 BlueMix。

shell> cf api https://api.ng.bluemix.net
shell> cf login

更改到 $APP_ROOT 目录并将应用程序推送到 BlueMix。

shell> cf push

下面是您在此过程中看到的内容示例。

将应用程序部署到 BlueMix

此时,您的应用程序应该已经部署到了 BlueMix。但是还没有完成所有操作!

d. 将 MySQL 服务实例绑定到应用程序

现在已经部署了应用程序,但您仍然需要将它与 MySQL 数据库实例连接起来,这样您的 API 才有一些数据可供使用。为此,访问 BlueMix 管理仪表板并使用您的 id 和密码登录。您应该会看到 'Apps' 菜单栏列出了您的应用程序。

BlueMix 应用程序仪表板

选择您的应用程序,在生成的页面上,使用 'Add new service' 选项将 'mysql' 服务添加到您的应用程序。

在 BlueMix 上将 MySQL 服务绑定到应用程序

您现在应在 BlueMix 管理仪表板中看到 MySQL 服务实例已经绑定到了应用程序。

MySQL 服务实例已经绑定到了 BlueMix 应用程序

具体地讲,如果您查看应用程序细节,应该能够在 VCAP_SERVICES 环境变量中看到 MySQL 访问凭据。

BlueMix 应用程序环境变量

e. 安装示例模式

此时,可以使用如步骤 1 所示的示例模式初始化应用程序数据库。尽管在 BlueMix 环境中没有办法直接运行步骤 1 中的 SQL 命令,但应用程序包含一个名为 /install-schema 的特殊路径,可以通过浏览器请求它,以便设置数据库表和示例数据。尽管本文没有记录此路径,但可以在应用程序的源代码库中找到它。

下面的图像显示了通过 /install-schema 路径成功安装模式的结果。

6. 开始使用 REST API
BlueMix 上的模式安装

部署 REST API 之后,可以向它发送 GET、POST、PUT 和 DELETE 请求。

  • Firefox 的 HttpRequester 和 Chrome 的 Postman 扩展是通过浏览器构建和发送不同 API 请求类型的强大工具。本文中的大部分屏幕截图都是使用 Postman 扩展生成的。
  • 可以使用任意流行的客户端或服务器端编程语言向 API 提交请求:jQuery、PHP、Perl、Java 和 Python(以及其他语言)都支持 HTTP 请求提交和响应处理。
  • 本文还包含使用 Swagger 的生动 API 演示,Swagger 为 API 探索提供了一个基于浏览器的工具。可以使用此演示添加和更新产品,删除产品或检索各个产品。下面的图像显示了实际的 Swagger 前端。Swagger API 浏览器

结束语

如本文所述,BlueMix 为在基于云的平台上创建和部署 REST API 奠定了坚实的基础。通过添加针对常见 REST 协定的 Bullet 灵活支持和 Eloquent 非常直观的 ORM,您可以获得快速制作原型和部署自定义 REST API 所需的一切内容。

您可以从本文的 Devops Services 存储库下载本文实现的所有代码和所用 PHP 构建包的配置文件。建议您获取这些代码并运行它们,然后添加新的功能。我可以保证您不会有任何损失,而且可以学到更多的东西。

参考资料

学习

讨论

  • Codename: BlueMix 资源中心:本专题为您提供了文章、教程、演示等丰富的学习资源,带您由浅入深地了解 BlueMix。此外,还有关于 BlueMix 的最新活动信息。欢迎大家访问学习。
  • 加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development, Cloud computing
ArticleID=972663
ArticleTitle=在 Codename: BlueMix 上使用 PHP 和 MySQL 构建和部署 REST API
publish-date=05292014