九、构建一个 RESTful 应用编程接口——电影和演员数据库

设计和开发一个成功的 RESTful API 大多是非常困难的。设计和编写一个成功的 RESTful API 有很多方面;例如,保护和限制应用编程接口。在本章中,我们将通过用 Laravel 编写一个简单的电影和演员应用编程接口来关注 REST 的基础知识。我们将在基本的身份验证系统后面制作一些 JSON 端点,还将学习一些 Laravel 4 技巧。我们将在本章中讨论以下主题:

  • 创建和迁移用户数据库
  • 配置用户模型
  • 添加示例用户
  • 创建和迁移电影数据库
  • 创建电影模型
  • 添加示例电影
  • 创建和迁移参与者数据库
  • 创建演员模型
  • 给电影分配演员
  • 了解身份验证机制
  • 查询应用编程接口

创建和迁移用户数据库

我们假设您已经在位于app/config/database.php文件中定义了数据库凭证。对于这个应用,我们需要一个数据库。您可以通过简单地运行以下 SQL 命令来创建一个新的数据库,或者基本上您可以使用您的数据库管理界面,如 phpMyAdmin:

CREATE DATABASE laravel_api

为应用成功创建数据库后,首先我们需要为应用生成一个应用键。正如您从前面几章中所知道的,这对于我们应用的安全和身份验证类是必要的。为此,首先打开终端,导航到项目文件夹,然后运行以下命令:

php artisian key:generate

如果没有出现错误,我们应该编辑认证类的配置文件。为了使用 Laravel 的内置认证类,我们需要编辑配置文件auth.php,它位于app/config/。该文件包含身份验证工具的几个选项。如果需要更改表名等,可以在auth.php文件中执行更改。默认情况下,Laravel 自带用户模型;你可以看到位于app/models/User.php文件。使用 Laravel 4,我们需要定义哪些字段可以在我们的User模型中填充。让我们编辑app/models/User.php并添加“可填充”数组:

<?php

use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;

class User extends Eloquent implements UserInterface, RemindableInterface {

  /**
   * The database table used by the model.
   *
   * @var string
   */
  protected $table = 'users';

  /**
   * The attributes excluded from the model's JSON form.
   *
   * @var array
   */
  protected $hidden = array('password');

  // Specify which attributes should be mass-assignable
   protected $fillable = array('email', 'password');

  /**
   * Get the unique identifier for the user.
   *
   * @return mixed
   */
  public function getAuthIdentifier()
  {
    return $this->getKey();
  }

  /**
   * Get the password for the user.
   *
   * @return string
   */
  public function getAuthPassword()
  {
    return $this->password;
  }

  /**
   * Get the e-mail address where password reminders are sent.
   *
   * @return string
   */
  public function getReminderEmail()
  {
    return $this->email;
  }

}

基本上,我们需要为我们的 RESTful API 用户提供两个列,它们是:

  • email:此栏存储作者的电子邮件 ID
  • password:此栏用于存储作者密码

现在我们需要几个迁移文件来创建users表,并将作者添加到我们的数据库中。要创建迁移文件,请给出如下命令:

php artisan migrate:make create_users_table --table=users --create

打开最近创建的位于app/database/migrations/的迁移文件。我们需要编辑up()功能如下:

  public function up()
  {
    Schema::create('users', function(Blueprint $table)
    {
      $table->increments('id');
      $table->string('email');
      $table->string('password');
      $table->timestamps();
    });
  }

编辑迁移文件后,请运行migrate命令:

php artisian migrate

如您所知,该命令创建了users表及其列。如果没有出现错误,请查看users表和列的laravel_api数据库。

添加样本用户

现在我们需要为创建一个新的迁移文件,在数据库中添加一些 API 用户:

php artisan migrate:make add_some_users

打开迁移文件,编辑up()功能如下:

public function up()
  {
    User::create(array(
            'email' => 'john@gmail.com',
            'password' => Hash::make('johnspassword'),
          ));
    User::create(array(
            'email' => 'andrea@gmail.com',
            'password' => Hash::make('andreaspassword'),
          ));
  }

现在我们的应用有两个应用编程接口用户。用户可以查询我们的 RESTful 应用编程接口。

创建和迁移电影数据库

对于一个简单的电影和演员应用,基本上我们需要两个表来存储数据。其中之一就是movies桌。该表将包含电影名称及其发行年份。

我们需要一个迁移文件来创建我们的movies表及其列。我们用artisan工具再做一遍。打开您的终端,导航到项目的文件夹,并运行以下命令:

php artisan migrate:make create_movies_table --table=movies --create

打开最近创建的位于app/database/migrations/的迁移文件。我们需要编辑up()功能如下:

  public function up()
  {
    Schema::create('movies', function(Blueprint $table)
    {
      $table->increments('id');
      $table->string('name');
      $table->integer('release_year');
      $table->timestamps();
    });
  }

编辑迁移文件后,运行migrate命令:

php artisian migrate

创建电影模型

正如你所知,对于任何与 Laravel 上的数据库操作相关的事情,使用模型是最好的做法。我们将利用的优势和的雄辩。

将以下代码保存在app/models/下的Movie.php文件中:

<?php
class Movie extends Eloquent {

protected $table = 'movies';

protected $fillable = array('name','release_year');

public function Actors(){

      return $this-> belongsToMany('Actor' , 'pivot_table');
}

}

我们已经用受保护的$table变量设置了数据库表名。此外,我们设置了可编辑列的$fillable变量,并为时间戳设置了一个$timestamps变量,正如我们在前面的章节中已经看到并使用的。模型中定义的变量足以使用 Laravel 的雄辩 ORM。我们将在本章的为电影分配演员部分介绍公共Actor()功能。

我们的电影模型已经准备好了:现在我们需要一个演员模型及其对应的表。

添加样片

现在我们需要创建一个新的迁移文件,为数据库添加一些电影。实际上,您也可以使用数据库播种器来播种数据库。在这里,我们将使用迁移文件来播种数据库。您可以在以下网址查看播种机:

http://laravel.com/docs/migrations#database-seeding

运行以下migrate命令:

php artisan migrate:make add_some_movies

打开迁移文件,编辑up()功能如下:

  public function up()
  {
    Movie::create(array(
            'name' => 'Annie Hall',
        'release_year' => '1977'
          ));

    Movie::create(array(
            'name' => ' Manhattan ',
        'release_year' => '1978'
          ));

Movie::create(array(
            'name' => 'The Shining',
        'release_year' => '1980'
          ));
  }

创建和迁移参与者数据库

我们需要创建一个actors表,其中包含电影演员的名字。我们需要一个迁移文件来创建我们的movies表和列。我们将再次使用artisan工具。让我们打开我们的终端,导航到我们的项目文件夹,并运行以下命令:

php artisan migrate:make create_actors_table --table=actors –create

打开最近创建的位于app/database/migrations/的迁移文件。我们需要编辑up()功能如下:

  public function up()
  {
    Schema::create('actors', function(Blueprint $table)
    {
      $table->increments('id');
      $table->string('name');
      $table->timestamps();
    });
  }

编辑完迁移文件后,运行migrate命令如下:

php artisian migrate

创建演员模型

对于创建演员模型,将以下代码保存为 app/models/下的Movies.php:

<?php
class Actor extends Eloquent {

protected $table = 'actors';

protected $fillable = array('name');

public function Movies(){

      return $this-> belongsToMany('Movies', 'pivot_table');
}

}

给电影分配演员

如你所知,我们使用了演员和电影模型之间的belongsToMany关系。这是因为一个演员可能演过很多电影。一部电影也可能会有很多演员。

正如您将看到的,在本章的前面几节中,我们使用了一个名为pivot_table的透视表。我们也可以使用artisan工具创建透视表。让我们创建它:

