关于Laravel API的资源教程

170 阅读7分钟

在本教程中,我们将使用Laravel和新的Eloquent API资源功能建立一个完整的json crud api。当你想用Laravel构建整个应用程序的后端,而把前端留给Vue,React或Angular时,就可以采用这种方法。通过使用Laravel和Eloquent API资源,你可以随意格式化你的数据响应,同时还可以添加元信息,分页链接和信息,自定义数据包装,条件属性,以及关系。


本地开发环境

在这里,我们只是建立了一个新的项目,名为 lpgvue来开始。为什么叫这个名字?它是Laravel Play Ground / Vue的缩写。从本质上讲,我们只是想有一个本地的开发环境,让我们建立起最新和最伟大的Lavavel版本,这样我们就可以随心所欲地构建和修补。

composer global require laravel/installer
composer create-project -prefer-dist laravel/laravel lpgvue
composer require -dev barryvdh/laravel-ide-helper
php artisan id-helper:generate

通过上面的命令, 我们现在有了一个漂亮的Laravel项目可以使用.


创建数据来工作

Laravel可以很容易地建立表格来保存样本数据.除了Migrations, 你还可以设置数据库播种,以快速填充这些表的数据,以便你可以测试你的API。


添加一个数据库

首先,我们使用一个图形用户界面工具在我们的服务器上添加一个数据库。查看Laravel的数据库设置,了解更多信息。
create database on homestead

当然,我们使用的是Homestead,这是一个用于本地开发环境的主要的Vagrant Box。这意味着我们的用户名和密码分别是homestead和secret。不过我们需要在env文件中指定我们刚刚创建的数据库。
set env mysql credentials


添加迁移

我们可以使用Artisan生成一个迁移,为post表提供一个模式,用来存放我们的帖子。

php artisan make:migration create_posts_table -create=posts

在下面突出显示的几行中, 我们添加了以下字段 titlebody
database/migrations/xxxx_xx_xx_xxxxxx_create_posts_table.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePostsTable extends Migration
{
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('title');
            $table->text('body');
            $table->timestamps();
        });
    }
    
    public function down()
    {
        Schema::dropIfExists('posts');
    }
}

数据库播种

为了启用数据库播种,我们可以再次使用Artisan为我们制作一个播种器类。

php artisan make:searer PostsTableSeeder

我们在下面添加的代码指定要在数据库中制作25个样本帖子。
database/seeds/PostsTableSeeder.php

<?php

use Illuminate\Database\Seeder;

class PostsTableSeeder extends Seeder
{
    public function run()
    {
        factory(App\Post::class, 25)->create();
    }
}

我们还需要更新DatabaseSeeder.php文件,以便现在使用PostTableSeeder类。
database/seeds/DatabaseSeeder.php

<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        $this->call(PostsTableSeeder::class);
    }
}

php artisan make:factory PostFactory

最后,我们需要一个工厂,允许我们指定填充到数据库中的数据的形状。我们希望标题使用40个字符,正文使用200个字符。要设置这样的东西,只需使用这些步骤。

database/factories/PostFactory.php

<?php

/* @var $factory \Illuminate\Database\Eloquent\Factory */

use App\Post;
use Faker\Generator as Faker;

$factory->define(Post::class, function (Faker $faker) {
    return [
        'title' => $faker->text(40),
        'body' => $faker->text(200)
    ];
});

创建一个数据模型

该应用程序还需要一个帖子表的模型。我们可以一次完成模型和迁移,但这种方法也可以。

php artisan make:model Post

就这样,所有的数据设置都完成了。现在,我们要迁移数据库,然后对数据库进行播种。我们开始吧!

php artisan migrate
php artisan db:seed

你应该看到关于迁移成功和数据库播种成功的信息。现在,当使用GUI工具查看数据库时,所有的样本数据都已经填充完毕。

laravel database seeding


构建控制器和路由

现在,我们开始构建实际的代码和文件,为REST API提供动力。首先,我们需要一个基于资源的Post控制器。Artisan让这一切变得简单。


创建一个控制器

php artisan make:controller PostController -resource

最终的文件如下,其中删除了注释和一些方法。在我们的小教程中,我们只使用index(), store(), show(), and destroy()。
app/Http/Controllers/PostController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PostController extends Controller
{
    public function index()
    {
        //
    }
    
    public function store(Request $request)
    {
        //
    }
    
    public function show($id)
    {
        //
    }
    
    public function destroy($id)
    {
        //
    }
}

