nvmcommunity/alchemist-restful-api

这是一个帮助您快速获取严格且灵活的基于RESTful的应用程序API接口的库。


README

Project Contributors Project License

Project Stars Project Forks Project Watchers Project Issues

这是一个帮助您快速获取严格且灵活的基于RESTful的应用程序API接口的库。

测试

此软件包已经准备好投入生产,包含193个测试和1151个断言。

./vendor/phpunit/phpunit/phpunit

目录

变更日志

  • 1.0.0: 首次发布
  • 2.0.0: 添加对新方式定义对象和集合字段结构的支持
  • 2.0.1: 添加对AlchemistAdapter的支持,以更改默认组件的请求输入中的参数名
  • 2.0.16: (2024-04-28) 添加更多测试和修复意外的行为
  • 2.0.17: (2024-06-14) 修复字段解析器中的错误模式
  • 2.0.19: (2024-06-14) 字段解析语法支持空格
  • 2.0.20: (2024-06-14) 添加API类的非静态版本:StatefulAlchemistQueryable

简介

Alchemist Restful API是一个帮助您快速获取严格且灵活的基于RESTful的应用程序API接口的库。

该库提供了一套组件,您可以使用它们来构建API接口,包括

  • 字段选择器:允许您的API客户端选择他们想要检索的字段,确保所有检索到的字段都在您的控制之下。
  • 资源筛选:关注检查您的API客户端使用的筛选是否在定义的可筛选列表中。
  • 资源排序:支持基于API客户端指定的排序和方向灵活返回数据。
  • 资源搜索:在筛选时,API客户端需要明确指定筛选标准。然而,在搜索的情况下,API客户端只需要传入要搜索的值,后端将自动从内部定义筛选标准。
  • 资源偏移分页器:支持通过偏移量和限制机制进行分页。

先决条件

安装

composer require nvmcommunity/alchemist-restful-api

Laravel集成

如果您正在使用Laravel,您可以使用名为laravel-eloquent-api的另一个软件包将Alchemist Restful API与Laravel中的Eloquent ORM集成。

composer require nvmcommunity/laravel-eloquent-api

有关如何使用nvmcommunity/laravel-eloquent-api软件包的更多详细信息,请参阅Laravel Eloquent API文档

基本用法

以下是如何使用Alchemist Restful API构建订单API的RESTful API接口的示例。

步骤1:定义API类

您需要定义一个扩展AlchemistQueryable类的API类,并实现字段选择器、资源筛选、资源分页、资源搜索和资源排序的方法。

<?php

use Nvmcommunity\Alchemist\RestfulApi\Common\Integrations\AlchemistQueryable;
use Nvmcommunity\Alchemist\RestfulApi\FieldSelector\Handlers\FieldSelector;
use Nvmcommunity\Alchemist\RestfulApi\ResourceFilter\Handlers\ResourceFilter;
use Nvmcommunity\Alchemist\RestfulApi\ResourceFilter\Objects\FilteringRules;
use Nvmcommunity\Alchemist\RestfulApi\ResourcePaginations\OffsetPaginator\Handlers\ResourceOffsetPaginator;
use Nvmcommunity\Alchemist\RestfulApi\ResourceSearch\Handlers\ResourceSearch;
use Nvmcommunity\Alchemist\RestfulApi\ResourceSort\Handlers\ResourceSort;
/**
 * Example of Order Api Query
 */
class OrderApiQuery extends AlchemistQueryable
{
    /**
     * @param FieldSelector $fieldSelector
     * @return void
     */
    public static function fieldSelector(FieldSelector $fieldSelector): void
    {
        $fieldSelector->defineFieldStructure([
            'id',
            'order_date',
            'order_status',
            'order_items' => [
                'item_id',
                'product_id',
                'price',
                'quantity'
            ]
        ]);
    }

    /**
     * @param ResourceFilter $resourceFilter
     * @return void
     */
    public static function resourceFilter(ResourceFilter $resourceFilter): void
    {
        $resourceFilter->defineFilteringRules([
            FilteringRules::String('product_name', ['eq', 'contains']),
            FilteringRules::Date('order_date', ['eq', 'lte', 'gte'], ['Y-m-d']),
            FilteringRules::Integer('product_id', ['eq']),
            FilteringRules::Boolean('is_best_sales', ['eq']),
        ]);
    }

