hds-solutions/laravel-api-helpers

一个用于在Laravel项目中生成API的辅助包

v1.2.0 2024-08-01 11:36 UTC

This package is auto-updated.

Last update: 2024-09-01 11:46:15 UTC


README

此库通过提供方便的类来管理资源集合的过滤、排序、关系加载和分页,简化了构建API控制器的过程。

Latest stable version License Total Downloads Monthly Downloads Required PHP version

功能

  • 易于管理请求查询过滤器,根据允许的列过滤资源集合。
  • 基于允许的列简化资源集合的排序。
  • 方便加载资源集合的额外关系。
  • 支持资源集合的分页。

安装

依赖项

  • PHP >= 8.0
  • Laravel框架 >= 9.0

通过composer

composer require hds-solutions/laravel-api-helpers

用法

要使用此库,您需要创建特定类来扩展提供的抽象类。提供的类包含每个功能(过滤、排序、关系加载和分页)所需逻辑的实现。

ResourceFilters

ResourceFilters 类管理资源集合的查询过滤器。它允许您定义允许的列及其相应的过滤器运算符。

在扩展类中,您可以定义用于过滤的允许列及其允许的运算符列表。

可用的运算符包括

  • eq:转换为 field_name = "value" 过滤器。
  • ne:转换为 field_name != "value" 过滤器。
  • has:转换为 field_name LIKE "%value%" 过滤器。
  • lt:转换为 field_name < "value" 过滤器。
  • lte:转换为 field_name <= "value" 过滤器。
  • gt:转换为 field_name > "value" 过滤器。
  • gte:转换为 field_name >= "value" 过滤器。
  • in:转换为 field_name IN ("value1", "value2", ...) 过滤器。
  • btw:转换为 field_name BETWEEN "value1" AND "value2" 过滤器。

运算符也按字段类型分组

  • string:转换为运算符 eqnehas
  • numeric:转换为运算符 eqneltltegtgteinbtw
  • boolean:转换为运算符 eqne
  • date:转换为运算符 eqneltltegtgtebtw

示例实现

您只需要扩展 ResourceFilters 类并定义允许的过滤列。

namespace App\Http\Filters;

class CountryFilters extends \HDSSolutions\Laravel\API\ResourceFilters {

    protected array $allowed_columns = [
        'name'      => 'string',
        'code'      => 'string',
        'size_km2'  => [ 'gt', 'lt', 'btw' ],
    ];

}

您还可以通过定义与可过滤列同名的方法来覆盖列的默认过滤实现。该方法 必须 有以下参数

  • Illuminate\Database\Eloquent\Builder:查询构建器的当前实例。
  • string:请求的过滤运算符。
  • mixed:过滤器的值。
namespace App\Http\Filters;

use Illuminate\Database\Eloquent\Builder;

class CountryFilters extends \HDSSolutions\Laravel\API\ResourceFilters {

    protected array $allowed_columns = [
        'name'          => 'string',
        'code'          => 'string',
        'size_km2'      => [ 'gt', 'lt', 'btw' ],
        'regions_count' => 'number',
    ];
    
    protected function regionsCount(Builder $query, string $operator, $value): void {
        return $query->whereHas('regions', operator: $operator, count: $value);
    }

}

示例请求

  • 按国家名称过滤

    GET https:///api/countries?name[has]=aus
    Accept: application/json

    示例响应

    {
        "data": [
            {
                "id": 123,
                "name": "Country name",
                "size_km2": 125000,
                ...
            },
            { ... },
            { ... },
            { ... },
            ...
        ],
        "links": {
            ...
        }
        "meta": {
            ...
        }
    }
  • 按国家大小过滤

    GET https:///api/countries?size_km2[btw]=100000,500000
    Accept: application/json

    示例响应

    {
        "data": [
            {
                "id": 123,
                "name": "Country name",
                "size_km2": 125000,
                ...
            },
            { ... },
            { ... },
            { ... },
            ...
        ],
        "links": {
            ...
        }
        "meta": {
            ...
        }
    }
  • 按拥有超过N个地区的国家过滤

    GET https:///api/countries?regions_count[gte]=15
    Accept: application/json

    示例响应

    {
        "data": [
            {
                "id": 123,
                "name": "Country name",
                "size_km2": 125000,
                ...
            },
            { ... },
            { ... },
            { ... },
            ...
        ],
        "links": {
            ...
        }
        "meta": {
            ...
        }
    }

ResourceOrders

ResourceOrders 类管理资源集合的排序。它允许您定义用于排序资源集合的允许列和默认排序字段。

在扩展类中,您可以定义用于排序资源集合的允许列列表。

示例实现

您只需要扩展 ResourceOrders 类并定义允许的排序列。

namespace App/Http/Orders;