配置路由文件

Laravel为你提供了两个路由的入口.一个是标准的web请求, 另一个是为api请求定制的.我们在这里建立一个API,所以我们需要在api.php文件中填充我们的路由。代码如下,并附有注释,解释每个路由的目的。

routes/api.php

<?php

use Illuminate\Http\Request;

// List all posts
Route::get('posts', 'PostController@index');

// List a single post
Route::get('post/{id}', 'PostController@show');

// Create a new post
Route::post('post', 'PostController@store');

// Update a post
Route::put('post', 'PostController@store');

// Delete a post
Route::delete('post/{id}', 'PostController@destroy');

请注意,有两条路由指向同一个store()方法。这是因为store()方法可以在使用PUT http动词时更新一个帖子,或者在使用POST http动词时创建一个新帖子。很酷的东西。


创建一个API资源

现在我们可以开始在Laravel中使用API资源.文档中把这个功能描述为一种转换层,位于数据模型层和JSON响应层之间。我们可以继续创建一个新的API资源,像这样。

php artisan make:resource Post

上面的命令创建了一个新的资源文件夹,并填充了以下文件。

app/Http/Resources/Post.php

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class Post extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return parent::toArray($request);
    }
}

这个文件是你的转化器,它允许你以多种方式定制你的api的JSON响应数据。我们现在就可以使用它了。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Post;
use App\Http\Resources\Post as PostResource;

class PostController extends Controller
{
    public function index()
    {
        // Get the posts
        $posts = Post::paginate(5);
        
        // Return collection of posts as a resource
        return PostResource::collection($posts);
    }
    
    public function store(Request $request)
    {
        //
    }
    
    public function show($id)
    {
        //
    }
    
    public function destroy($id)
    {
        //
    }
}

现在我们可以访问 http://lpgvue.io/api/posts浏览器,并看到格式良好的JSON输出。数据被很好地包含在数据属性中,同时还有分页和元信息的链接。非常酷

{
  "data": [
    {
      "id": 1,
      "title": "Sunt quis iure est deserunt ut.",
      "body": "Eos eum accusantium totam maiores ipsum autem recusandae. Dolores in qui officiis culpa eligendi quas. Cupiditate earum perferendis qui quia.",
      "created_at": "2019-05-13 15:43:06",
      "updated_at": "2019-05-13 15:43:06"
    },
    {
      "id": 2,
      "title": "Enim ut cupiditate dolorum quos enim.",
      "body": "Mollitia sequi tempora cupiditate quia. Mollitia aliquid nesciunt provident. Nam reiciendis delectus a est repellendus omnis ut occaecati.",
      "created_at": "2019-05-13 15:43:06",
      "updated_at": "2019-05-13 15:43:06"
    },
    {
      "id": 3,
      "title": "Nihil libero nam quisquam optio.",
      "body": "Aut vitae ut deserunt beatae magnam eaque. Eaque quam rerum dolores ea tempore sint ipsa. Velit mollitia sapiente a. Et ratione quam soluta odit ullam fugit dolores voluptatum.",
      "created_at": "2019-05-13 15:43:06",
      "updated_at": "2019-05-13 15:43:06"
    },
    {
      "id": 4,
      "title": "Quis esse a sit est.",
      "body": "Corporis tempore autem optio odit atque sed. Cupiditate adipisci recusandae doloribus quas. Tempora qui sequi error rerum aperiam voluptas.",
      "created_at": "2019-05-13 15:43:06",
      "updated_at": "2019-05-13 15:43:06"
    },
    {
      "id": 5,
      "title": "Nam velit doloribus cumque qui cumque.",
      "body": "Voluptates culpa quidem omnis soluta animi eaque. Nihil voluptates voluptas quis cum adipisci tenetur. Eveniet voluptatem quo velit ea occaecati ducimus sed.",
      "created_at": "2019-05-13 15:43:06",
      "updated_at": "2019-05-13 15:43:06"
    }
  ],
  "links": {
    "first": "http://lpgvue.io/api/posts?page=1",
    "last": "http://lpgvue.io/api/posts?page=5",
    "prev": null,
    "next": "http://lpgvue.io/api/posts?page=2"
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 5,
    "path": "http://lpgvue.io/api/posts",
    "per_page": 5,
    "to": 5,
    "total": 25
  }
}