    /**
     * @param ResourceOffsetPaginator $resourceOffsetPaginator
     * @return void
     */
    public static function resourceOffsetPaginator(ResourceOffsetPaginator $resourceOffsetPaginator): void
    {
        $resourceOffsetPaginator
            ->defineDefaultLimit(10)
            ->defineMaxLimit(1000);
    }

    /**
     * @param ResourceSearch $resourceSearch
     * @return void
     */
    public static function resourceSearch(ResourceSearch $resourceSearch): void
    {
        $resourceSearch
            ->defineSearchCondition('product_name');
    }

    /**
     * @param ResourceSort $resourceSort
     * @return void
     */
    public static function resourceSort(ResourceSort $resourceSort): void
    {
        $resourceSort
            ->defineDefaultSort('id')
            ->defineDefaultDirection('desc')
            ->defineSortableFields(['id', 'created_at']);
    }
}

步骤2:验证输入参数

务必使用validate方法验证从请求输入传递的输入参数。

<?php

use Nvmcommunity\Alchemist\RestfulApi\AlchemistRestfulApi;

// Assuming that the input parameters are passed in from the request input
$input = [
    'fields' => 'id,order_date,order_status,order_items{item_id,product_id,price,quantity}',
    'filtering' => [
        'order_date:lte' => '2023-02-26',
        'product_name:contains' => 'clothes hanger'
    ],
    'search' => 'clothes hanger',
    'sort' => 'id',
    'direction' => 'desc',
    'limit' => 10,
    'offset' => 0,
];

$restfulApi = AlchemistRestfulApi::for(OrderApiQuery::class, $input);

// Check if the input parameters are not valid, the validator will collect all errors and return them to you.
if (! $restfulApi->validate($errorBag)->passes()) {
    // var_dump(json_encode($errorBag->getErrors()));
    
    echo "validate failed"; die();
}

步骤3:好了,所有输入参数都已经经过仔细验证

最后,您将收到的是来自API客户端的字段、筛选、偏移、搜索和排序参数。所有这些都已经经过仔细验证。

// Get all string fields that user want to retrieve
$restfulApi->fieldSelector()->flatFields();

// Get all filtering conditions that client passed in
$restfulApi->resourceFilter()->filtering();

$offsetPaginate = $restfulApi->resourceOffsetPaginator()->offsetPaginate();

// Get pagination limit and offset
$offsetPaginate->getLimit();
$offsetPaginate->getOffset();

$search = $restfulApi->resourceSearch()->search();

// Get search condition (defined in the API class) and search value (provided by user)
$search->getSearchCondition();
$search->getSearchValue();

$sort = $restfulApi->resourceSort()->sort();

// Get sort field and direction
$sort->getSortField();
$sort->getDirection();

每个组件的更多说明

以下是您可以使用来构建API接口的每个组件的详细说明。

字段选择器

字段选择器组件允许您的API客户端选择他们想要检索的字段,确保所有检索的字段都在您的控制范围内。

use Nvmcommunity\Alchemist\RestfulApi\AlchemistRestfulApi;

$restfulApi = new AlchemistRestfulApi([
    // The fields are passed in from the request input, fields are separated by commas, and subsidiary fields
    // are enclosed in `{}`.
    'fields' => 'id,order_date,order_status,order_items{item_id,product_id,price,quantity}'
]);

// Call `$restfulApi->fieldSelector()` to start the field selector builder, then your API definitions can be
// defined based on the chain of builder.
$fieldSelector = $restfulApi->fieldSelector()
    // If no field is passed up by the API Client, the default field will present.
    ->defineDefaultFields(['id'])
    
    // Your API client can be able to retrieve any fields in the list
    ->defineFieldStructure([
            'id',
            'order_date',
            'order_status',
            'order_items' => [
                'item_id',
                'product_id',
                'price',
                'quantity'
            ]
        ]);

