REST or REST-like APIs 的特点

  1. 围绕 “资源” 建立的, 用 url 代表资源, 例如 /cats 代表所有的猫,/cats/15 代表 ID 是15的猫。
  2. 与资源的交互只能用 http 动词
  3. 无状态的,请求之间不会持久化会话身份验证,每个请求必须单独进行身份验证
  4. 它是可缓存和一致的, 无论请求者是谁,每个请求除了少数经过身份验证的特定请求外,都应返回同样的结果。
  5. 返回 json

生成 APIs

生成控制器

  1. php artisan make:controller Api\DogsController --api

补充完整代码

// 使用 Eloquent 补充完整代码
class DogsController extends Controller
{
    // 获取列表 GET /api/dogs
    public function index(){
        return Dog::all();
    }

    // 创建  POST /api/dogs with body: {'name':xxx, 'age':xx}
    public function store(Request $request){
        return Dog::create($request->only(['name', 'age']));
    }

    // 显示一个 GET /api/dogs/1 
    pulic function show($id){
           return Dog::findOrFail($id);
    }

    // 更新数据 PATCH /api/dogs/3 with body: {name:'aaa', age:3}
    public function updete(Request $request, $id){
        $dog = Dog::findOrFail($id);
        $dog->update($request->only(['name', 'age']));
        return $dog;
    }

    // 删除数据 DELETE /api/dogs/2
    public function destroy($id){
        Dog::findOrFail($id)->delete();
    }
}

接下来,绑定路由即可

Route::namespace('Api')->group(function(){
    Route::apiResource('dogs', 'DogsController');
});

RESTful API 大功告成~ !

发送与接收 header

发送

Route::get('dogs', function () {
    return respon(Dogs::all())
        ->header('X-Greatness-Index', 12);
})

接受

Route::get('dogs', function (Request $request) {
    var_dump($request->header('Accept'));
});

分页

Route::get('dogs', function(){
    return Dog::paginate(20);

    // 等价于
    return DB::table('dogs')->paginate(20);
});

我们将会得到

GET /dogs        - Return results 1-20  
GET /dogs?page=1 - Return results 1-20
GET /dogs?page=2 - Return results 21-40

输入示例

{
    "current_page": 1,
    "data": [
        {
            'name': 'Fido'
        },
        {
            'name': 'Pickles'
        },
        {
            'name': 'Spot'
        }
    ]
    "first_page_url": "http://myapp.com/api/dogs?page=1",
    "from": 1,
    "last_page": 2,
    "last_page_url": "http://myapp.com/api/dogs?page=2",
    "next_page_url": "http://myapp.com/api/dogs?page=2",
    "path": "http://myapp.com/api/dogs",
    "per_page": 2,
    "prev_page_url": null,
    "to": 2,
    "total": 4
}

排序和过滤

对结果进行排序

// 简单排序 Handles /dogs?sort=name
Route::get('dogs', function(Request $request){
    // 接收排序字段名,默认 'name'
    $sortColumn =  $request->input('sort', 'name');
    return Dog::orderBy($sortColumn)->paginate(20);
});
// 控制排序方向 Handles /dogs?sort=name or /dogs?sort=-name
Route::get('dogs', function(){
    $sortColumn =  $request->input('sort', 'name');

    $sortDirection = starts_with($sortColumn, '-') ? 'desc' : 'asc';
    $sortColumn = ltrim($sortColumn, '-');

    return Dog::orderBy($sortCoulmn, $sortDirection)->paginate(20);
});
// 多字段排序 Handles /dogs?sort=name,-weight
Route::get('dogs', function(Request $request){
    $sorts = explod(',', $request->input('sort', ''));

    $query = Dog::query();

    foreach($sorts as $sortColumn){
        $sortDiretion = starts_with($sortColumn, '-');
        $sortCloumn = ltrim($sortColumn, '-');

        $query->orderBy($sortColumn, $sortDiretion);
    }

    return $query->paginate(20);
});

过滤结果

// 单个过滤 ?filter=breed:Chihuahua
Route::get('dogs', function() {
    $query = Dog::query();

    $query->when(request()->filled('fillter'), function ($query) {
        [$criteria, $value] = explode(':', request('filter'));
        return $query->where($criteria, $value);
    });

    return $query->paginate(20);
});
// 多个过滤 ?filter=breed:chihuahua,color:brown
Route::get('dogs', function(Request $request){
    $query = Dog::query();

    $query->when(request()->filled('filter'), function($query){
        $filters = expode(',', request('filter'));

        foreach($filters as $filter){
            [$criteria, $value] = explode(':', $filter);
            $query->where($criteria, $value); 
        }

        return $query;
    });

    return $query->paginate(20);
});

