Skip to content

Commit c907343

Browse files
committed
added validation utility for validating input to rest services
1 parent a561711 commit c907343

File tree

2 files changed

+341
-0
lines changed

2 files changed

+341
-0
lines changed

src/RESTFul/Validation.php

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
<?php
2+
3+
namespace PhpPlatform\RESTFul;
4+
5+
use PhpPlatform\Errors\Exceptions\Http\_4XX\BadRequest;
6+
7+
/**
8+
* Provides utilities to validate and sanitize the input data from rest clients
9+
*
10+
* All of the methods can be chained to validate for multiple conditions
11+
*
12+
* Example
13+
*
14+
* $validation = new Validation('data',['a','b','c']);
15+
* $validation->isArray()->hasCount(2,3); // success
16+
* $validation->key(2)->isString()->hasLength(2,4);
17+
*
18+
*/
19+
class Validation {
20+
21+
private $key = null;
22+
private $data = null;
23+
private $validationErrors = null;
24+
private $continue = true; // continue to next validation or skip it
25+
26+
/**
27+
* @var Validation
28+
*/
29+
private $parentValidation = null;
30+
31+
/**
32+
* @var Validation[]
33+
*/
34+
private $childValidations = [];
35+
36+
function __construct($key, $data){
37+
$this->key = $key;
38+
$this->data = $data;
39+
}
40+
41+
function containsOnly($keys){
42+
if($this->continue){
43+
if(!is_array($this->data) || count(array_diff(array_keys($this->data), $keys)) > 0){
44+
$this->validationErrors = 'invalid';
45+
$this->continue = false;
46+
}
47+
}
48+
return $this;
49+
}
50+
51+
function containsAll($keys){
52+
if($this->continue){
53+
if(!is_array($this->data) || count(array_diff($keys,array_keys($this->data))) > 0){
54+
$this->validationErrors = 'invalid';
55+
$this->continue = false;
56+
}
57+
}
58+
return $this;
59+
}
60+
61+
function containsExactly($keys){
62+
if($this->continue){
63+
$this->containsOnly($keys);
64+
$this->containsAll($keys);
65+
}
66+
return $this;
67+
}
68+
69+
function key($key){
70+
$subValidation = new Validation($key,$this->data[$key]);
71+
$subValidation->continue = $this->continue;
72+
$subValidation->parentValidation = $this;
73+
$this->childValidations[] = $subValidation;
74+
return $subValidation;
75+
}
76+
77+
function required(){
78+
if($this->continue){
79+
if(!isset($this->data)){
80+
$this->validationErrors = 'missing';
81+
$this->continue = false;
82+
}
83+
}
84+
return $this;
85+
}
86+
87+
function defaultValue($value){
88+
if($this->continue){
89+
if(!isset($this->data)){
90+
$this->parentValidation->data[$this->key] = $value;
91+
}
92+
}
93+
return $this;
94+
}
95+
96+
function isNumeric(){
97+
if($this->continue){
98+
if(!is_numeric($this->data)){
99+
$this->validationErrors = 'invalid';
100+
$this->continue = false;
101+
}
102+
}
103+
return $this;
104+
}
105+
106+
function isInt(){
107+
$this->isNumeric();
108+
if($this->continue){
109+
if(intval($this->data) != $this->data){
110+
$this->validationErrors = 'invalid';
111+
$this->continue = false;
112+
}
113+
}
114+
return $this;
115+
}
116+
117+
function inRange($min = null,$max = null){
118+
if($this->continue){
119+
if($min !== null && $this->data < $min){
120+
$this->validationErrors = 'invalid';
121+
$this->continue = false;
122+
}
123+
if($max !== null && $this->data > $max){
124+
$this->validationErrors = 'invalid';
125+
$this->continue = false;
126+
}
127+
}
128+
}
129+
130+
function isTimestamp(){
131+
if($this->continue){
132+
if(strtotime($this->data) === false){
133+
$this->validationErrors = 'invalid';
134+
$this->continue = false;
135+
}
136+
}
137+
return $this;
138+
}
139+
140+
function isString(){
141+
if($this->continue){
142+
if(is_string($this->data) === false){
143+
$this->validationErrors = 'invalid';
144+
$this->continue = false;
145+
}
146+
}
147+
return $this;
148+
}
149+
150+
function isArray(){
151+
if($this->continue){
152+
if(is_array($this->data) === false){
153+
$this->validationErrors = 'invalid';
154+
$this->continue = false;
155+
}
156+
}
157+
return $this;
158+
}
159+
160+
function isBoolean(){
161+
if($this->continue){
162+
if(is_bool($this->data) === false){
163+
$this->validationErrors = 'invalid';
164+
$this->continue = false;
165+
}
166+
}
167+
return $this;
168+
}
169+
170+
function match($regExpr){
171+
if($this->continue){
172+
if(preg_match($regExpr,$this->data) === false){
173+
$this->validationErrors = 'invalid';
174+
$this->continue = false;
175+
}
176+
}
177+
return $this;
178+
}
179+
180+
function hasLength($min = null, $max = null){
181+
if($this->continue){
182+
if($min !== null && strlen($this->data) < $min){
183+
$this->validationErrors = 'invalid';
184+
$this->continue = false;
185+
}
186+
if($max !== null && strlen($this->data) > $max){
187+
$this->validationErrors = 'invalid';
188+
$this->continue = false;
189+
}
190+
}
191+
return $this;
192+
}
193+
194+
function hasCount($min = null, $max = null){
195+
if($this->continue){
196+
if($min !== null && count($this->data) < $min){
197+
$this->validationErrors = 'invalid';
198+
$this->continue = false;
199+
}
200+
if($max !== null && count($this->data) > $max){
201+
$this->validationErrors = 'invalid';
202+
$this->continue = false;
203+
}
204+
}
205+
return $this;
206+
}
207+
208+
function in($range){
209+
if($this->continue){
210+
if(in_array($this->data,$range) === false){
211+
$this->validationErrors = 'invalid';
212+
$this->continue = false;
213+
}
214+
}
215+
return $this;
216+
}
217+
218+
function getValidationErrors(){
219+
if(isset($this->validationErrors)){
220+
return $this->validationErrors;
221+
}
222+
$validationErrors = [];
223+
foreach ($this->childValidations as $childValidation){
224+
$childValidationErrors = $childValidation->getValidationErrors();
225+
if(is_string($childValidationErrors) || (is_array($childValidationErrors) && count($childValidationErrors) > 0)){
226+
$validationErrors[$childValidation->key] = $childValidationErrors;
227+
}
228+
}
229+
return $validationErrors;
230+
}
231+
232+
function generateBadRequestOnError(){
233+
$validationErrors = $this->getValidationErrors();
234+
if(is_string($validationErrors) || (is_array($validationErrors) && count($validationErrors) > 0)){
235+
// there is validation error
236+
throw new BadRequest($validationErrors);
237+
}
238+
}
239+
}

