├── README.md ├── RequestObject.php ├── test1.php ├── test2.php ├── test3.php ├── test4.php ├── test5.php └── test6.php /README.md: -------------------------------------------------------------------------------- 1 | # Example 1: simple example 2 | class Request1 extends RequestObject 3 | { 4 | public $param1; 5 | } 6 | 7 | $request = new Request1(); 8 | echo "param1: " . $request->param1 . '
'; 9 | 10 | output: 11 | 12 | test1.php?param1=11212 13 | param1: 11212 14 | 15 | test1.php?param1=hi 16 | param1: hi 17 | 18 | 19 | # Example 2: data types and default values 20 | 21 | ```php 22 | "; 39 | var_dump($request->param1); 40 | echo "param2:
"; 191 | var_dump($request->param2); 192 | 193 | echo "param3:
"; 194 | var_dump($request->param3); 195 | ``` 196 | 197 | output: 198 | 199 | test5.php?param1[]=1¶m1[]=2¶m2[]=hi 200 | param1: array(2) { [0]=> int(1) [1]=> int(2) } 201 | param2: int(1) 202 | param3: array(2) { [0]=> string(5) "hello" [1]=> string(5) "world" } 203 | 204 | test5.php?param1[]=1¶m1[]=2¶m2=2 205 | param1: array(2) { [0]=> string(1) "1" [1]=> string(1) "2" } 206 | param2: int(3) 207 | param3: array(2) { [0]=> string(5) "hello" [1]=> string(5) "world" } 208 | -------------------------------------------------------------------------------- /RequestObject.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @copyright under GPL 2 licence 9 | */ 10 | class RequestObjectException extends Exception 11 | { 12 | private $validationErrors = array(); 13 | function __construct($message, $validationErrors) 14 | { 15 | $this->validationErrors = $validationErrors; 16 | parent::__construct($message); 17 | } 18 | 19 | public function getValidationErrors() 20 | { 21 | return $this->validationErrors; 22 | } 23 | } 24 | 25 | abstract class RequestObject 26 | { 27 | private $parameters = array(); 28 | private $reflection; 29 | 30 | private function processPHPDoc(ReflectionProperty $reflect) 31 | { 32 | $out = array('default' => null, 'cast' => 'raw'); 33 | $docComment = $reflect->getDocComment(); 34 | if (trim($docComment) == '') return $out; 35 | 36 | $docComment = preg_replace('#[ \t]*(?:\/\*\*|\*\/|\*)?[ ]{0,1}(.*)?#', '$1', $docComment); 37 | $docComment = trim(str_replace('*/', null, $docComment)); 38 | 39 | foreach (explode("\n", $docComment) as $item) { 40 | $item = strtolower($item); 41 | if (strpos($item, '@cast') !== false) { 42 | $cast = trim(str_replace('@cast', null, $item)); 43 | $out['cast'] = $cast; 44 | } 45 | 46 | if (strpos($item, '@default') !== false) { 47 | $default = trim(str_replace('@default', null, $item)); 48 | $out['default'] = $default; 49 | } 50 | } 51 | return $out; 52 | } 53 | 54 | /** 55 | * @static 56 | * @param bool $validateInConstructor 57 | * @return RequestObject 58 | */ 59 | public static function factory($validateInConstructor = true) 60 | { 61 | return new self($validateInConstructor); 62 | } 63 | 64 | /** 65 | * @var RequestObject 66 | */ 67 | private static $instance = null; 68 | 69 | /** 70 | * @static 71 | * @return RequestObject 72 | */ 73 | public static function singleton() 74 | { 75 | if (is_null(self::$instance)) { 76 | self::$instance = self::factory(); 77 | } 78 | return self::$instance; 79 | } 80 | 81 | public function __construct($validateInConstructor = true) 82 | { 83 | $this->reflection = new ReflectionClass($this); 84 | 85 | $args = array(); 86 | $availablefilters = $this->getAvailableFilters(); 87 | 88 | foreach ($this->reflection->getProperties(ReflectionProperty::IS_PUBLIC) as $reflectionProperty) { 89 | $propertyName = $reflectionProperty->getName(); 90 | $doc = $this->processPHPDoc($this->reflection->getProperty($propertyName)); 91 | 92 | $values = array('default' => $doc['default']); 93 | if (strpos($doc['cast'], 'array') !== false) { 94 | $values['flags'] = FILTER_REQUIRE_ARRAY; 95 | $cast = str_replace('array', null, $doc['cast']); 96 | } else { 97 | $cast = $doc['cast']; 98 | } 99 | 100 | if (!array_key_exists($cast, $availablefilters)) { 101 | throw new Exception("cast '{$cast}' not available"); 102 | } 103 | $values['filter'] = $availablefilters[$cast]; 104 | $args[$propertyName] = $values; 105 | } 106 | 107 | $inputMethods = array('GET' => INPUT_GET, 'POST' => INPUT_POST); 108 | $inputMethod = $inputMethods[$_SERVER['REQUEST_METHOD']]; 109 | 110 | $myinputs = filter_input_array($inputMethod, $args); 111 | 112 | foreach ($myinputs as $propertyName => $filteredInput) { 113 | if (is_null($filteredInput) && ! is_null($args[$propertyName])) { 114 | $this->$propertyName = filter_var($args[$propertyName]['default'], $args[$propertyName]['filter']); 115 | } else { 116 | $this->$propertyName = $filteredInput; 117 | } 118 | $this->filteredParameters[$propertyName] = $this->$propertyName; 119 | 120 | } 121 | 122 | if ($validateInConstructor === true) { 123 | $this->validateAll(); 124 | } 125 | } 126 | 127 | private $filteredParameters = array(); 128 | 129 | public function getFilteredParameters() 130 | { 131 | return $this->filteredParameters; 132 | } 133 | 134 | private function filterInput($cast, $key, $default) 135 | { 136 | $inputMethods = array('GET' => INPUT_GET, 'POST' => INPUT_POST); 137 | 138 | if (!array_key_exists($_SERVER['REQUEST_METHOD'], $inputMethods)) { 139 | throw new Exception("Input method not supported"); 140 | } 141 | 142 | $inputMethod = $inputMethods[$_SERVER['REQUEST_METHOD']]; 143 | 144 | $filters = $this->getAvailableFilters(); 145 | $filter = $filters[$cast]; 146 | $out = filter_input($inputMethod, $key, $filter); 147 | 148 | if (is_null($out) && $default != '') { 149 | $out = filter_var($default, $filter); 150 | } 151 | return $out; 152 | } 153 | 154 | private function getAvailableFilters() 155 | { 156 | $filters = array( 157 | 'string' => FILTER_SANITIZE_STRING, 158 | 'int' => FILTER_VALIDATE_INT, 159 | 'integer' => FILTER_VALIDATE_INT, 160 | 'bool' => FILTER_VALIDATE_BOOLEAN, 161 | 'float' => FILTER_VALIDATE_FLOAT, 162 | 'url' => FILTER_VALIDATE_EMAIL, 163 | 'email' => FILTER_SANITIZE_STRING, 164 | 'raw' => FILTER_UNSAFE_RAW 165 | ); 166 | return $filters; 167 | } 168 | 169 | public function validateAll() 170 | { 171 | $validationErrors = array(); 172 | foreach (array_keys($this->parameters) as $parameter) { 173 | $validateMethod = "validate_{$parameter}"; 174 | if ($this->reflection->hasMethod($validateMethod) || isset($this->dinamicCallbacks[$validateMethod])) { 175 | $parameter = $this->$parameter; 176 | if ($this->$validateMethod($parameter) === false) { 177 | $validationErrors[$parameter] = array( 178 | 'value' => $this->$parameter 179 | ); 180 | } 181 | } 182 | } 183 | 184 | if (count($validationErrors) > 0) { 185 | throw new RequestObjectException('Validation error', $validationErrors); 186 | } 187 | } 188 | 189 | private $dinamicCallbacks = array(); 190 | 191 | public function appendValidateTo($property, Closure $callback) 192 | { 193 | $this->dinamicCallbacks["validate_{$property}"] = $callback; 194 | } 195 | 196 | public function __call($name, $arguments) 197 | { 198 | if (isset($this->dinamicCallbacks[$name])) { 199 | return call_user_func_array($this->dinamicCallbacks[$name], $arguments); 200 | } else { 201 | return null; 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /test1.php: -------------------------------------------------------------------------------- 1 | param1 . ''; 12 | /* 13 | test1.php?param1=11212 14 | param1: 11212 15 | 16 | test1.php?param1=hola 17 | param1: hola 18 | */ 19 | -------------------------------------------------------------------------------- /test2.php: -------------------------------------------------------------------------------- 1 | "; 18 | var_dump($request->param1); 19 | echo "