定制返回的数据

我们上面创建的生成的资源可以被定制,这样你就可以从API中准确地返回你喜欢的东西。通过像下面这样修改返回语句,现在只发送id、title和body,这使事情变得简单了。

app/Http/Resources/Post.php

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class Post extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'body' => $this->body
        ];
    }
}

下面是访问时的输出结果 http://lpgvue.io/api/posts

{
  "data": [
    {
      "id": 1,
      "title": "Sunt quis iure est deserunt ut.",
      "body": "Eos eum accusantium totam maiores ipsum autem recusandae. Dolores in qui officiis culpa eligendi quas. Cupiditate earum perferendis qui quia."
    },
    {
      "id": 2,
      "title": "Enim ut cupiditate dolorum quos enim.",
      "body": "Mollitia sequi tempora cupiditate quia. Mollitia aliquid nesciunt provident. Nam reiciendis delectus a est repellendus omnis ut occaecati."
    },
    {
      "id": 3,
      "title": "Nihil libero nam quisquam optio.",
      "body": "Aut vitae ut deserunt beatae magnam eaque. Eaque quam rerum dolores ea tempore sint ipsa. Velit mollitia sapiente a. Et ratione quam soluta odit ullam fugit dolores voluptatum."
    },
    {
      "id": 4,
      "title": "Quis esse a sit est.",
      "body": "Corporis tempore autem optio odit atque sed. Cupiditate adipisci recusandae doloribus quas. Tempora qui sequi error rerum aperiam voluptas."
    },
    {
      "id": 5,
      "title": "Nam velit doloribus cumque qui cumque.",
      "body": "Voluptates culpa quidem omnis soluta animi eaque. Nihil voluptates voluptas quis cum adipisci tenetur. Eveniet voluptatem quo velit ea occaecati ducimus sed."
    }
  ],
  "links": {
    "first": "http://lpgvue.io/api/posts?page=1",
    "last": "http://lpgvue.io/api/posts?page=5",
    "prev": null,
    "next": "http://lpgvue.io/api/posts?page=2"
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 5,
    "path": "http://lpgvue.io/api/posts",
    "per_page": 5,
    "to": 5,
    "total": 25
  }
}

数据库字段到API响应的映射

Laravel的API资源的这个功能真的很不错。你的数据库字段不必与JSON响应中使用的键完全匹配。当然, 为了保证每个人的理智, 最好的办法是让他们这样做.然而,如果你需要在响应数据中使用自定义属性,你可以这么做。让我们看看这是如何工作的。

app/Http/Resources/Post.php

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class Post extends JsonResource
{
    public function toArray($request)
    {
        return [
            'the_custom_id' => $this->id,
            'the_custom_title' => $this->title,
            'the_custom_body' => $this->body
        ];
    }
}

来自API的JSON响应现在使用这些自定义属性。

{
  "data": [
    {
      "the_custom_id": 1,
      "the_custom_title": "This Post Has Been Updated!",
      "the_custom_body": "Some fresh new updates for you."
    },
    {
      "the_custom_id": 2,
      "the_custom_title": "Enim ut cupiditate dolorum quos enim.",
      "the_custom_body": "Mollitia sequi tempora cupiditate quia. Mollitia aliquid nesciunt provident. Nam reiciendis delectus a est repellendus omnis ut occaecati."
    },
    {
      "the_custom_id": 3,
      "the_custom_title": "Nihil libero nam quisquam optio.",
      "the_custom_body": "Aut vitae ut deserunt beatae magnam eaque. Eaque quam rerum dolores ea tempore sint ipsa. Velit mollitia sapiente a. Et ratione quam soluta odit ullam fugit dolores voluptatum."
    },
    {
      "the_custom_id": 4,
      "the_custom_title": "Quis esse a sit est.",
      "the_custom_body": "Corporis tempore autem optio odit atque sed. Cupiditate adipisci recusandae doloribus quas. Tempora qui sequi error rerum aperiam voluptas."
    },
    {
      "the_custom_id": 6,
      "the_custom_title": "Eum soluta qui quas.",
      "the_custom_body": "Omnis dolor quos unde dolor. Quam ex non voluptates qui harum cumque soluta. Natus rerum deleniti eveniet ad."
    }
  ],
  "links": {
    "first": "http://lpgvue.io/api/posts?page=1",
    "last": "http://lpgvue.io/api/posts?page=5",
    "prev": null,
    "next": "http://lpgvue.io/api/posts?page=2"
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 5,
    "path": "http://lpgvue.io/api/posts",
    "per_page": 5,
    "to": 5,
    "total": 25
  }
}

