api de gestion de ticket, basé sur php-crud-api. Le but est de décorrélé les outils de gestion des données, afin
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

ValidationMiddleware.php 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. <?php
  2. namespace Tqdev\PhpCrudApi\Middleware;
  3. use Psr\Http\Message\ResponseInterface;
  4. use Psr\Http\Message\ServerRequestInterface;
  5. use Psr\Http\Server\RequestHandlerInterface;
  6. use Tqdev\PhpCrudApi\Column\ReflectionService;
  7. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
  8. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedColumn;
  9. use Tqdev\PhpCrudApi\Controller\Responder;
  10. use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
  11. use Tqdev\PhpCrudApi\Middleware\Router\Router;
  12. use Tqdev\PhpCrudApi\Record\ErrorCode;
  13. use Tqdev\PhpCrudApi\RequestUtils;
  14. class ValidationMiddleware extends Middleware
  15. {
  16. private $reflection;
  17. public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
  18. {
  19. parent::__construct($router, $responder, $properties);
  20. $this->reflection = $reflection;
  21. }
  22. private function callHandler($handler, $record, string $operation, ReflectedTable $table) /*: ResponseInterface?*/
  23. {
  24. $context = (array) $record;
  25. $details = array();
  26. $tableName = $table->getName();
  27. foreach ($context as $columnName => $value) {
  28. if ($table->hasColumn($columnName)) {
  29. $column = $table->getColumn($columnName);
  30. $valid = call_user_func($handler, $operation, $tableName, $column->serialize(), $value, $context);
  31. if ($valid === true || $valid === '') {
  32. $valid = $this->validateType($column, $value);
  33. }
  34. if ($valid !== true && $valid !== '') {
  35. $details[$columnName] = $valid;
  36. }
  37. }
  38. }
  39. if (count($details) > 0) {
  40. return $this->responder->error(ErrorCode::INPUT_VALIDATION_FAILED, $tableName, $details);
  41. }
  42. return null;
  43. }
  44. private function validateType(ReflectedColumn $column, $value)
  45. {
  46. $types = $this->getArrayProperty('types', 'all');
  47. if (in_array('all', $types) || in_array($column->getType(), $types)) {
  48. if (is_null($value)) {
  49. return ($column->getNullable() ? true : "cannot be null");
  50. }
  51. if (is_string($value)) {
  52. // check for whitespace
  53. switch ($column->getType()) {
  54. case 'varchar':
  55. case 'clob':
  56. break;
  57. default:
  58. if (strlen(trim($value)) != strlen($value)) {
  59. return 'illegal whitespace';
  60. }
  61. break;
  62. }
  63. // try to parse
  64. switch ($column->getType()) {
  65. case 'integer':
  66. case 'bigint':
  67. if (
  68. filter_var($value, FILTER_SANITIZE_NUMBER_INT) !== $value ||
  69. filter_var($value, FILTER_VALIDATE_INT) === false
  70. ) {
  71. return 'invalid integer';
  72. }
  73. break;
  74. case 'varchar':
  75. if (mb_strlen($value, 'UTF-8') > $column->getLength()) {
  76. return 'string too long';
  77. }
  78. break;
  79. case 'decimal':
  80. if (!is_numeric($value)) {
  81. return 'invalid decimal';
  82. }
  83. break;
  84. case 'float':
  85. case 'double':
  86. if (
  87. filter_var($value, FILTER_SANITIZE_NUMBER_FLOAT) !== $value ||
  88. filter_var($value, FILTER_VALIDATE_FLOAT) === false
  89. ) {
  90. return 'invalid float';
  91. }
  92. break;
  93. case 'boolean':
  94. if (
  95. filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) === null
  96. ) {
  97. return 'invalid boolean';
  98. }
  99. break;
  100. case 'date':
  101. if (date_create_from_format('Y-m-d', $value) === false) {
  102. return 'invalid date';
  103. }
  104. break;
  105. case 'time':
  106. if (date_create_from_format('H:i:s', $value) === false) {
  107. return 'invalid time';
  108. }
  109. break;
  110. case 'timestamp':
  111. if (date_create_from_format('Y-m-d H:i:s', $value) === false) {
  112. return 'invalid timestamp';
  113. }
  114. break;
  115. case 'clob':
  116. // no checks needed
  117. break;
  118. case 'blob':
  119. case 'varbinary':
  120. if (base64_decode($value, true) === false) {
  121. return 'invalid base64';
  122. }
  123. break;
  124. case 'geometry':
  125. // no checks yet
  126. break;
  127. }
  128. } else { // check non-string types
  129. switch ($column->getType()) {
  130. case 'integer':
  131. case 'bigint':
  132. if (!is_int($value)) {
  133. return 'invalid integer';
  134. }
  135. break;
  136. case 'decimal':
  137. case 'float':
  138. case 'double':
  139. if (!is_float($value) && !is_int($value)) {
  140. return 'invalid float';
  141. }
  142. break;
  143. case 'boolean':
  144. if (!(is_int($value) && ($value === 1 || $value === 0)) && !is_bool($value)) {
  145. return 'invalid boolean';
  146. }
  147. break;
  148. default:
  149. return 'invalid ' . $column->getType();
  150. }
  151. }
  152. // extra checks
  153. switch ($column->getType()) {
  154. case 'integer': // 4 byte signed
  155. $value = filter_var($value, FILTER_VALIDATE_INT);
  156. if ($value > 2147483647 || $value < -2147483648) {
  157. return 'invalid integer';
  158. }
  159. break;
  160. case 'decimal':
  161. $value = "$value";
  162. if (strpos($value, '.') !== false) {
  163. list($whole, $decimals) = explode('.', $value, 2);
  164. } else {
  165. list($whole, $decimals) = array($value, '');
  166. }
  167. if (strlen($whole) > 0 && !ctype_digit($whole)) {
  168. return 'invalid decimal';
  169. }
  170. if (strlen($decimals) > 0 && !ctype_digit($decimals)) {
  171. return 'invalid decimal';
  172. }
  173. if (strlen($whole) > $column->getPrecision() - $column->getScale()) {
  174. return 'decimal too large';
  175. }
  176. if (strlen($decimals) > $column->getScale()) {
  177. return 'decimal too precise';
  178. }
  179. break;
  180. case 'varbinary':
  181. if (strlen(base64_decode($value)) > $column->getLength()) {
  182. return 'string too long';
  183. }
  184. break;
  185. }
  186. }
  187. return (true);
  188. }
  189. public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
  190. {
  191. $operation = RequestUtils::getOperation($request);
  192. if (in_array($operation, ['create', 'update', 'increment'])) {
  193. $tableName = RequestUtils::getPathSegment($request, 2);
  194. if ($this->reflection->hasTable($tableName)) {
  195. $record = $request->getParsedBody();
  196. if ($record !== null) {
  197. $handler = $this->getProperty('handler', '');
  198. if ($handler !== '') {
  199. $table = $this->reflection->getTable($tableName);
  200. if (is_array($record)) {
  201. foreach ($record as $r) {
  202. $response = $this->callHandler($handler, $r, $operation, $table);
  203. if ($response !== null) {
  204. return $response;
  205. }
  206. }
  207. } else {
  208. $response = $this->callHandler($handler, $record, $operation, $table);
  209. if ($response !== null) {
  210. return $response;
  211. }
  212. }
  213. }
  214. }
  215. }
  216. }
  217. return $next->handle($request);
  218. }
  219. }