brezgalov/ yii2-api-helpers
使您的代码更出色的API工具
Requires
- php: >=7.2.0
- yiisoft/yii2: >=2.0.6
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 存储库中查看。