tests/RESTFul/TestValidation.php

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
namespace PhpPlatform\Tests\RESTFul;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use PhpPlatform\RESTFul\Validation;
7+
8+
class TestValidation extends TestCase{
9+
10+
/**
11+
* @dataProvider dataProviderValidation
12+
*/
13+
function testValidation($data,$validationSequence,$expectedErrors){
14+
$validation = new Validation('data', $data);
15+
call_user_func($validationSequence,$validation);
16+
$this->assertEquals($expectedErrors, $validation->getValidationErrors());
17+
}
18+
19+
function dataProviderValidation(){
20+
return [
21+
'without any validation sequence'=>[
22+
['a','b','c'],
23+
function($validation){
24+
25+
},
26+
[]
27+
],
28+
'with simple validation sequence'=>[
29+
['a','b','c'],
30+
function($validation){
31+
$validation->isString();
32+
},
33+
'invalid'
34+
],
35+
'containsOnly success'=>[
36+
['a'=>'x','b'=>'y','c'=>'z'],
37+
function($validation){
38+
$validation->containsOnly(['a','b','c','d']);
39+
},
40+
[]
41+
],
42+
'containsOnly failure'=>[
43+
['a'=>'x','b'=>'y','c'=>'z'],
44+
function($validation){
45+
$validation->containsOnly(['b','c','d']);
46+
},
47+
'invalid'
48+
],
49+
'containsAll success'=>[
50+
['a'=>'x','b'=>'y','c'=>'z'],
51+
function($validation){
52+
$validation->containsAll(['a','b']);
53+
},
54+
[]
55+
],
56+
'containsAll failure'=>[
57+
['a'=>'x','b'=>'y','c'=>'z'],
58+
function($validation){
59+
$validation->containsAll(['b','c','d']);
60+
},
61+
'invalid'
62+
],
63+
'containsExactly success'=>[
64+
['a'=>'x','b'=>'y','c'=>'z'],
65+
function($validation){
66+
$validation->containsExactly(['a','b','c']);
67+
},
68+
[]
69+
],
70+
'containsExactly failure'=>[
71+
['a'=>'x','b'=>'y','c'=>'z'],
72+
function($validation){
73+
$validation->containsExactly(['b','c','d']);
74+
},
75+
'invalid'
76+
],
77+
'containsExactly failure 2'=>[
78+
['a'=>'x','b'=>'y','c'=>'z'],
79+
function($validation){
80+
$validation->containsExactly(['a','b','c','d']);
81+
},
82+
'invalid'
83+
],
84+
'sub validation success'=>[
85+
['a'=>'x','b'=>['A'=>200],'c'=>'z'],
86+
function($validation){
87+
$validation->containsOnly(['a','b','c'])->key('b')->required()->key('A')->isNumeric()->inRange(100,200);
88+
},
89+
[]
90+
],
91+
'sub validation failure'=>[
92+
['a'=>'x','b'=>['A'=>200],'c'=>'z'],
93+
function($validation){
94+
$validation->containsOnly(['a','b','c'])->key('b')->required()->key('A')->isNumeric()->in([10,20,30,40]);
95+
},
96+
['b'=>['A'=>'invalid']]
97+
]
98+
99+
];
100+
}
101+
102+
}

0 commit comments

Comments
 (0)