返回一个单一的资源

现在让我们看看在控制器的show()方法中返回一个单一资源。
app/Http/Controllers/PostController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Post;
use App\Http\Resources\Post as PostResource;

class PostController extends Controller
{
    public function index()
    {
        // Get the posts
        $posts = Post::paginate(5);
        
        // Return collection of posts as a resource
        return PostResource::collection($posts);
    }
    
    public function store(Request $request)
    {
        //
    }
    
    public function show($id)
    {
        // Get a single post
        $post = Post::findOrFail($id);
        
        // Return a single post as a resource
        return new PostResource($post);
    }
    
    public function destroy($id)
    {
        //
    }
}

这是访问时的输出结果 http://lpgvue.io/api/post/5

{
  "data": {
    "id": 5,
    "title": "Nam velit doloribus cumque qui cumque.",
    "body": "Voluptates culpa quidem omnis soluta animi eaque. Nihil voluptates voluptas quis cum adipisci tenetur. Eveniet voluptatem quo velit ea occaecati ducimus sed."
  }
}

添加额外的数据

要在发送的响应中添加一些额外的数据,比如api版本什么的,你可以像我们这里一样设置with()方法。
app/Http/Resources/Post.php

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class Post extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'body' => $this->body
        ];
    }
    
    public function with($request)
    {
        return [
            'version' => '1.0.0',
            'api_url' => url('http://lpgvue/api')
        ];
    }
}

当访问时 http://lpgvue.io/api/post/5的时候,我们现在看到响应中的额外元数据。所以我们可以看到这是一个非常强大的方法,可以准确地定制你希望API产生的东西。

{
  "data": {
    "id": 5,
    "title": "Nam velit doloribus cumque qui cumque.",
    "body": "Voluptates culpa quidem omnis soluta animi eaque. Nihil voluptates voluptas quis cum adipisci tenetur. Eveniet voluptatem quo velit ea occaecati ducimus sed."
  },
  "version": "1.0.0",
  "api_url": "http://lpgvue/api"
}

创建和更新

同样的端点可以用来更新一个帖子,或者完全创建一个新的帖子。唯一的区别是提出请求时使用的HTTP动词。如果使用的是PUT请求,那么就会认为将更新一个帖子。如果使用的是POST请求,那就意味着要创建一个新的帖子。这里的代码允许使用PostController中相同的store()方法进行帖子更新或帖子创建。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Post;
use App\Http\Resources\Post as PostResource;

class PostController extends Controller
{
    public function index()
    {
        // Get the posts
        $posts = Post::paginate(5);
        
        // Return collection of posts as a resource
        return PostResource::collection($posts);
    }
    
    public function store(Request $request)
    {
        //  Allow for post update *or* create a new post
        $post        = $request->isMethod('put') ? Post::findOrFail($request->id) : new Post;
        $post->id    = $request->input('id');
        $post->title = $request->input('title');
        $post->body  = $request->input('body');
        
        if ($post->save()) {
            return new PostResource($post);
        }
    }
    
    public function show($id)
    {
        // Get a single post
        $post = Post::findOrFail($id);
        
        // Return a single post as a resource
        return new PostResource($post);
    }
    
    public function destroy($id)
    {
        //
    }
}

测试一个帖子的更新

当你还没有建立一个正式的前端时,为了测试一个API,你可以使用像Postman这样的伟大工具。使用Postman测试允许我们使用我们选择的HTTP动词向应用程序发送API请求。这里我们将设置一个PUT请求到 http://lpgvue.io/api/post endpoint.这将使我们能够更新一个帖子。第一步是将请求类型设置为PUT,并在请求中添加一个头,指定内容类型为application/json。
laravel and postman api put request

第二步是点击Body标签,选择raw,并在请求中加入json有效载荷。为了更新一个帖子,我们需要指定要更新的数据的ID、标题和正文。
postman json application payload example