class CountryOrders extends \HDSSolutions\Laravel\API\ResourceOrders {

    protected array $default_order = [
        'name',
    ];

    protected array $allowed_columns = [
        'name',
    ];

}

您也可以通过定义一个以可排序列的驼峰式命名的函数来覆盖列的默认排序实现。该函数必须包含以下参数

  • Illuminate\Database\Eloquent\Builder:查询构建器的当前实例。
  • string:排序方向。
namespace App/Http/Orders;

use Illuminate\Database\Eloquent\Builder;

class CountryOrders extends \HDSSolutions\Laravel\API\ResourceOrders {

    protected array $default_order = [
        'name',
    ];

    protected array $allowed_columns = [
        'name',
        'regions_count',
    ];
    
    protected function regionsCount(Builder $query, string $direction): void {
        $query->orderBy('regions_count', direction: $direction);
    }

}

示例请求

请求的排序参数必须遵循以下语法:order[{index}][{direction}]={field}

  • 按国家名称排序

    GET https:///api/countries?order[0][asc]=name
    Accept: application/json

    示例响应

    {
        "data": [
            {
                "id": 123,
                "name": "Country name",
                ...
            },
            { ... },
            { ... },
            { ... },
            ...
        ],
        "links": {
            ...
        }
        "meta": {
            ...
        }
    }
  • 按国家名称和地区数量降序排序

    GET https:///api/countries?order[0][asc]=name&order[1][desc]=regions_count
    Accept: application/json

    示例响应

    {
        "data": [
            {
                "id": 123,
                "name": "Country name",
                ...
            },
            { ... },
            { ... },
            { ... },
            ...
        ],
        "links": {
            ...
        }
        "meta": {
            ...
        }
    }

ResourceRelations

ResourceRelations类管理资源集合的额外关系加载。它允许您指定要加载的允许关系和应始终加载的关系。

在扩展类中,您可以定义可以添加到资源集合中的允许关系的列表。

示例实现

namespace App/Http/Relations;

class CountryRelations extends \HDSSolutions\Laravel\API\ResourceRelations {

    protected array $with_count = [
        'regions',
    ];

    protected array $allowed_relations = [
        'regions',
    ];

}

您还可以捕获已加载的关系以添加过滤器、排序或您需要的任何操作。该函数必须包含以下参数

  • Illuminate\Database\Eloquent\Relations\Relation:正在加载的关系实例。
namespace App/Http/Relations;

class CountryRelations extends \HDSSolutions\Laravel\API\ResourceRelations {

    protected array $with_count = [
        'regions',
    ];

    protected array $allowed_relations = [
        'regions',
    ];
    
    protected function regions(Relation $regions): void {
        $regions->where('active', true);
    }

}

示例请求

  • 加载包含地区关系集合的国家

    GET https:///api/countries?with[]=regions
    Accept: application/json

    示例响应

    {
        "data": [
            {
                "id": 123,
                "name": "Country name",
                "regions_count": 5,
                "regions": [
                    { ... },
                    { ... },
                    { ... },
                    ...
                ]
            },
            { ... },
            { ... },
            { ... },
            ...
        ],
        "links": {
            ...
        }
        "meta": {
            ...
        }
    }

PaginateResults

PaginateResults类处理资源集合的分页。它提供了对结果分页或检索所有记录的支持。

示例请求

  • 请求所有国家
    GET https:///api/countries?all=true
    Accept: application/json
    示例响应
    {
        "data": [
            {
                "id": 123,
                "name": "Country name",
                "regions_count": 5
            },
            { ... },
            { ... },
            { ... },
            { ... },
            { ... },
            { ... },
            { ... },
            { ... },
            { ... },
            { ... },
            { ... }
        ]
    }

控制器实现

以下是一个使用Pipeline外观实现所有先前功能的控制器示例。

namespace App/Http/Controllers/Api;

use App\Models\Country;

use App\Http\Filters;
use App\Http\Relations;
use App\Http\Orders;

use HDSSolutions\Laravel\API\Actions\PaginateResults;

use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Pipeline;

class CountryController extends Controller {

    public function index(Request $request): ResourceCollection {
        return new ResourceCollection(
            Pipeline::send(Country::query())
                ->through([
                    Filters\CountryFilters::class,
                    Relations\CountryRelations::class,
                    Orders\CountryOrders::class,
                    PaginateResults::class,
                ])
                ->thenReturn()
        );
    }
    
    public function show(Request $request, int $country_id): JsonResource {
        return new Resource(
            Pipeline::send(Country::where('id', $country_id))
                ->through([
                    Relations\CountryRelations::class,
                ])
                ->thenReturn()
                ->firstOrFail()
            )
        );
    }

}

更多请求示例

GET https:///api/regions
Accept: application/json

示例响应