API 资源 (API Resources)

过去,Laravel中开发 API的首要挑战之一就是,如何转换数据。 简单的 API 可以将 Eloquent 对象作为 json 返回,但是大多数 API会超过这个需求。

Laravel 5.5 之后, 我们可以访问 Eloquent API resource 功能,它是一种定义了怎么把给定类的 Eloquent 对象(或者 对象集合 collection )转变成 API 结果的结构。

创建 资源类

执行命令

php artisan make:resource Dog

将创建一个新的类 app/Http/Resources/Dog.php ,并且有一个方法 toArray() ,如下:

<?php
namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

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

这里,我们使用 toArray() 方法可以获取两个重要的参数,首先, 它可以访问 \Illuminate\Http\Request 对象,因此我们可以根据 请求参数、请求头或者其他重要的东西来自定义响应;其次, 它可以访问整个 Eloquent 对象, 只需通过访问 $this 上的属性和方法即可。

class Dog extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'breed' => $this->breed,
        ];
    }
}

要使用新资源,更新任何返回单个 DogAPI节点,你只需在响应上包裹此资源即可。如下:

use App\Dog;
use App\Http\Resources\Dog as DogResource;

Route::get('dogs/{dogId}', function($dogId){
    return new DogResource(Dog::findOrFail($dogId));
})

资源集合 (Resource Collection)

use App\Dog;
use App\Http\Resource\Dog as DogResource;

Route::get('dogs', function() {
    return DogResource::collection(Dog::all());    
});

这个方法遍历传入的每个条目,然后用 DogResource API 资源转换它,然后返回集合。

对大多数 APIs 来说,已经足够了,但是如果你需要自定义结构或者增加元数据(metadata),你需要创建自定义资源 API 集合。

执行如下 shell 命令行

php artisan make:resource Dogcollection

将生成 App/Http/Resources/DogCollection.php 文件

<?php
namespace App\Http\Resource;

use Illuminate\Http\Resource\Json\ResourceCollection;

class DogCollection extends ResourceCollection
{
    /**
    * Transform the resource collection into an array
    * 
    * @param \Illuminate\Http\Request $request
    * @return array
    */
    public function toArray($request)
    {
        return [
            'data' => $this->collection,
            'links' => [
                'self' => route('dogs.index'),
            ],    
        ];
    }
}

API resource 不同的是,我们处理的是资源集合,而不是单个资源,所以我们可以直接使用 $this->collection获取集合(已经转换过的)。

嵌套关系

API 相对复杂的一点就是 关系嵌套 。最简单的关系嵌套就是增加一个key , 这个 key 设置资源集合。

public function toArray(){
    return [
        'name' => $this->name,
        'breed' => $this->breed,
        'friends' => DogResource::collection($this->friends);
    ];
}

如果你想增加条件判断,只要在请求需要关系时,或者 Eloquent 已经懒加载时,才嵌套关系,则如下:

public function toArray(){
    return [
        'name' => $this->name,
        'breed' => $this->breed,
        // 如果已经懒加载时,加载关系
        'bones' => BoneResource::collection($this->whenLoaded('bones')),
        // 如果 请求中明确指出需要
        'bones' => $this->when(
            $request->get('include') == 'bones',
            BoneResource::collection($this->bones)
        ),
    ];
}

资源 API 使用分页

就像你可以传递一个Eloquent模型集合到资源一样,你也可以传递一个分页器实例。

Route::get('dogs', function(){
    return new DogCollection(Dog::paginate(2));    
});

如果传递一个分页器实例,则转换后的结果将包含分页信息(第一页、最后一页、上一页和下一页)和有关整个集合的元信息。如下:

{
    "data": [
        {
        "name": "Pickles", "breed": "Chorkie",
        }, 
        {
        "breed": "Golden Retriever Mix", 
        }
    ],
    "links": {
        "first": "http://gooddogbrant.com/api/dogs?page=1",
        "last": "http://gooddogbrant.com/api/dogs?page=3",
        "prev": null,
        "next": "http://gooddogbrant.com/api/dogs?page=2"
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 3,
        "path": "http://gooddogbrant.com/api/dogs", "per_page": 2,
        "to": 2,
        "total": 5 
    }
}

条件属性

您还可以指定仅在满足特定条件时,才在资源响应中的包含属性。

public function toArray($request) {
    return [
        'name' => $this->name,
        'breed' => $this->breed,
        'rating' => $this->when(Auth::user()->canSeeRatings(), 12),
    ]; 
}

更多自定义

详细了解如何更多自定义 API 请查看 文档