php artisan migrate:make create_pivot_table --table=pivot_table --create

打开最近创建的位于app/database/migrations/的迁移文件。我们需要编辑up()功能如下:

  public function up()
  {
    Schema::create('pivot_table', function(Blueprint $table)
    {
      $table->increments('id');
      $table->integer('movies_id');
      $table->integer('actors_id');
      $table->timestamps();
    });  
}

编辑迁移文件后,运行migrate命令:

php artisian migrate

现在,我们需要创建一个新的迁移文件,向数据库中添加一些参与者:

php artisan migrate:make add_some_actors

打开迁移文件,编辑up()功能如下:

  public function up()
  {
    $woody = Actor::create(array(
            'name' => 'Woody Allen'
          ));

    $woody->Movies()->attach(array('1','2'));

    $diane = Actor::create(array(
            'name' => 'Diane Keaton'
          ));

$diane->Movies()->attach(array('1','2'));

$jack = Actor::create(array(
            'name' => 'Jack Nicholson'
        ));

$jack->Movies()->attach(3);

}

让我们抓住的迁移文件。当我们将users附加到movies时,我们必须使用如下所示的电影标识:

$voody = Actor::create(array(
            'name' => 'Woody Allen'
          ));

$voody->Movies()->attach(array('1','2'));

这意味着伍迪·艾伦在两部电影中扮演了一个角色,这两部电影的 ID 分别是12。还有,黛安娜·基顿在那两部电影中都有出演。但是杰克·尼克尔森闪耀中扮演了一个角色,这部电影的 ID 是3。正如我们已经在第 8 章构建一个 Q &一个 Web 应用中阐述的,我们的关系类型是雄辩下面到一个关系。

了解认证机制

像许多其他 API 一样,我们的 API 系统是基于身份验证的。你可能还记得前几章,Laravel 附带了一个认证机制。在本节中,我们将使用 Laravel 的基于模式的路由过滤功能来保护和限制我们的 API。首先,我们需要为我们的应用编辑auth.basic过滤器。

打开位于app/filters.php的路由过滤器配置文件,编辑auth.basic过滤器如下:

Route::filter('auth.basic', function()
{
  return Auth::basic('email');
});

应用编程接口用户应该将他们的电子邮件标识和密码以及他们的请求发送给我们的应用。因为请求,我们编辑过滤器。应用编程接口请求如下:

curl -i –user andrea@gmail.com:andreaspassword localhost/api/getactorinfo/Woody%20Allen

现在,我们需要在我们的路线上应用过滤器。打开位于app/routes.php的路由过滤器配置文件,添加如下代码:

Route::when('*', 'auth.basic');

这个代码表明我们的应用需要对它的每个请求进行身份验证。现在我们需要写下我们的路线。在app/routes.php增加以下几行:

Route::get('api/getactorinfo/{actorname}', array('uses' => 'ActorController@getActorInfo'));
Route::get('api/getmovieinfo/{moviename}', array('uses' => 'MovieController@getMovieInfo'));
Route::put('api/addactor/{actorname}', array('uses' => 'ActorController@putActor'));
Route::put('api/addmovie/{moviename}/{movieyear}', array('uses' => 'MovieController@putMovie'));
Route::delete('api/deleteactor/{id}', array('uses' => 'ActorController@deleteActor'));
Route::delete('api/deletemovie/{id}', array('uses' => 'MovieController@deleteMovie'));

查询 API

我们的 RESTful 路由功能需要两个控制器文件。让我们在app/controllers/下创建两个控制器文件。文件应命名为MovieController.phpActorController.php

从 API 获取电影/演员信息

首先,我们需要getActorInfo()getMovieInfo()功能从数据库中获取演员和电影信息。打开位于app/controllers/ActorController.php文件,并编写以下代码:

<?php