最后一步是点击发送按钮,如果一切顺利,你将看到一个响应,在有效载荷中包含更新的帖子。
200ok response to put request


测试一个帖子的创建

现在我们已经熟悉了Postman的基本工作流程,我们也应该能够轻松地创建一个新的帖子。要做到这一点,我们可以设置一个POST请求到相同的端点,即 **http://lpgvue.io/api/post**这个端点,我们刚刚在更新的例子中使用过。注意,我们设置了json的有效载荷,以包括一个新的帖子的标题和正文。我们不需要包括一个ID,因为应用程序会自动分配一个新的ID。
create new resource laravel api postman

当你点击发送时,Postman将发出POST请求,你应该得到一个201创建的响应,在响应的正文中包含新创建的帖子。
201 created new resource success

直接查看我们数据库中的帖子表也会显示新创建的帖子。
new post via api worked


删除一个资源

为了完善我们的API并允许通过JSON Rest Api实现完整的CRUD功能,我们可以增加删除帖子的功能。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Post;
use App\Http\Resources\Post as PostResource;

class PostController extends Controller
{
    public function index()
    {
        // Get the posts
        $posts = Post::paginate(5);
        
        // Return collection of posts as a resource
        return PostResource::collection($posts);
    }
    
    public function store(Request $request)
    {
        //  Allow for post update *or* create a new post
        $post        = $request->isMethod('put') ? Post::findOrFail($request->id) : new Post;
        $post->id    = $request->input('id');
        $post->title = $request->input('title');
        $post->body  = $request->input('body');
        
        if ($post->save()) {
            return new PostResource($post);
        }
    }
    
    public function show($id)
    {
        // Get a single post
        $post = Post::findOrFail($id);
        
        // Return a single post as a resource
        return new PostResource($post);
    }
    
    public function destroy($id)
    {
        // Get the post
        $post = Post::findOrFail($id);
        
        //  Delete the post, return as confirmation
        if ($post->delete()) {
            return new PostResource($post);
        }
    }
}

现在,让我们试着删除id为5的帖子。删除请求是非常简单的。你所要做的就是提供要点击的端点,包括ID,然后点击发送。确保HTTP动词类型被设置为DELETE。
laravel rest delete request

现在,当我们访问 **http://lpgvue.io/api/posts**查看所有的帖子时,注意到它们被依次列为1、2、3、4和6。所以,这意味着我们对帖子ID 5的删除起了作用。

{
  "data": [
    {
      "id": 1,
      "title": "This Post Has Been Updated!",
      "body": "Some fresh new updates for you."
    },
    {
      "id": 2,
      "title": "Enim ut cupiditate dolorum quos enim.",
      "body": "Mollitia sequi tempora cupiditate quia. Mollitia aliquid nesciunt provident. Nam reiciendis delectus a est repellendus omnis ut occaecati."
    },
    {
      "id": 3,
      "title": "Nihil libero nam quisquam optio.",
      "body": "Aut vitae ut deserunt beatae magnam eaque. Eaque quam rerum dolores ea tempore sint ipsa. Velit mollitia sapiente a. Et ratione quam soluta odit ullam fugit dolores voluptatum."
    },
    {
      "id": 4,
      "title": "Quis esse a sit est.",
      "body": "Corporis tempore autem optio odit atque sed. Cupiditate adipisci recusandae doloribus quas. Tempora qui sequi error rerum aperiam voluptas."
    },
    {
      "id": 6,
      "title": "Eum soluta qui quas.",
      "body": "Omnis dolor quos unde dolor. Quam ex non voluptates qui harum cumque soluta. Natus rerum deleniti eveniet ad."
    }
  ],
  "links": {
    "first": "http://lpgvue.io/api/posts?page=1",
    "last": "http://lpgvue.io/api/posts?page=5",
    "prev": null,
    "next": "http://lpgvue.io/api/posts?page=2"
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 5,
    "path": "http://lpgvue.io/api/posts",
    "per_page": 5,
    "to": 5,
    "total": 25
  }
}

Laravel API资源教程摘要

在本教程中,我们使用Laravel API资源功能建立了一个完整的JSON CRUD API。我们可以看到,使用Laravel和它提供的许多工具来建立一个后端API真的很容易。接下来,我们将看看如何建立一个Vue前端,可以消费我们的API。