brezgalov/yii2-api-helpers

使您的代码更出色的API工具

v2.1.4 2022-07-02 22:04 UTC

This package is auto-updated.

Last update: 2024-09-18 18:01:25 UTC


README

本包是标准化、加速团队开发并提高代码质量工作的成果。

它主要面向服务器API的开发,但同时也支持web表单的操作。(API通常需要“管理后台”才能作为一个产品存在)

关键特性

逻辑连接标准化。

控制器中不包含任何逻辑,确保“Controller -> Action -> Service”的连接。

用户输入的独立接口。

如果服务不需要用户输入,我们不会尝试填充它。

无需控制事务。

扭转游戏规则:现在事务无处不在,“实习生”不会忘记应用它们,并且明确表示不使用事务。

在同一个IP地址同时调用action时,顺序执行action。

避免同时请求过多导致混乱的情况。

使用延迟事件。

只有在action保证无误执行后,才从用户卡中扣除金额。

分离响应格式化和逻辑。

一个服务,多个action中的不同格式化选项。

分离逻辑和显示以支持web表单。

输出HTML - 独立的格式化选项。使用“DTO接口”将数据传递到页面。

安装

composer require brezgalov/yii2-api-helpers --prefer-dist

连接服务

使用 Controller::actions() 方法连接 action

public function actions()
{
    return [
        'list' => [
            'class' => ApiGetAction::class,
            'service' => MyExampleRepositoryService::class,
            'methodName' => MyExampleRepositoryService::METHOD_LIST,
        ],

        'cities' => [
            'class' => ApiActiveGetAction::class,
            'service' => KladrCitiesSearch::class,
        ],

        'regions' => [
            'class' => ApiActiveGetAction::class,
            'service' => KladrRegionsSearch::class,
            'dataProviderSetup' => [
                'pagination' => false,
            ],
            'afterDataProviderInit' => function(ActiveDataProvider $dataProvider) {
                $dataProvider->sort->defaultOrder = ['name' => SORT_DESC];

                return $dataProvider;
            },
        ],
    ];
}

MyExampleRepositoryService 的服务代码

class MyExampleRepositoryService extends Model
{
    const METHOD_LIST = 'listData';

    protected function getExampleData()
    {
        return [
            [
                'id' => 1,
                'name' => 'Barbara',
                'sex' => 'female',
            ],
            [
                'id' => 2,
                'name' => 'Mike',
                'sex' => 'male',
            ],
        ];
    }

    public function listData()
    {
        return $this->getExampleData();
    }
}

建议使用以下 action 来开发API

  • ApiGetAction - 用于输出任何信息
  • ApiPostAction - 用于工作逻辑
  • ApiGetActiveAction - 使用 ActiveDataProvider 输出列表

_ApiGetAction 和 ApiPostAction 在内部行为上有所不同。请参阅“action行为”部分。

连接 ApiGetAction/ApiPostAction action 时使用两个参数

  • service - 服务类的配置
  • methodName - 表示需要调用的服务方法的字符串

为了简化实现输出列表,可以使用 ApiGetActiveAction。与 \yii\rest\IndexAction 的主要区别在于使用 ApiGetAction 和响应格式化机制。这使得可以灵活地配置 DataProvider

连接 ApiGetActiveAction 时可以指定

  • service - 返回 ActiveQuery 的模型
  • methodName - 假设模型继承了 ISearch,因此默认指定为 "getQuery"
  • dataProviderSetup - DataProviderInterface 的配置数组
  • afterDataProviderInit - 用于编辑实例化的 DataProviderInterface 的回调

用户输入

直接处理用户输入

用户输入以服务字段的形式表示,并通过 IRegisterInputInterface 填充

class MyExampleRepositoryService extends Model implements IRegisterInputInterface
{
    public $idFilter;
  
    public $nameFilter;

    public function registerInput(array $data = [])
    {
        $this->nameFilter = $data['filter_name'] ?? $this->nameFilter;
        $this->idFilter = $data['filter_ID'] ?? $this->idFilter;
    }

    ... // other code

通过此接口

! 当不需要时,无需实现/调用填充用户数据

! Web API和服务接口可能不同,这在重构时很有用

! 现在断开了基于Model的服务与rules和load之间的联系,现在可以更轻松地在rules中指定不希望用请求数据填充的变量的验证规则

使用 Model::load 处理用户输入

我们还可以通过包装方法 load 来支持与旧版本库的web表单或服务的工作

class MyExampleRepositoryService extends Model implements IRegisterInputInterface
{
    public $id;

    public $name;

    public function rules()
    {
        return [
            [['id'], 'integer'],
            [['name'], 'string'],
        ];
    }

    public function registerInput(array $data = [])
    {
        $this->load($data, '');
    }