// The important thing here is that your API will be rigorously checked by the validator, which will check things like
// whether the selected fields are in the list of "selectable" fields, the same for subsidiary fields, and also
// whether your API client is selecting subsidiary fields on atomic fields that do not have subsidiary fields,
// for example: "id{something}", where id is an atomic field and has no subsidiary fields.
if (! $restfulApi->validate($errorBag)->passes()) {
    // var_dump(json_encode($errorBag->getErrors()));
    
    echo "validate failed"; die();
}

// Finally, what you will receive by call the `$fieldSelector->fields()` method is a list of field objects, and everything has been carefully checked.

// Combine with the use of an ORM/Query Builder

$result = ExampleOrderQueryBuilder::select($fieldSelector->flatFields())->get();

// For other purposes, use `$fieldSelector->fields()` to obtain a complete map list of field object.

var_dump($fieldSelector->fields());

资源筛选

资源过滤主要关注检查您的API客户端使用的过滤是否在定义的可过滤列表中。

use Nvmcommunity\Alchemist\RestfulApi\AlchemistRestfulApi;
use Nvmcommunity\Alchemist\RestfulApi\ResourceFilter\Objects\FilteringRules;
use Nvmcommunity\Alchemist\RestfulApi\ResourceFilter\Objects\FilteringOptions;

$restfulApi = new AlchemistRestfulApi([
    // The filtering are passed in from the request input.
    // Use a colon `:` to separate filtering and operator.
    'filtering' => [
        'order_date:lte' => '2023-02-26',
        'product_name:contains' => 'clothes hanger'
    ]
]);

$resourceFilter = $restfulApi->resourceFilter()
    // Defining options for your API
    ->defineFilteringOptions(new FilteringOptions([
        // Your API client needs to pass order_date filtering with any operation in order to pass the validator
        'required' => ['order_date:any']
    ]))
    // Defining filtering for your API
    ->defineFilteringRules([
        // Your API allows filtering by product_name with the operations "eq" and "contains", and the data of the
        // filtering must be a string type
        FilteringRules::String('product_name', ['eq', 'contains']),

        // Your API allows filtering by product_name with the operations "eq", "lte" and "gte", and the data of
        // the filtering must be a valid date in `Y-m-d` format
        FilteringRules::Date('order_date', ['eq', 'lte', 'gte'], ['Y-m-d']),

        // Your API allows filtering by product_id with the operations "eq" and the data of the filtering must
        // be an integer type
        FilteringRules::Integer('product_id', ['eq']),

        // Your API allows filtering by is_best_sales with the operations "eq" and the data of the filtering must
        // be an integer type with value of: `0` (represent for false) or `1` (represent for true)
        FilteringRules::Boolean('is_best_sales', ['eq']),
    ]);

// Support for setting a default filtering in case your API client does not pass data to a specific filtering.
$resourceFilter->addFilteringIfNotExists('is_best_sales', 'eq', 1);

// Validate your API client filtering, the same concept with field selector above
if (! $restfulApi->validate($errorBag)->passes()) {
    // var_dump(json_encode($errorBag->getErrors()));

    echo "validate failed"; die();
}

// And finally, what you will receive is a list of filtering objects, and everything has been carefully checked.

// Combine with the use of an ORM/Query Builder

$conditions = array_map(static fn($filteringObj) => $filteringObj->flatArray(), $resourceFilter->filtering());

$result = ExampleOrderQueryBuilder::where($conditions)->get();

// For other purposes, use `$resourceFilter->filtering()` to obtain a complete map list of filtering object.

var_dump($resourceFilter->filtering());

过滤规则

过滤规则是根据FilteringRules类定义的,其中包含以下信息

  • 过滤名称:API客户端将传递的过滤名称。
  • 支持的运算符:API客户端可以使用来过滤数据的运算符列表。
  • 数据类型:过滤数据的类型。

支持的过滤规则

// `String` type is a special type that allows you to filter the data based on the string value.
FilteringRules::String(string $filtering, array $supportedOperators)

// `Integer` type is a special type that allows you to filter the data based on the integer value.
FilteringRules::Integer(string $filtering, array $supportedOperators)

// `Number` type is a special type that allows you to filter the data based on the numeric value.
FilteringRules::Number(string $filtering, array $supportedOperators)

