Skip to content

Commit a6869d6

Browse files
committed
Add new actions
1 parent eb789eb commit a6869d6

File tree

16 files changed

+653
-18
lines changed

16 files changed

+653
-18
lines changed

CHANGELOG.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,16 @@
1010

1111
1.2.4
1212
- Add a callback for validate ids to (create/update/delete)Relationships actions
13-
- fix IdOnlyTransformer
13+
- fix IdOnlyTransformer
14+
15+
1.3.0
16+
- ListAction And ViewRelationshipAction requested with HEAD method now return headers with pagination info, same
17+
like yii rest
18+
- X-Pagination-Total-Count
19+
- X-Pagination-Page-Count
20+
- X-Pagination-Current-Page
21+
- X-Pagination-Per-Page
22+
23+
- new CountAction that can count query without loading data. Return X-Pagination-Total-Count with count result
24+
25+
- new ListForIdentityAction and ViewForIdentityAction for show data related to current user

src/actions/CountAction.php

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (c) 2020 Insolita <webmaster100500@ya.ru> and contributors
5+
* @license https://github.com/insolita/yii2-fractal/blob/master/LICENSE
6+
*/
7+
8+
namespace insolita\fractal\actions;
9+
10+
use insolita\fractal\exceptions\ValidationException;
11+
use insolita\fractal\providers\JsonApiActiveDataProvider;
12+
use Yii;
13+
use yii\db\ActiveQueryInterface;
14+
15+
/**
16+
* Provide ability for count resource items without data loading
17+
* (with filters support)
18+
* Return header X-Pagination-Total-Count with count value (Use with HEAD request)
19+
* @example
20+
* count posts
21+
* Post::find()->where([...filter condition])->count();
22+
* 'count' => [
23+
* 'class' => CountAction::class,
24+
* 'modelClass' => Post::class,
25+
* 'dataFilter' => PostDataFilter::class
26+
* ],
27+
* count posts for category (for example by route /category/<id:d+>/post-count)
28+
* Post::find()->where(['category_id' => Yii::$app->request->get('id')])->andWhere([...filter condition])->count();
29+
* 'count-for-category' => [
30+
* 'class' => CountAction::class,
31+
* 'modelClass' => Post::class,
32+
* 'parentIdAttribute' => 'category_id',
33+
* 'parentIdParam' => 'id'
34+
* ]
35+
**/
36+
class CountAction extends JsonApiAction
37+
{
38+
use HasParentAttributes;
39+
40+
/**
41+
* @var callable
42+
* @example
43+
* 'queryWrapper' => function(CountAction $action, ActiveQuery Query) {
44+
* Modify $query
45+
* or return own ActiveQuery
46+
* }
47+
*/
48+
public $queryWrapper;
49+
50+
/**
51+
* @var \yii\data\DataFilter
52+
*/
53+
public $dataFilter;
54+
55+
/**
56+
* @throws \yii\base\InvalidConfigException
57+
*/
58+
public function init():void
59+
{
60+
parent::init();
61+
$this->validateParentAttributes();
62+
}
63+
64+
/**
65+
* @return \insolita\fractal\providers\JsonApiActiveDataProvider|object
66+
* @throws \insolita\fractal\exceptions\ValidationException
67+
* @throws \yii\base\InvalidConfigException
68+
*/
69+
public function run()
70+
{
71+
if ($this->checkAccess) {
72+
call_user_func($this->checkAccess, $this->id);
73+
}
74+
75+
$query = $this->makeQuery();
76+
if ($this->queryWrapper !== null) {
77+
$query = \call_user_func($this->queryWrapper, $this, $query);
78+
}
79+
$count = $query->count();
80+
Yii::$app->response->headers->set('X-Pagination-Total-Count', $count);
81+
Yii::$app->response->setStatusCode(204);
82+
}
83+
84+
/**
85+
* @param $requestParams
86+
* @return array|bool|mixed|null
87+
* @throws \insolita\fractal\exceptions\ValidationException
88+
* @throws \yii\base\InvalidConfigException
89+
*/
90+
protected function prepareDataFilter($requestParams)
91+
{
92+
if ($this->dataFilter === null) {
93+
return null;
94+
}
95+
$this->dataFilter = Yii::createObject($this->dataFilter);
96+
if ($this->dataFilter->load($requestParams)) {
97+
$filter = $this->dataFilter->build();
98+
if ($filter === false) {
99+
throw new ValidationException($this->dataFilter->getErrors());
100+
}
101+
return $filter;
102+
}
103+
return null;
104+
}
105+
106+
/**
107+
* Add condition for parent model restriction if needed
108+
* @param \yii\db\ActiveQueryInterface $query
109+
* @return \yii\db\ActiveQueryInterface
110+
*/
111+
protected function prepareParentQuery(ActiveQueryInterface $query):ActiveQueryInterface
112+
{
113+
if (!$this->isParentRestrictionRequired()) {
114+
return $query;
115+
}
116+
$id = Yii::$app->request->getQueryParam('id', null);
117+
$condition = ($this->parentIdParam !== 'id')? $this->findModelCondition($id): [];
118+
$parentId = Yii::$app->request->getQueryParam($this->parentIdParam, null);
119+
$condition[$this->modelTable().'.'.$this->parentIdAttribute] = $parentId;
120+
$query->where($condition);
121+
return $query;
122+
}
123+
124+
125+
/**
126+
* @return JsonApiActiveDataProvider|object
127+
* @throws \insolita\fractal\exceptions\ValidationException
128+
* @throws \yii\base\InvalidConfigException
129+
*/
130+
protected function makeQuery()
131+
{
132+
$requestParams = Yii::$app->getRequest()->getBodyParams();
133+
if (empty($requestParams)) {
134+
$requestParams = Yii::$app->getRequest()->getQueryParams();
135+
}
136+
$filter = $this->prepareDataFilter($requestParams);
137+
138+
/* @var $modelClass \yii\db\BaseActiveRecord */
139+
$modelClass = $this->modelClass;
140+
$query = $this->prepareParentQuery($modelClass::find());
141+
$query = $this->prepareIncludeQuery($query);
142+
143+
if (!empty($filter)) {
144+
$query->andWhere($filter);
145+
}
146+
return $query;
147+
}
148+
}