class ActorController extends BaseController {
public function getActorInfo($actorname){

$actor = Actor::where('name', 'like', '%'.$actorname.'%')->first();
if($actor){

$actorInfo = array('error'=>false,'Actor Name'=>$actor->name,'Actor ID'=>$actor->id);
$actormovies = json_decode($actor->Movies);
foreach ($actormovies as $movie) {
$movielist[] = array("Movie Name"=>$movie->name, "Release Year"=>$movie->release_year);
}
$movielist =array('Movies'=>$movielist);
return Response::json(array_merge($actorInfo,$movielist));

}
else{

return Response::json(array(
'error'=>true,
'description'=>'We could not find any actor in database like :'.$actorname
));
}
}
}

接下来,打开位于app/controllers/MovieController.php 文件,并编写以下代码:

<?php

class MovieController extends BaseController {

public function getMovieInfo($moviename){
$movie = Movie::where('name', 'like', '%'.$moviename.'%')->first();
if($movie){

$movieInfo = array('error'=>false,'Movie Name'=>$movie->name,'Release Year'=>$movie->release_year,'Movie ID'=>$movie->id);
$movieactors = json_decode($movie->Actors);
foreach ($movieactors as $actor) {
$actorlist[] = array("Actor"=>$actor->name);
}
$actorlist =array('Actors'=>$actorlist);
return Response::json(array_merge($movieInfo,$actorlist));

}
else{

return Response::json(array(
'error'=>true,
'description'=>'We could not find any movie in database like :'.$moviename
));
}
}
}

功能getActorInfo()getMovieInfo()基本上是用给定的文本在数据库中搜索电影/演员的名字。如果找到这样的电影或演员,它将以 JSON 格式返回。因此,为了从 API 获取参与者信息,我们的用户可以发出如下请求:

curl -i –-user andrea@gmail.com:andreaspassword localhost/api/getactorinfo/Woody

对参与者信息请求的响应如下:

{
   "error":false,
   "Actor Name":"Woody Allen",
   "Actor ID":1,
   "Movies":[
      {
         "Movie Name":"AnnieHall",
         "Release Year":1977
      },
      {
         "Movie Name":"Manhattan",
         "Release Year":1978
      }
   ]
}

对任何电影的请求都类似于这样:

curl -i --user andrea@gmail.com:andreaspassword localhost/api/getmovieinfo/Manhattan

电影信息请求的响应如下:

{
   "error":false,
   "Movie Name":"Manhattan",
   "Release Year":1978,
   "Movie ID":2,
   "Actors":[
      {
         "Actor":"Woody Allen"
      },
      {
         "Actor":"Diane Keaton"
      }
   ]
}

如果任何用户从数据库中不存在的 API 请求电影信息,响应将如下所示:

{
   "error":true,
   "description":"We could not find any movie in database like :Terminator"
}

同样,对于数据库中不存在的参与者,也会有类似的响应:

{
   "error":true,
   "description":"We could not find any actor in database like :Al Pacino"
}

向 API 的数据库发送新电影/演员

我们需要putActor()putMovie()功能来允许用户向我们的数据库中添加新的演员/电影。

打开位于app/controllers/ActorController.php文件,添加以下功能:

public function putActor($actorname)
{

$actor = Actor::where('name', '=', $actorname)->first();
if(!$actor){

$the_actor = Actor::create(array('name'=>$actorname));

return Response::json(array(
'error'=>false,
'description'=>'The actor successfully saved. The ID number of Actor is : '.$the_actor->id
));

}
else{

return Response::json(array(
'error'=>true,
'description'=>'We have already in database : '.$actorname.'. The ID number of Actor is : '.$actor->id
));
}
}

现在打开位于app/controllers/的文件,添加以下功能:

public function putMovie($moviename,$movieyear)
{

$movie = Movie::where('name', '=', $moviename)->first();
if(!$movie){

$the_movie = Movie::create(array('name'=>$moviename,'release_year'=>$movieyear));

return Response::json(array(
'error'=>false,
'description'=>'The movie successfully saved. The ID number of Movie is : '.$the_movie->id
));

}
else{

return Response::json(array(
'error'=>true,
'description'=>'We have already in database : '.$moviename.'. The ID number of Movie is : '.$movie->id
));
}
}