    ... // other code

响应格式化

为了格式化服务响应,需要使用接口对象 IFormatter

interface IFormatter
{
    public function format($service, $result);
}

方法 format($service, $result) 允许通过变量 $service 访问服务状态,以便形成更复杂的格式化逻辑。

默认情况下,使用 ModelResultFormatter 类对象来格式化 API 响应。它负责标准的错误处理。

例如,实现一个隐藏数据集中 "sex" 字段的 Formatter

class MyExampleFormatter extends ModelResultFormatter
{
    public function format($service, $result)
    {
        if (is_array($result)) {
            foreach ($result as &$item) {
                unset($item['sex']);
            }

            return $result;
        }

        return parent::format($service, $result);
    }
}

在控制器中连接 Formatter

public function actions()
{
    return [
        'index' => [
            'class' => ApiGetAction::class,
            'service' => MyExampleRepositoryService::class,
            'methodName' => MyExampleRepositoryService::METHOD_LIST,
            'formatter' => MyExampleFormatter::class
        ],
    ];
}

验证和错误处理

当服务方法返回 false 时发生 API 错误处理。

可以使用对象 Brezgalov\ApiHelpers\v2\ErrorException 来输出错误,或者使用方法 Model::addError($attribute, $error)

ErrorException 允许自行选择错误格式和响应代码。

使用 Model 类的错误会导致标准的 Yii2 错误显示,响应代码为 422。

操作行为

库允许将行为连接到 action

通过方法 BaseAction::getDefaultBehaviors() 设置 action 的标准行为集。

class ApiPostAction extends BaseAction
{
    const BEHAVIOR_KEY_TRANSACTION = 'transaction';
    const BEHAVIOR_KEY_MUTEX = 'mutex';
    const BEHAVIOR_KEY_DELAYED_EVENTS = 'delayedEvents';

    public $formatter = ModelResultFormatter::class;

    protected function getDefaultBehaviors()
    {
        return [
            static::BEHAVIOR_KEY_TRANSACTION => TransactionBehavior::class,
            static::BEHAVIOR_KEY_MUTEX  => MutexBehavior::class,
            static::BEHAVIOR_KEY_DELAYED_EVENTS  => DelayedEventsBehavior::class,
        ];
    }
}

字段 BaseAction::$behavior 允许在连接到控制器时管理行为。如果通过行为键传递值 false,则不会将行为连接到 action

public function actions()
{
    return [
        'my-post-action' => [
            'class' => ApiPostAction::class,
            ...
            'behaviors' => [
                ApiPostAction::BEHAVIOR_KEY_TRANSACTION => false,
                MyCustomBehavior::class,
            ],
        ],
    ];
}

处理 web 表单

RenderAction 负责页面渲染,并提供连接/断开 layout页面标题、指定 view 和渲染模式(默认 / 文件渲染 / Ajax 渲染)的设置。

class RenderAction extends BaseAction
{
    /**
    * @var bool
    */
    public $layout = true;

    /**
     * @var string
     */
    public $title;

    /**
     * @var string
     */
    public $view;

    /**
     * @var string
     */
    public $mode = ViewResultFormatter::RENDER_MODE_DEFAULT;

    /**
     * @var ViewContextInterface|string|array
     */
    public $viewContext;

    /**
     * @var IFormatter
     */
    public $formatter = ViewResultFormatter::class;

    ...
}

实现 ViewContext 的例子

class ViewContext implements ViewContextInterface
{
    /**
     * @return string the view path that may be prefixed to a relative view name.
     */
    public function getViewPath()
    {
        return __DIR__;
    }
}

使用接口 IRenderFormatterDTO 传递数据到 view

可以使用实现该接口的 DTO 对象的服务返回对象,或者简单地创建一个继承该接口的 Page 服务并返回自身。

示例 Page 服务

class RightsTablePage extends Model implements IRenderFormatterDTO, IRegisterInputInterface
{
    const PAGE_PREPARE_METHOD = 'preparePageData';

    /**
     * @var RightsTableDto
     */
    protected $tableDto;

    /**
     * @var RightsTableFactory
     */
    public $rightsTableFactory;

    /**
     * RightsTablePage constructor.
     * @param array $config
     */
    public function __construct($config = [])
    {
        parent::__construct($config);

        if (empty($this->rightsTableFactory)) {
            $this->rightsTableFactory = new RightsTableFactory();
        }
    }

    /**
     * @return RightsTableDto[]
     */
    public function getViewParams()
    {
        return [
            'tableDto' => $this->tableDto,
            'tableErrors' => $this->submitRightsService->getErrorSummary(true),
        ];
    }

    /**
     * @param array $data
     * @return bool
     */
    public function registerInput(array $data = [])
    {
        $this->submitRightsService->registerInput($data);

        return true;
    }

    /**
     * @return $this
     */
    public function preparePageData()
    {
        $this->tableDto = $this->rightsTableFactory->buildTableDto();

        return $this;
    }
}

::getViewParams() 负责将数据传递到 view

::registerInput() 负责注册用户输入

::preparePageData() 负责准备数据

使用示例

public function actions()
{
    return [
        'get-table' => [
            'class' => RenderAction::class,
            'service' => RightsTablePage::class,
            'methodName' => RightsTablePage::PAGE_PREPARE_METHOD,
            'title' => 'Таблица ролей и разрешений',
            'view' => 'RightsTable/View',
            'viewContext' => ViewContext::class,
        ],
    ];
}

建议使用 SubmitRenderAction 来处理表单的 submit。它与 RenderAction 的工作方式类似,允许在出现错误时再次渲染表单。参数 successRedirectRoute 允许指定成功的 submit 路由。

SubmitRenderAction 的连接示例

[
    'class' => SubmitRenderAction::class,
    'service' => RightsTablePage::class,
    'methodName' => RightsTablePage::SUBMIT_TABLE_METHOD,
    'title' => 'Таблица ролей и разрешений',
    'successRedirectRoute' => 'rights-table/index',
    'view' => 'RightsTable/View',
    'viewContext' => ViewContext::class,
],

更详细的示例可以在 brezgalov/yii2-rights-manager 存储库中查看。