requestum/symfony-api-edition

Symfony API开发版


README

/api/doc

认证

API使用OAuth2认证。为了认证您的请求,请添加包含值“Bearer access_token”的“Authorization”头信息。

固定值

用户1
用户名: artur@gmail.com
密码: 123

用户2
用户名: kirill@gmail.com
密码: 123

访问令牌: Access_Token_For_Artur, Access_Token_For_Kirill

基本概念

此包提供了用于标准REST API操作(如创建、更新、删除、列表和转换)的现成操作类。它使用每个操作一个类的方法,因此每种操作类型都有自己的类。此外,此包还利用了每个操作类拥有多个服务(即类实例)的概念。

假设我们有三个提供资源列表的端点。

GET /resource1
GET /resource2
GET /resource3

这些端点具有相同的企业逻辑,可以由相同的代码处理。我们必须查询数据库,应用一些过滤器、分页和排序。然后我们必须准备结果数据,对其进行序列化并将其发送回客户端。这里唯一的区别是,每个端点有不同的资源实体存储库。此外,这些几乎相同的端点可能具有不同的参数值,例如可用的过滤器、默认页面大小等。因此,我们的想法是拥有统一的参数化列表操作类,并将其实例化三次,使用不同的参数。为了在参数化方面获得更大的灵活性,我们使用Symfony OptionResolver组件。因此,每个操作类都有一组选项来配置具体的端点。

内部结构

此包由以下组件组成

  1. 资源元数据工厂
  2. 具有以下功能的序列化器扩展
  • 按需扩展实体关系
  • 序列化期间按字段进行访问控制
  1. 处理API开发中常用用例的事件监听器
  • JSON解码器,将传递的数据填充到HttpFoundation请求对象的请求参数包中
  • 异常监听器,格式化错误
  1. 错误工厂
  2. 用于对具体资源实体进行投票的投票者的基类
  3. 操作类

操作类

创建操作

列表操作

获取项目列表。
对象类型:集合
HTTP方法:GET

配置

1 添加服务。示例

# config/services.yml

services:
    ...
    action.country.list:
        parent: core.action.abstract
        class: Requestum\ApiBundle\Action\ListAction
        arguments:
            - MyProject\MyBundle\Entity\Сountry
        calls:
            - ['setOptions', 
                [{
                    'default_per_page' : 15,
                    'pagerfanta_fetch_join_collection' : true,
                    'pagerfanta_use_output_walkers' : true,
                    'serialization_groups': ['default', 'custom-group'],
                    'filters' : ['query', 'order-by', 'name', 'language'],  
                    'preset_filters' : {availableForUser: '__USER__', order-by: 'createdAt|desc'},
                }]
            ]
    ...

位置
arguments: ... - 传递给Requestum\ApiBundle\Action\ListAction类构造函数的必需参数
- MyProject\MyBundle\Entity\Country - 实体类(必需)
['setOptions', ...] - 选项数组

2 添加服务到路由。示例

# config/routing.yml
...
country.list:
   path: /country
   methods: GET
   defaults: { _controller: action.country.list:executeAction }
...

可用选项

过滤器