功能putActor()putMovie()基本上是用给定的文本在数据库中搜索电影/演员的名字。如果找到了电影或演员,函数会以 JSON 格式返回它的标识,否则它会创建新的演员/电影,并用新的记录标识进行响应。因此,为了在 API 数据库中创建一个新的参与者,我们的用户可以发出如下请求:

curl –i –X PUT –-user andrea@gmail.com:andreaspassword localhost/api/addactor/Al%20Pacino

电影信息请求的响应如下:

{
   "error":false,
   "description":"The actor successfully saved. The ID number of Actor is : 4"
}

如果任何应用编程接口用户试图添加现有的参与者,应用编程接口将做出如下响应:

{
   "error":true,
   "description":"We have already in database : Al Pacino. The ID number of Actor is : 4"
}

另外,在 API 数据库中创建新电影的响应应该如下:

curl -i –X PUT –-user andrea@gmail.com:andreaspassword localhost/api/addmovie/The%20Terminator/1984

对该请求的答复如下:

{
   "error":false,
   "description":"The movie successfully saved. The ID number of Movie is : 4"
}

如果任何应用编程接口用户试图添加现有的参与者,应用编程接口将做出如下响应:

{
   "error":true,
   "description":"We have already in database : The Terminator. The ID number of Movie is : 4"
}

从 API 中删除电影/演员

现在我们需要deleteActor()deleteMovie() 功能来允许用户向我们的数据库中添加新的演员/电影。

打开app/controllers/下的ActorController.php文件,增加以下功能:

public function deleteActor($id)
{

$actor = Actor::find($id);
if($actor){

$actor->delete();

return Response::json(array(
'error'=>false,
'description'=>'The actor successfully deleted : '.$actor->name
));

}
else{

return Response::json(array(
'error'=>true,
'description'=>'We could not find any actor in database with ID number :'.$id
));
}
}

添加功能后,位于app/controllers/ActorController.php中的内容应该如下所示:

<?php
class ActorController extends BaseController
{
    public function getActorInfo($actorname)
    {
        $actor = Actor::where('name', 'like', '%' . $actorname . '%')->first();
        if ($actor)
        {
            $actorInfo   = array(
                'error' => false,
                'Actor Name' => $actor->name,
                'Actor ID' => $actor->id
            );
            $actormovies = json_decode($actor->Movies);
            foreach ($actormovies as $movie)
            {
                $movielist[] = array(
                    "Movie Name" => $movie->name,
                    "Release Year" => $movie->release_year
                );
            }
            $movielist = array(
                'Movies' => $movielist
            );
            return Response::json(array_merge($actorInfo, $movielist));
        }
        else
        {
            return Response::json(array(
                'error' => true,
                'description' => 'We could not find any actor in database like :' . $actorname
            ));
        }
    }
    public function putActor($actorname)
    {
        $actor = Actor::where('name', '=', $actorname)->first();
        if (!$actor)
        {
            $the_actor = Actor::create(array(
                'name' => $actorname
            ));
            return Response::json(array(
                'error' => false,
                'description' => 'The actor successfully saved. The ID number of Actor is : ' . $the_actor->id
            ));
        }
        else
        {
            return Response::json(array(
                'error' => true,
                'description' => 'We have already in database : ' . $actorname . '. The ID number of Actor is : ' . $actor->id
            ));
        }
    }
    public function deleteActor($id)
    {
        $actor = Actor::find($id);
        if ($actor)
        {
            $actor->delete();
            return Response::json(array(
                'error' => false,
                'description' => 'The actor successfully deleted : ' . $actor->name
            ));
        }
        else
        {
            return Response::json(array(
                'error' => true,
                'description' => 'We could not find any actor in database with ID number :' . $id
            ));
        }
    }
}

现在我们需要一个类似于MovieController的功能。打开app/controllers/下的MovieController.php文件,添加以下功能:

public function deleteMovie($id)
{

$movie = Movie::find($id);
if($movie){

$movie->delete();

return Response::json(array(
'error'=>false,
'description'=>'The movie successfully deleted : '.$movie->name
));

}
else{

return Response::json(array(
'error'=>true,
'description'=>'We could not find any movie in database with ID number :'.$id
));
}
}

添加功能后,位于 app/controllers/ActorController. php下的内容应该如下图所示:

<?php
class  extends BaseController
{
    public function getMovieInfo($moviename)
    {
        $movie = Movie::where('name', 'like', '%' . $moviename . '%')->first();
        if ($movie)
        {
            $movieInfo   = array(
                'error' => false,
                'Movie Name' => $movie->name,
                'Release Year' => $movie->release_year,
                'Movie ID' => $movie->id
            );
            $movieactors = json_decode($movie->Actors);
            foreach ($movieactors as $actor)
            {
                $actorlist[] = array(
                    "Actor" => $actor->name
                );
            }
            $actorlist = array(
                'Actors' => $actorlist
            );
            return Response::json(array_merge($movieInfo, $actorlist));
        }
        else
        {
            return Response::json(array(
                'error' => true,
                'description' => 'We could not find any movie in database like :' . $moviename
            ));
        }
    }
    public function putMovie($moviename, $movieyear)
    {
        $movie = Movie::where('name', '=', $moviename)->first();
        if (!$movie)
        {
            $the_movie = Movie::create(array(
                'name' => $moviename,
                'release_year' => $movieyear
            ));
            return Response::json(array(
                'error' => false,
                'description' => 'The movie successfully saved. The ID number of Movie is : ' . $the_movie->id
            ));
        }
        else
        {
            return Response::json(array(
                'error' => true,
                'description' => 'We have already in database : ' . $moviename . '. The ID number of Movie is : ' . $movie->id
            ));
        }
    }
    public function deleteMovie($id)
    {
        $movie = Movie::find($id);
        if ($movie)
        {
            $movie->delete();
            return Response::json(array(
                'error' => false,
                'description' => 'The movie successfully deleted : ' . $movie->name
            ));
        }
        else
        {
            return Response::json(array(
                'error' => true,
                'description' => 'We could not find any movie in database with ID number :' . $id
            ));
        }
    }
}

功能deleteActor()deleteMovie()基本上在数据库中搜索具有给定 ID 的电影/演员。如果有电影或演员,API 会删除演员/电影,并以 JSON 格式返回状态。因此,为了从 API 中删除一个参与者,我们的用户可以发出如下请求:

curl –I –X DELETE –-user andrea@gmail.com:andreaspassword localhost/api/deleteactor/4

对该请求的答复如下:

{
   "error":false,
   "description":"The actor successfully deleted : Al Pacino"
}

此外,从应用编程接口数据库中删除电影的响应应该如下:

curl –I –X DELETE –-user andrea@gmail.com:andreaspassword localhost/api/deletemovie/4

对该请求的答复如下:

{
   "error":false,
   "description":"The movie successfully deleted : The Terminator"
}

如果任何应用编程接口用户试图从不存在的应用编程接口数据库中删除电影/演员,应用编程接口将做出如下响应:

{
   "error":true,
   "description":"We could not find any movie in database with ID number :17"
}

对于删除不存在的参与者,响应如下:

{
   "error":true,
   "description":"We could not find any actor in database with ID number :58"
}

总结

在这一章中,我们将重点放在 REST 的基础上,用 Laravel 编写一个简单的电影和演员应用编程接口。我们在一个基本的身份验证系统后面制作了一些 JSON 端点,并学习了一些 Laravel 4 技巧,同时本章使用了类似于基于模式的路由过滤的东西。如您所见,使用 Laravel 开发和保护 RESTful 应用非常容易。在下一章中,我们将在编写一个简单的电子商务应用时,介绍 Laravel 中更有效的方法。