// `Date` type allow you to define the date format that your API client can use to filter the data, default format: 'Y-m-d'
FilteringRules::Date(string $filtering, array $supportedOperators, array $formats = ['Y-m-d'])

// // `Datetime` type allow you to define the date and time format that your API client can use to filter the data, default format: 'Y-m-d H:i:s'
FilteringRules::Datetime(string $filtering, array $supportedOperators, array $formats = ['Y-m-d H:i:s'])

// `Enum` type is a special type that allows you to define a list of values that your API client can use to filter the data.
FilteringRules::Enum(string $filtering, array $supportedOperators, array $enums)

// `Boolean` type is a special type that allows you to define the boolean value that your API client can use to filter the data.
FilteringRules::Boolean(string $filtering, array $supportedOperators = [])

过滤运算符

在请求输入中使用运算符的过滤可以表示为:<filtering>:<operator>

从请求输入中传递的运算符(请求运算符)将被转换为目标运算符。此表还描述了特殊数据类型(如:betweennot betweeninnot in)的过滤值结构。

支持的运算符

(*) 注意这些运算符,因为它们不是任何数据库管理系统的原生运算符;您需要手动处理它们。

资源分页

支持通过偏移量和限制机制进行分页。

$restfulApi = new AlchemistRestfulApi([
    // The limit and offset are passed in from the request input.
    'limit' => 10,
    'offset' => 0,
]);

$resourceOffsetPaginator = $restfulApi->resourceOffsetPaginator()
    // Define default limit for resource
    ->defineDefaultLimit(10)

     // Define max limit for resource (set max limit to `0` or not define it will disable max limit)
    ->defineMaxLimit(1000);


// Validate your API client pagination parameters (limit, offset), Check if the offset value passed in is negative
// or not, and whether the limit parameter passed in exceeds the max limit (if max limit defined).
if (! $resourceOffsetPaginator->validate($notification)->passes()) {
    // var_dump(json_encode($errorBag->getErrors()));

    echo "validate failed"; die();
}

// Receive an object containing parameters for offset, limit, and max limit.
$offsetPaginate = $resourceOffsetPaginator->offsetPaginate();

// Combine with the use of an ORM/Query Builder
$result = ExampleOrderQueryBuilder::limit($offsetPaginate->getLimit())->offset($offsetPaginate->getOffset())->get();

资源排序

支持根据API客户端指定的排序和方向灵活返回结果。

$restfulApi = new AlchemistRestfulApi([
    // The sort and direction are passed in from the request input.
    'sort' => 'id',
    'direction' => 'desc',
]);

$resourceSort = $restfulApi->resourceSort()
    // define default sort field
    ->defineDefaultSort('id')
    
    // define default sort direction
    ->defineDefaultDirection('desc')

    // define list of field that client able to sort
    ->defineSortableFields(['id', 'created_at']);

// Validate your API client sort parameters (sort, direction), Check if the sort field passed in is in the list of
// sortable fields, and whether the direction parameter passed in is valid.
if (! $restfulApi->validate($errorBag)->passes()) {
    // var_dump(json_encode($errorBag->getErrors()));

    echo "validate failed"; die();
}

$sort = $resourceSort->sort();

// Combine with the use of an ORM/Query Builder

if (! empty($sort->getSortField())) {
    ExampleOrderQueryBuilder::orderBy($sort->getSortField(), $sort->getDirection());
}

资源搜索

当通过过滤进行过滤时,API客户端需要清楚地指定过滤标准。然而,在搜索的情况下,API客户端只需要传递要搜索的值,后端将自动从内部定义过滤标准。

$restfulApi = new AlchemistRestfulApi([
    // The search are passed in from the request input.
    'search' => 'clothes hanger',
]);

$resourceSearch = $restfulApi->resourceSearch()
    // define the search criteria
    ->defineSearchCondition('product_name');
    
$search = $resourceSearch->search();

// Combine with the use of an ORM/Query Builder
ExampleOrderQueryBuilder::where($search->getSearchCondition(), 'like', "%{$search->getSearchValue()}%");

贡献

有关详细信息,请参阅CONTRIBUTING

贡献者

代码贡献者

这个项目之所以存在,要归功于所有贡献者。贡献

许可证

本项目采用MIT许可证。