查询过滤器
在某个字段中的一些可用文本搜索(LIKE)。支持通配符(*后缀前缀**中间*
要添加字段,您需要编辑实体存储库中的createHandlers()方法。
使用'filters': ['query']选项添加过滤器。
示例

# YourBundle\Repository\CountryRepository.php

class CountryRepository extends EntityRepository implements FilterableRepositoryInterface
{
    use ApiRepositoryTrait;
    ...
    /**
     * @inheritdoc
     */
    protected function createHandlers()
    {
        return [
            new SearchHandler([
               'language',
               'cities.name', // use the dot for fields of related entities
               'president_full_name' => ['president.firstName', 'president.lastName'] //use array to concatenate fields
            ])
        ];
    }
    ...
}

带有过滤器的示例查询: GET /country?query=*nglish

您可以指定要搜索的特定字段(从您传递给SearchHandler的列表中)。

GET /country?query[term]=*Charles*&query[fields]=president_full_name,cities.name

排序
可以在请求中添加属性名和排序顺序来排序(模式:“字段|顺序”)。例如:'order-by': 'createdAt|desc'

按属性过滤
实体支持此类过滤

  • 精确匹配(示例:GET /country?status=false);
  • 使用比较运算符(!=, <=, <>等)和*'is_null_value'is_not_null_value(示例:GET /country?status=!=true

要更改通过关联实体或现有过滤器进行的过滤逻辑,可以对实体存储库中的getPathAliases()方法进行更改。示例

# YourBundle\Repository\CountryRepository.php

use Doctrine\ORM\EntityRepository;
use Requestum\ApiBundle\Repository\ApiRepositoryTrait;
use Requestum\ApiBundle\Repository\FilterableRepositoryInterface;

class CountryRepository extends EntityRepository implements FilterableRepositoryInterface
{
    use ApiRepositoryTrait;

    /**
     * @return array
     */
    protected function getPathAliases()
    {
        return [
            ...
            'city' => '[cities][id]',  
            ...
        ];
    }
}

自定义过滤器
要创建自定义过滤器,需要
1 添加新的处理器。示例

# YourBundle\Filter\CustomFilteHandler

use Requestum\ApiBundle\Filter\Handler\AbstractHandler;

class CustomFilteHandler extends AbstractHandler
{
    public function handle(QueryBuilder $builder, $filter, $value)
    {
      ... // Some filter logic
    }
    
    protected function getFilterKey()
    {
        return 'customFilterName'; // filter name
    }
}

2 将处理器添加到项目存储库。示例

# YourBundle\Repository\CountryRepository.php

class CountryRepository extends EntityRepository implements FilterableRepositoryInterface
{
    use ApiRepositoryTrait;
    ...
    /**
     * @inheritdoc
     */
    protected function createHandlers()
    {
        return [
            new CustomFilterHandler()
        ];
    }
    ...
}

3 将自定义过滤器添加到服务。示例

services:
    ...
    action.country.list:
        parent: core.action.abstract
        class: Requestum\ApiBundle\Action\ListAction
        arguments:
            - MyProject\MyBundle\Entity\Country
        calls:
            - ['setOptions', [{'filters': ['customFilterName']}]]
    ...

附加功能

分页

Pagerfanta用于分页,并且仅与DoctrineORM查询对象一起工作。
ApiBundle分页使用默认选项pagerfanta_fetch_join_collection = falsepagerfanta_use_output_walkers = null(此设置可以在选项中更改)。
要使用分页,请将page={int}per-page={int}添加到请求中。
示例:GET /country?page=1&per-page=15

仅计数

要仅获取查询结果的数量,可以在请求属性中添加count-only。例如,将其添加到路由配置中

# config/routing.yml
...
country.list:
    path: /country
    methods: GET
    defaults: 
        {  
            _controller: action.country.list:executeAction,
            count-only: true
        }
...

扩展

可以通过在实体属性上添加注释@Reference来使用相关实体引用而不是完整值(可按需扩展),例如

# YouBundle\Entity\Country.php;
use Requestum\ApiBundle\Rest\Metadata;

class Country
{
    ...
    /**
     * @ORM\OneToMany
     * @Reference
     **/
    protected $cities;
    ...
}

要将扩展添加到请求中,请添加expand。对于多个引用扩展,应按字段用逗号分隔(注意:此处不需要空格!)!使用点来扩展关联实体的字段。
示例

// GET /country?expand=cities&name=Australia
{
    total: 1,
    entities: 
        [
            {
                id: 1,
                name: 'Australia',
                language: 'English',
                population: 25103900,               
                status: true,
                createdAt: "2018-03-22T10:49:07+00:00",
                cities: 
                    [
                        {
                            id: 11,
                            name: 'Sydney',
                            districts: [112, 113],
                            population: 25103900,
                            isCapital: false,
                            createdAt: "2018-03-23T10:49:07+00:00"
                        },
                        {
                            id: 12,
                            name: 'Melbourne',
                            districts: [122],
                            population: 4850740,
                            isCapital: false,
                            createdAt: "2018-03-23T10:49:07+00:00"
                        },
                        {
                            id: 13,
                            name: 'Brisbane',
                            districts: [131, 132],
                            population: 2408223,
                            isCapital: false,
                            createdAt: "2018-03-23T10:49:07+00:00"
                        }
                    ]
            }
        ]
}

// GET /country?name=Australia
{
    total: 1,
    entities: 
    [
        {
            id: 1,
            name: 'Australia',
            language: 'English',
            population: 25103900,               
            status: true,
            createdAt: "2018-03-22T10:49:07+00:00",
            cities: 
                [11, 12, 13] 
        }
    ]
}

请求示例

http://mysite/country?expand=cities

响应示例

{
    total: 2,
    entities: 
        [
            {
                id: 1,
                name: 'Australia',
                language: 'English',
                population: 25103900,               
                status: true,
                createdAt: "2018-03-22T10:49:07+00:00",
                cities: 
                    [
                        {
                            id: 11,
                            name: 'Sydney',
                            districts: [112, 113],
                            population: 25103900,
                            isCapital: false,
                            createdAt: "2018-03-23T10:49:07+00:00"
                        },
                        {
                            id: 12,
                            name: 'Melbourne',
                            districts: [122],
                            population: 4850740,
                            isCapital: false,
                            createdAt: "2018-03-23T10:49:07+00:00"
                        },
                        {
                            id: 13,
                            name: 'Brisbane',
                            districts: [131, 132],
                            population: 2408223,
                            isCapital: false,
                            createdAt: "2018-03-23T10:49:07+00:00"
                        }
                    ]
            },
            {
                id: 2,
                name: 'Spain',
                language: 'Spanish',
                population: 46700000,               
                status: false,
                createdAt: "2018-03-23T10:49:07+00:00",
                cities: 
                    [
                        {
                            id: 21,
                            name: 'Madrid',
                            districts: [212],
                            population: 3165235,
                            isCapital: true,
                            createdAt: "2018-03-24T10:49:07+00:00"
                        },
                        {
                            id: 22,
                            name: 'Barcelona',
                            districts: [224],
                            population: 1602386,
                            isCapital: false,
                            createdAt: "2018-03-24T10:49:07+00:00"
                        },
                        {
                            id: 23,
                            name: 'Valencia',
                            districts: [231, 232],
                            population: 786424,
                            isCapital: false,
                            createdAt: "2018-03-24T10:49:07+00:00"
                        }
                    ]
            }
        ]
}

获取操作

通过标识符获取单个项目。
对象类型:项目
HTTP方法:GET

配置

1 添加服务。示例

# config/services.yml

services:
    ...
    action.country.fetch:
        parent: core.action.abstract
        class: Requestum\ApiBundle\Action\FetchAction
        arguments:
            - MyProject\MyBundle\Entity\Сountry
        calls:
            - ['setOptions', 
                [{
                    'serialization_groups':['full_post', 'default'],
                    'fetch_field': 'email' 
                }]
            ]
    ...

位置
参数: ... - Requestum\ApiBundle\Action\FetchAction类构造函数所需的必需参数
- MyProject\MyBundle\Entity\Country - 实体类(必需)
['setOptions', ...] - 选项数组

2 添加服务到路由。示例

# config/routing.yml
...
country.fetch:
   path: /country/{id}
   methods: GET
   defaults: { _controller: action.country.fetch:executeAction }
...

可用选项

访问属性

Symfony投票用于检查用户的访问权限。AccessDecisionManager将接收access_attribute的值作为$attribute,并将实体作为主题。
该捆绑包提供了基础类AbstractEntityVoter,它根据接收到的参数$userRequired(可选,默认为true)检查会话中的用户。它易于使用,以下是对access_decision_manager的设置

# config/security.yml
...
access_decision_manager:
    strategy: unanimous
    allow_if_all_abstain: true
...

此外,该捆绑包还有一个OwnerVoter类,它与[更新、删除]属性一起工作。它使用Symfony PropertyAccess组件来检查当前用户与主题实体之间的关系(是否是所有者)。通过传递给OwnerVoter类构造函数的$propertyPath检查这些关系。
可以基于AbstractEntityVoter类创建自定义投票者。示例

1 添加新的投票者

# YourBundle\Security\Entity\CustomVoter.php

use Requestum\ApiBundle\Security\Authorization\AbstractEntityVoter;

class CustomVoter extends AbstractEntityVoter
{  
    /**
     * @param string $attribute
     * @param object $entity
     * @param UserInterface|null $user
     */
     protected function voteOnEntity($attribute, $entity, UserInterface $user = null);
    {
        // some logic
    }
}

2 将新的投票者添加到服务中

# config/services.yml

services:
...
    voter.country.owner:
        class: YourBundle\Security\Entity\CustomVoter
        arguments: [[fetch, create, update, delete], YourBundle\Entity\Country, true]
        tags:
            - { name: security.voter }
...

位置
参数: ... - 自定义投票者类构造函数的参数
[fetch, create, update, delete] - 访问属性数组(必需)
YourBundle\Entity\Country - 实体类(必需)
true - 必需的用户标志(可选,默认为true

3 将'access_attribute'添加到服务配置中,以设置要检查用户权限的属性(如有必要)。
默认情况下为'access_attribute' : 'fetch'

附加功能

扩展

可以在响应中使用相关实体引用而不是完整值。请参阅列表操作中的扩展

请求示例

http://mysite/country/1?expand=cities

响应示例

{
    id: 1,
    name: 'Australia',
    language: 'English',
    population: 25103900,               
    status: true,
    createdAt: "2018-03-22T10:49:07+00:00",
    cities: 
        [
            {
                id: 11,
                name: 'Sydney',
                districts: [112, 113],
                population: 25103900,
                isCapital: false,
                createdAt: "2018-03-23T10:49:07+00:00"
            },
            {
                id: 12,
                name: 'Melbourne',
                districts: [122],
                population: 4850740,
                isCapital: false,
                createdAt: "2018-03-23T10:49:07+00:00"
            },
            {
                id: 13,
                name: 'Brisbane',
                districts: [131, 132],
                population: 2408223,
                isCapital: false,
                createdAt: "2018-03-23T10:49:07+00:00"
            }
        ]
}

抽象表单操作类

这是一个抽象类,是 创建更新 动作的父类。可用于继承并创建其他自定义动作。

可用选项

创建动作

创建新对象的动作。这是一个继承自 AbstractFormAction 类的子类。

有两个必需的参数:实体类和表单类。示例

# src/AppBundle/Resources/config/services.yml

services:
    #...

    action.user.create:
        parent: core.action.abstract
        class: Requestum\ApiBundle\Action\CreateAction
        arguments:
            - AppBundle\Entity\User
            - AppBundle\Form\User\UserType

可用选项

事件监听器

默认情况下,创建和更新动作抛出以下事件: 'action.before_save''action.after_save'。您可以使用如下选项分发这些事件,或抛出其他事件:before_save_eventsafter_save_events

您可以创建在提交请求前后响应事件的监听器。您需要在 services.yml 文件中配置它。

    before_save.user.event:
        class: Requestum\ApiBundle\EventListener\UserBeforeSaveListener
        arguments: ["@security.token_storage"]
        tags:
            - { name: kernel.event_listener, event: action.before_save_user, method: onBeforeSaveUser }

    after_save.user.event:
        class: Requestum\ApiBundle\EventListener\UserAfterSaveListener
        arguments: ["@security.token_storage"]
        tags:
            - { name: kernel.event_listener, event: action.after_save_user, method: onAfterSaveUser }

然后您需要在此创建动作配置中指定这些监听器。

    action.user.create:
        parent: core.action.abstract
        class: Requestum\ApiBundle\Action\CreateAction
        arguments:
            - AppBundle\Entity\User
            - AppBundle\Form\User\UserType
        calls:
            - ['setOptions', [{'before_save_events': ['action.before_save_user'], 'after_save_events': ['action.after_save_user']}]]

更新动作

更新现有对象的动作。这是一个继承自 AbstractFormAction 类的子类。

有两个必需的参数:实体类和表单类。示例

# src/AppBundle/Resources/config/services.yml

services:
    #...

    action.user.update:
        parent: core.action.abstract
        class: Requestum\ApiBundle\Action\UpdateAction
        arguments:
            - AppBundle\Entity\User
            - AppBundle\Form\User\UserType

可用选项

更新动作具有与创建动作相同的可用功能和选项。(参见 "创建动作")

删除动作

删除现有对象的动作

有一个必需的参数:实体类。示例

# src/AppBundle/Resources/config/services.yml

services:
    #...

    action.user.delete:
        parent: core.action.abstract
        class: Requestum\ApiBundle\Action\DeleteAction
        arguments:
            - AppBundle\Entity\User

可用选项

事件监听器

默认情况下,删除动作抛出以下事件:'action.before_delete'。您可以使用此选项分发事件,或抛出其他事件:before_delete_events

您可以创建在删除实体前响应事件的监听器。您需要在 services.yml 文件中配置它。

    before_delete.user.event:
        class: Requestum\ApiBundle\EventListener\UserBeforeDeleteListener
        tags:
            - { name: kernel.event_listener, event: action.before_delete_user, method: onBeforeDeleteUser }

然后您需要在此删除动作配置中指定这些监听器。

    action.user.delete:
        parent: core.action.abstract
        class: Requestum\ApiBundle\Action\DeleteAction
        arguments:
            - AppBundle\Entity\User
        calls:
            - ['setOptions', [{'before_delete_events': ['action.before_delete_user'] }]]