Skip to content
This repository was archived by the owner on Jun 4, 2024. It is now read-only.

Commit fcc9838

Browse files
authored
Merge pull request #12 from Insolita/validation
Extend generated validation rules
2 parents feb2c35 + c12d263 commit fcc9838

File tree

21 files changed

+230
-93
lines changed

21 files changed

+230
-93
lines changed

src/lib/ValidationRulesBuilder.php

Lines changed: 127 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@
77

88
namespace cebe\yii2openapi\lib;
99

10+
use cebe\yii2openapi\lib\items\Attribute;
1011
use cebe\yii2openapi\lib\items\DbModel;
1112
use cebe\yii2openapi\lib\items\ValidationRule;
13+
use function in_array;
14+
use function preg_match;
15+
use function strtolower;
1216

1317
class ValidationRulesBuilder
1418
{
@@ -19,11 +23,14 @@ class ValidationRulesBuilder
1923

2024
/**
2125
* @var array|ValidationRule[]
22-
**/
26+
*/
2327
private $rules = [];
2428

2529
private $typeScope = [
26-
'safe' => [], 'required' => [], 'int' => [], 'bool' => [], 'float' => [], 'string' => [], 'ref' => []
30+
'required' => [],
31+
'ref' => [],
32+
'trim' => [],
33+
'safe' => [],
2734
];
2835

2936
public function __construct(DbModel $model)
@@ -34,13 +41,124 @@ public function __construct(DbModel $model)
3441
/**
3542
* @return array|\cebe\yii2openapi\lib\items\ValidationRule[]
3643
*/
37-
public function build(): array
44+
public function build():array
3845
{
3946
$this->prepareTypeScope();
40-
$this->rulesByType();
47+
48+
if (!empty($this->typeScope['trim'])) {
49+
$this->rules[] = new ValidationRule($this->typeScope['trim'], 'trim');
50+
}
51+
52+
if (!empty($this->typeScope['required'])) {
53+
$this->rules[] = new ValidationRule($this->typeScope['required'], 'required');
54+
}
55+
if (!empty($this->typeScope['ref'])) {
56+
$this->addExistRules($this->typeScope['ref']);
57+
}
58+
foreach ($this->model->attributes as $attribute) {
59+
$this->resolveAttributeRules($attribute);
60+
}
61+
if (!empty($this->typeScope['safe'])) {
62+
$this->rules[] = new ValidationRule($this->typeScope['safe'], 'safe');
63+
}
4164
return $this->rules;
4265
}
4366

67+
private function resolveAttributeRules(Attribute $attribute):void
68+
{
69+
if ($attribute->isReadOnly()) {
70+
return;
71+
}
72+
if ($attribute->isUnique()) {
73+
$this->rules[] = new ValidationRule([$attribute->columnName], 'unique');
74+
}
75+
if ($attribute->phpType === 'bool') {
76+
$this->rules[] = new ValidationRule([$attribute->columnName], 'boolean');
77+
return;
78+
}
79+
80+
if (in_array($attribute->dbType, ['date', 'time', 'datetime'], true)) {
81+
$this->rules[] = new ValidationRule([$attribute->columnName], $attribute->dbType, []);
82+
return;
83+
}
84+
if (in_array($attribute->phpType, ['int', 'double', 'float']) && !$attribute->isReference()) {
85+
$this->addNumericRule($attribute);
86+
return;
87+
}
88+
if ($attribute->phpType === 'string' && !$attribute->isReference()) {
89+
$this->addStringRule($attribute);
90+
}
91+
if (!empty($attribute->enumValues)) {
92+
$this->rules[] = new ValidationRule([$attribute->columnName], 'in', ['range' => $attribute->enumValues]);
93+
return;
94+
}
95+
$this->addRulesByAttributeName($attribute);
96+
}
97+
98+
private function addRulesByAttributeName(Attribute $attribute):void
99+
{
100+
//@TODO: probably also patterns for file, image
101+
$patterns = [
102+
'~e?mail~i' => 'email',
103+
'~(url|site|website|href|link)~i' => 'url',
104+
'~(ip|ipaddr)~i' => 'ip',
105+
];
106+
foreach ($patterns as $pattern => $validator) {
107+
if (preg_match($pattern, strtolower($attribute->columnName))) {
108+
$this->rules[] = new ValidationRule([$attribute->columnName], $validator);
109+
return;
110+
}
111+
}
112+
}
113+
114+
/**
115+
* @param array|Attribute[] $relations
116+
*/
117+
private function addExistRules(array $relations):void
118+
{
119+
foreach ($relations as $attribute) {
120+
if ($attribute->phpType === 'int') {
121+
$this->addNumericRule($attribute);
122+
} elseif ($attribute->phpType === 'string') {
123+
$this->addStringRule($attribute);
124+
}
125+
$this->rules[] = new ValidationRule(
126+
[$attribute->columnName],
127+
'exist',
128+
['targetRelation' => $attribute->camelName()]
129+
);
130+
}
131+
}
132+
133+
private function addStringRule(Attribute $attribute):void
134+
{
135+
$params = [];
136+
if ($attribute->maxLength === $attribute->minLength && $attribute->minLength !== null) {
137+
$params['length'] = $attribute->minLength;
138+
} else {
139+
if ($attribute->minLength !== null) {
140+
$params['min'] = $attribute->minLength;
141+
}
142+
if ($attribute->maxLength !== null) {
143+
$params['max'] = $attribute->maxLength;
144+
}
145+
}
146+
$this->rules[] = new ValidationRule([$attribute->columnName], 'string', $params);
147+
}
148+
149+
private function addNumericRule(Attribute $attribute):void
150+
{
151+
$params = [];
152+
if ($attribute->limits['min'] !== null) {
153+
$params['min'] = $attribute->limits['min'];
154+
}
155+
if ($attribute->limits['max'] !== null) {
156+
$params['max'] = $attribute->limits['max'];
157+
}
158+
$validator = $attribute->phpType === 'int' ? 'integer' : 'double';
159+
$this->rules[] = new ValidationRule([$attribute->columnName], $validator, $params);
160+
}
161+
44162
private function prepareTypeScope():void
45163
{
46164
foreach ($this->model->attributes as $attribute) {
@@ -51,57 +169,20 @@ private function prepareTypeScope():void
51169
$this->typeScope['required'][$attribute->columnName] = $attribute->columnName;
52170
}
53171

54-
if ($attribute->isReference()) {
55-
if (in_array($attribute->phpType, ['int', 'string'])) {
56-
$this->typeScope[$attribute->phpType][$attribute->columnName] = $attribute->columnName;
57-
}
58-
$this->typeScope['ref'][] = ['attr' => $attribute->columnName, 'rel' => $attribute->camelName()];
59-
continue;
172+
if ($attribute->phpType === 'string') {
173+
$this->typeScope['trim'][$attribute->columnName] = $attribute->columnName;
60174
}
61175

62-
if (in_array($attribute->phpType, ['int', 'string', 'bool', 'float'])) {
63-
$this->typeScope[$attribute->phpType][$attribute->columnName] = $attribute->columnName;
176+
if ($attribute->isReference()) {
177+
$this->typeScope['ref'][] = $attribute;
64178
continue;
65179
}
66180

67-
if ($attribute->phpType === 'double') {
68-
$this->typeScope['float'][$attribute->columnName] = $attribute->columnName;
181+
if (in_array($attribute->phpType, ['int', 'string', 'bool', 'float', 'double'])) {
69182
continue;
70183
}
71184

72185
$this->typeScope['safe'][$attribute->columnName] = $attribute->columnName;
73186
}
74187
}
75-
76-
private function rulesByType():void
77-
{
78-
if (!empty($this->typeScope['string'])) {
79-
$this->rules[] = new ValidationRule($this->typeScope['string'], 'trim');
80-
}
81-
if (!empty($this->typeScope['required'])) {
82-
$this->rules[] = new ValidationRule($this->typeScope['required'], 'required');
83-
}
84-
85-
if (!empty($this->typeScope['int'])) {
86-
$this->rules[] = new ValidationRule($this->typeScope['int'], 'integer');
87-
}
88-
89-
foreach ($this->typeScope['ref'] as $relation) {
90-
$this->rules[] = new ValidationRule([$relation['attr']], 'exist', ['targetRelation'=>$relation['rel']]);
91-
}
92-
93-
if (!empty($this->typeScope['string'])) {
94-
$this->rules[] = new ValidationRule($this->typeScope['string'], 'string');
95-
}
96-
97-
if (!empty($this->typeScope['float'])) {
98-
$this->rules[] = new ValidationRule($this->typeScope['float'], 'double');
99-
}
100-
if (!empty($this->typeScope['bool'])) {
101-
$this->rules[] = new ValidationRule($this->typeScope['bool'], 'boolean');
102-
}
103-
if (!empty($this->typeScope['safe'])) {
104-
$this->rules[] = new ValidationRule($this->typeScope['safe'], 'safe');
105-
}
106-
}
107188
}

src/lib/items/Attribute.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
* @property-write mixed $default
1818
* @property-write bool $isPrimary
1919
* @property-read string $formattedDescription
20+
* @property-read null|int $maxLength
21+
* @property-read null|int $minLength
2022
*/
2123
class Attribute extends BaseObject
2224
{
@@ -27,7 +29,7 @@ class Attribute extends BaseObject
2729
public $propertyName;
2830

2931
/**
30-
* should be string/integer/boolean/float/double
32+
* should be string/integer/boolean/float/double/array
3133
* @var string
3234
*/
3335
public $phpType = 'string';
@@ -213,6 +215,16 @@ public function camelName():string
213215
return Inflector::camelize($this->propertyName);
214216
}
215217

218+
public function getMaxLength():?int
219+
{
220+
return $this->size;
221+
}
222+
223+
public function getMinLength():?int
224+
{
225+
return $this->limits['minLength'];
226+
}
227+
216228
public function getFormattedDescription():string
217229
{
218230
$comment = $this->columnName.' '.$this->description;

src/lib/items/ValidationRule.php

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@
77

88
namespace cebe\yii2openapi\lib\items;
99

10-
use yii\base\BaseObject;
1110
use yii\helpers\ArrayHelper;
1211
use yii\helpers\VarDumper;
1312
use function gettype;
1413
use function implode;
1514
use function is_string;
1615
use function sprintf;
1716

18-
class ValidationRule extends BaseObject
17+
final class ValidationRule
1918
{
2019
/**@var array * */
2120
public $attributes = [];
@@ -26,29 +25,11 @@ class ValidationRule extends BaseObject
2625
/**@var array * */
2726
public $params = [];
2827

29-
public function __construct(array $attributes, string $validator, array $params = [], $config = [])
28+
public function __construct(array $attributes, string $validator, array $params = [])
3029
{
3130
$this->attributes = array_values($attributes);
3231
$this->validator = $validator;
3332
$this->params = $params;
34-
parent::__construct($config);
35-
}
36-
37-
/**
38-
* @param string $key
39-
* @param int|string|array $value
40-
* @return $this
41-
*/
42-
public function addParam(string $key, $value):ValidationRule
43-
{
44-
$this->params[$key] = $value;
45-
return $this;
46-
}
47-
48-
public function withParams(array $params):ValidationRule
49-
{
50-
$this->params = $params;
51-
return $this;
5233
}
5334

5435
public function __toString():string

tests/specs/blog/models/base/Category.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ public function rules()
2323
return [
2424
[['title'], 'trim'],
2525
[['title', 'active'], 'required'],
26-
[['title'], 'string'],
26+
[['title'], 'unique'],
27+
[['title'], 'string', 'max' => 255],
2728
[['active'], 'boolean'],
2829
];
2930
}

tests/specs/blog/models/base/Comment.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ public function rules()
2525
{
2626
return [
2727
[['post_id', 'author_id', 'message', 'created_at'], 'required'],
28-
[['post_id', 'author_id', 'created_at'], 'integer'],
28+
[['post_id'], 'integer'],
2929
[['post_id'], 'exist', 'targetRelation' => 'Post'],
30+
[['author_id'], 'integer'],
3031
[['author_id'], 'exist', 'targetRelation' => 'Author'],
32+
[['created_at'], 'integer'],
3133
[['message'], 'safe'],
3234
];
3335
}

tests/specs/blog/models/base/Fakerable.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,21 @@ public function rules()
3434
{
3535
return [
3636
[['uuid', 'str_text', 'str_varchar', 'str_date', 'str_datetime', 'str_country'], 'trim'],
37-
[['int_min', 'int_max', 'int_minmax', 'int_created_at', 'int_simple'], 'integer'],
38-
[['uuid', 'str_text', 'str_varchar', 'str_date', 'str_datetime', 'str_country'], 'string'],
39-
[['floatval', 'floatval_lim', 'doubleval'], 'double'],
4037
[['active'], 'boolean'],
38+
[['floatval'], 'double'],
39+
[['floatval_lim'], 'double', 'min' => 0, 'max' => 1],
40+
[['doubleval'], 'double'],
41+
[['int_min'], 'integer', 'min' => 5],
42+
[['int_max'], 'integer', 'max' => 5],
43+
[['int_minmax'], 'integer', 'min' => 5, 'max' => 25],
44+
[['int_created_at'], 'integer'],
45+
[['int_simple'], 'integer'],
46+
[['uuid'], 'string'],
47+
[['str_text'], 'string'],
48+
[['str_varchar'], 'string', 'max' => 100],
49+
[['str_date'], 'date'],
50+
[['str_datetime'], 'datetime'],
51+
[['str_country'], 'string'],
4152
];
4253
}
4354

tests/specs/blog/models/base/Post.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,16 @@ public function rules()
2929
return [
3030
[['title', 'slug', 'created_at'], 'trim'],
3131
[['title', 'category_id', 'active'], 'required'],
32-
[['category_id', 'created_by_id'], 'integer'],
32+
[['category_id'], 'integer'],
3333
[['category_id'], 'exist', 'targetRelation' => 'Category'],
34+
[['created_by_id'], 'integer'],
3435
[['created_by_id'], 'exist', 'targetRelation' => 'CreatedBy'],
35-
[['title', 'slug', 'created_at'], 'string'],
36+
[['title'], 'unique'],
37+
[['title'], 'string', 'max' => 255],
38+
[['slug'], 'unique'],
39+
[['slug'], 'string', 'min' => 1, 'max' => 200],
3640
[['active'], 'boolean'],
41+
[['created_at'], 'date'],
3742
];
3843
}
3944

tests/specs/blog/models/base/User.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@ public function rules()
2525
return [
2626
[['username', 'email', 'password', 'role', 'created_at'], 'trim'],
2727
[['username', 'email', 'password'], 'required'],
28-
[['username', 'email', 'password', 'role', 'created_at'], 'string'],
28+
[['username'], 'unique'],
29+
[['username'], 'string', 'max' => 200],
30+
[['email'], 'unique'],
31+
[['email'], 'string', 'max' => 200],
32+
[['email'], 'email'],
33+
[['password'], 'string'],
34+
[['role'], 'string', 'max' => 20],
35+
[['created_at'], 'datetime'],
2936
];
3037
}
3138

tests/specs/blog_v2/models/base/Category.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ public function rules()
2424
return [
2525
[['title', 'cover'], 'trim'],
2626
[['title', 'cover', 'active'], 'required'],
27-
[['title', 'cover'], 'string'],
27+
[['title'], 'unique'],
28+
[['title'], 'string', 'max' => 100],
29+
[['cover'], 'string'],
2830
[['active'], 'boolean'],
2931
];
3032
}

0 commit comments

Comments
 (0)