{
    "data": [
        {
            "id": 5,
            "name": "Argentina",
            "code": "AR",
            "regions_count": 24
        },
        {
            "id": 1,
            "name": "Canada",
            "code": "CA",
            "regions_count": 13
        },
        {
            "id": 3,
            "name": "Germany",
            "code": "DE",
            "regions_count": 16
        },
        ...
    ],
    "links": {
        "first": "https:///api/regions?page=1",
        "last": "https:///api/regions?page=13",
        "prev": null,
        "next": "https:///api/regions?page=2"
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 13,
        "links": [
            {
                "url": null,
                "label": "&laquo; Previous",
                "active": false
            },
            {
                "url": "https:///api/regions?page=1",
                "label": "1",
                "active": true
            },
            {
                "url": "https:///api/regions?page=2",
                "label": "2",
                "active": false
            },
            {
                "url": null,
                "label": "...",
                "active": false
            },
            {
                "url": "https:///api/regions?page=12",
                "label": "12",
                "active": false
            },
            {
                "url": "https:///api/regions?page=13",
                "label": "13",
                "active": false
            },
            {
                "url": "https:///api/regions?page=2",
                "label": "Next &raquo;",
                "active": false
            }
        ],
        "path": "https:///api/regions",
        "per_page": 15,
        "to": 15,
        "total": 195
    }
}
GET https:///api/regions?name[has]=aus
Accept: application/json

示例响应

{
    "data": [
        {
            "id": 34,
            "name": "Australia",
            "code": "AU",
            "regions_count": 8
        },
        {
            "id": 12,
            "name": "Austria",
            "code": "AT",
            "regions_count": 9
        }
    ],
    "links": {
        ...
    },
    "meta": {
        ...
    }
}
GET https:///api/regions?regions_count[gt]=15&order[][desc]=name
Accept: application/json

示例响应

{
    "data": [
        ...
        {
            "id": 3,
            "name": "Germany",
            "code": "DE",
            "regions_count": 16
        },
        {
            "id": 5,
            "name": "Argentina",
            "code": "AR",
            "regions_count": 24
        },
        ...
    ],
    "links": {
        ...
    },
    "meta": {
        ...
    }
}

附加信息

前后回调

ResourceFiltersResourceOrders类有两个方法(before & after),允许在查询构建器上进行更好的自定义。您可以通过覆盖它们来对查询构建器进行更多操作。

ResourceRequest

ResourceRequest类具有以下功能

  • hash()方法基于查询参数提供一个唯一标识符。
  • authorize()方法是一个正在进行的(WIP)功能,将处理资源访问授权。

缓存请求

您可以使用ResourceRequest类的hash()方法并将其用作缓存键。参数cache被忽略且不用于构建请求标识符。

在以下示例中,我们捕获了cache请求参数以强制清除缓存。

namespace App/Http/Controllers/Api;

use App\Models\Country;

use App\Http\Filters;
use App\Http\Relations;
use App\Http\Orders;

use HDSSolutions\Laravel\API\Actions\PaginateResults;
use HDSSolutions\Laravel\API\ResourceRequest;

use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Pipeline;

class CountryController extends Controller {

    public function index(ResourceRequest $request): JsonResponse | ResourceCollection {
        // forget cached data if is requested
        if ($request->boolean('cache', true) === false) {
            cache()->forget($request->hash(__METHOD__));
        }

        // remember data for 8 hours, using request unique hash as cache key
        return cache()->remember(
            key: $request->hash(__METHOD__),
            ttl: new DateInterval('PT8H'),
            callback: fn() => (new ResourceCollection($request,
                Pipeline::send(Country::query())
                    ->through([
                        Filters\CountryFilters::class,
                        Relations\CountryRelations::class,
                        Orders\CountryOrders::class,
                        PaginateResults::class,
                    ])
                    ->thenReturn()
                )
            )->response($request)
        );
    }

    public function show(Request $request, int $country_id): JsonResponse | JsonResource {
        if ($request->boolean('cache', true) === false) {
            cache()->forget($request->hash(__METHOD__));
        }

        return cache()->remember(
            key: $request->hash(__METHOD__),
            ttl: new DateInterval('PT8H'),
            callback: fn() => (new Resource(
                Pipeline::send(Model::where('id', $country_id))
                    ->through([
                        Relations\CountryRelations::class,
                    ])
                    ->thenReturn()
                    ->firstOrFail()
                )
            )->response($request)
        );
    }

}

安全漏洞

如果您遇到任何安全相关的问题,请随时在问题跟踪器上提交工单。

贡献

欢迎贡献!如果您发现任何问题或想添加新功能或改进,请随时提交拉取请求。

贡献者

许可

此库是开源软件,根据MIT许可证授权。请参阅许可文件以获取更多信息。