src/actions/ListAction.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace insolita\fractal\actions;
99

1010
use insolita\fractal\exceptions\ValidationException;
11+
use insolita\fractal\pagination\JsonApiPaginator;
1112
use insolita\fractal\providers\CursorActiveDataProvider;
1213
use insolita\fractal\providers\JsonApiActiveDataProvider;
1314
use Yii;
@@ -76,7 +77,11 @@ public function run()
7677
call_user_func($this->checkAccess, $this->id);
7778
}
7879

79-
return $this->makeDataProvider();
80+
$dataProvider = $this->makeDataProvider();
81+
if (Yii::$app->request->isHead && $dataProvider->pagination !== false) {
82+
$dataProvider->fillHeaders(Yii::$app->response->headers);
83+
}
84+
return $dataProvider;
8085
}
8186

8287
/**
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (c) 2020 Insolita <webmaster100500@ya.ru> and contributors
5+
* @license https://github.com/insolita/yii2-fractal/blob/master/LICENSE
6+
*/
7+
8+
namespace insolita\fractal\actions;
9+
10+
use insolita\fractal\exceptions\ValidationException;
11+
use insolita\fractal\providers\CursorActiveDataProvider;
12+
use insolita\fractal\providers\JsonApiActiveDataProvider;
13+
use Yii;
14+
use yii\base\InvalidConfigException;
15+
use yii\db\ActiveQueryInterface;
16+
17+
/**
18+
* Handler for list actions with parent id equals current user identity
19+
* @example
20+
* show user posts
21+
* $dataProvider by query Post::find()->where(['author_id' => Yii::$app->user->id])->... + filter conditions
22+
* 'my-posts' => [
23+
* 'class' => ListForIdentityAction::class,
24+
* 'userIdAttribute' => 'author_id',
25+
* 'modelClass' => Post::class,
26+
* 'transformer' => PostTransformer::class
27+
* ],
28+
**/
29+
class ListForIdentityAction extends JsonApiAction
30+
{
31+
use HasResourceTransformer;
32+
33+
/**
34+
* @var string
35+
* user foreign key attribute
36+
*/
37+
public $userIdAttribute = 'user_id';
38+
/**
39+
* @var callable
40+
* @example
41+
* 'prepareDataProvider' => function(ListAction $action, \yii\data\DataProviderInterface $dataProvider) {
42+
* Modify $dataProvider
43+
* or return completely configured dataProvider (JsonApiActiveDataProvider|CursorActiveDataProvider)
44+
* }
45+
*/
46+
public $prepareDataProvider;
47+
48+
/**
49+
* @var \yii\data\DataFilter
50+
*/
51+
public $dataFilter;
52+
53+
/**
54+
* Provide custom configured dataProvider object (JsonApiActiveDataProvider|CursorActiveDataProvider)
55+
* You can set 'pagination' => false for disable pagination
56+
* @var array
57+
*/
58+
public $dataProvider = [
59+
'class' => JsonApiActiveDataProvider::class,
60+
'pagination'=>['defaultPageSize' => 20]
61+
];
62+
63+
/**
64+
* @throws \yii\base\InvalidConfigException
65+
*/
66+
public function init():void
67+
{
68+
parent::init();
69+
$this->initResourceTransformer();
70+
}
71+
72+
/**
73+
* @return \insolita\fractal\providers\JsonApiActiveDataProvider|object
74+
* @throws \insolita\fractal\exceptions\ValidationException
75+
* @throws \yii\base\InvalidConfigException
76+
*/
77+
public function run()
78+
{
79+
if ($this->checkAccess) {
80+
call_user_func($this->checkAccess, $this->id);
81+
}
82+
83+
return $this->makeDataProvider();
84+
}
85+
86+
/**
87+
* @param $requestParams
88+
* @return array|bool|mixed|null
89+
* @throws \insolita\fractal\exceptions\ValidationException
90+
* @throws \yii\base\InvalidConfigException
91+
*/
92+
protected function prepareDataFilter($requestParams)
93+
{
94+
if ($this->dataFilter === null) {
95+
return null;
96+
}
97+
$this->dataFilter = Yii::createObject($this->dataFilter);
98+
if ($this->dataFilter->load($requestParams)) {
99+
$filter = $this->dataFilter->build();
100+
if ($filter === false) {
101+
throw new ValidationException($this->dataFilter->getErrors());
102+
}
103+
return $filter;
104+
}
105+
return null;
106+
}
107+
108+
/**
109+
* Add condition for parent model restriction if needed
110+
* @param \yii\db\ActiveQueryInterface $query
111+
* @return \yii\db\ActiveQueryInterface
112+
*/
113+
protected function prepareParentQuery(ActiveQueryInterface $query):ActiveQueryInterface
114+
{
115+
$userId = Yii::$app->user->id;
116+
$condition[$this->modelTable().'.'.$this->userIdAttribute] = $userId;
117+
$query->where($condition);
118+
return $query;
119+
}
120+
121+
122+
/**
123+
* @return JsonApiActiveDataProvider|object
124+
* @throws \insolita\fractal\exceptions\ValidationException
125+
* @throws \yii\base\InvalidConfigException
126+
*/
127+
protected function makeDataProvider()
128+
{
129+
$requestParams = Yii::$app->getRequest()->getBodyParams();
130+
if (empty($requestParams)) {
131+
$requestParams = Yii::$app->getRequest()->getQueryParams();
132+
}
133+
$filter = $this->prepareDataFilter($requestParams);
134+
135+
/* @var $modelClass \yii\db\BaseActiveRecord */
136+
$modelClass = $this->modelClass;
137+
$query = $this->prepareParentQuery($modelClass::find());
138+
$query = $this->prepareIncludeQuery($query);
139+
140+
141+
if (!empty($filter)) {
142+
$query->andWhere($filter);
143+
}
144+
145+
$dataProvider = Yii::createObject($this->dataProvider);
146+
if (!$dataProvider instanceof JsonApiActiveDataProvider && !$dataProvider instanceof CursorActiveDataProvider) {
147+
throw new InvalidConfigException('Invalid dataProvider configuration');
148+
}
149+
$dataProvider->query = $query;
150+
$dataProvider->resourceKey = $this->resourceKey;
151+
$dataProvider->transformer = $this->transformer;
152+
$dataProvider->setSort(['params' => $requestParams]);
153+
154+
if ($this->prepareDataProvider !== null) {
155+
return call_user_func($this->prepareDataProvider, $this, $dataProvider);
156+
}
157+
158+
return $dataProvider;
159+
}
160+
}

0 commit comments

Comments
 (0)