validator.class.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. <?php
  2. /**
  3. * [WeEngine System] Copyright (c) 2014 WE7.CC
  4. * WeEngine is NOT a free software, it under the license terms, visited http://www.we7.cc/ for more details.
  5. */
  6. class Validator {
  7. const IMG = 'jpg, jepg, png, gif, bmp';
  8. const IMG_MIMETYPE = 'image/jpeg,image/jpeg,image/png,image/gif,image/bmp';
  9. private $defaults = array(
  10. 'required' => ':attribute 必须填写',
  11. 'integer' => ':attribute必须是整数',
  12. 'int' => ':attribute必须是整数',
  13. 'numeric' => ':attribute必须是数字',
  14. 'string' => ':attribute必须是字符串',
  15. 'json' => ':attribute 必须是json',
  16. 'array' => ':attribute必须是数组',
  17. 'min' => ':attribute不能小于%s',
  18. 'max' => ':attribute不能大于%s',
  19. 'between' => ':attribute 必须在 %s %s 范围内',
  20. 'size' => ':attribute 大小必须是 %s',
  21. 'url' => ':attribute不是有效的url',
  22. 'email' => ':attribute不是有效的邮箱',
  23. 'mobile' => ':attribute不是有效的手机号',
  24. 'file' => ':attribute必须是一个文件',
  25. 'image' => ':attribute必须是一个图片',
  26. 'ip' => ':attribute不是有效的ip',
  27. 'in' => ':attribute 必须在 %s 内',
  28. 'notin' => ':attribute 不在 %s 内',
  29. 'date' => ':attribute 必须是有效的日期',
  30. 'after' => ':attribute 日期不能小于 %s',
  31. 'before' => ':attribute 日期不能大于 %s',
  32. 'regex' => ':attribute 不是有效的数据',
  33. 'same' => ':attribute 和 %s 不一致',
  34. 'bool' => ':attribute 必须是bool值',
  35. 'path' => ':attribute 不是有效的路径',
  36. );
  37. private $custom = array();
  38. private $rules = array();
  39. private $messages = array();
  40. private $data = array();
  41. private $errors = array();
  42. public function __construct($data, $rules = array(), $messages = array()) {
  43. $this->data = $data;
  44. $this->rules = $this->parseRule($rules);
  45. $this->messages = $messages;
  46. }
  47. public static function create($data, $rules, array $messages = array()) {
  48. return new self($data, $rules, $messages);
  49. }
  50. public function addRule($name, callable $callable) {
  51. if (!$name) {
  52. throw new InvalidArgumentException('无效的参数');
  53. }
  54. if (!is_callable($callable)) {
  55. throw new InvalidArgumentException('无效的callable 对象');
  56. }
  57. $this->custom[$name] = $callable;
  58. }
  59. public function isError() {
  60. return 0 !== count($this->errors);
  61. }
  62. public function error() {
  63. return $this->errors;
  64. }
  65. public function message() {
  66. $init = array();
  67. $errmsg = array_reduce($this->error(), function ($result, $value) {
  68. return array_merge($result, array_values($value));
  69. }, $init);
  70. return implode(',', array_values($errmsg));
  71. }
  72. public function getData() {
  73. return $this->data;
  74. }
  75. protected function parseRule(array $rules) {
  76. $result = array();
  77. if (0 == count($rules)) {
  78. throw new InvalidArgumentException('无效的rules');
  79. }
  80. foreach ($rules as $key => $rule) {
  81. $result[$key] = $this->parseSingleRule($rule);
  82. }
  83. return $result;
  84. }
  85. protected function parseSingleRule($value) {
  86. if (is_string($value)) {
  87. $rules = explode('|', $value);
  88. $result = array();
  89. foreach ($rules as $dataKey => $rule) {
  90. $kv = explode(':', $rule);
  91. $params = array();
  92. if (count($kv) > 1) {
  93. $params = explode(',', $kv[1]);
  94. }
  95. $result[] = array('name' => $kv[0], 'params' => $params);
  96. }
  97. return $result;
  98. }
  99. if (is_array($value)) {
  100. $value = array_map(function ($item) {
  101. if (is_string($item)) {
  102. $name_params = explode(':', $item);
  103. $params = array();
  104. if (count($name_params) > 1) {
  105. $params = explode(',', $name_params[1]);
  106. }
  107. return array('name' => $name_params[0], 'params' => $params);
  108. }
  109. if (!is_array($item)) {
  110. throw new InvalidArgumentException('无效的rule参数');
  111. }
  112. $newitem = $item;
  113. if (!isset($item['name'])) {
  114. $newitem = array();
  115. $newitem['name'] = $newitem[0];
  116. $newitem['params'] = count($item) > 1 ? $item[1] : array();
  117. }
  118. return $newitem;
  119. }, $value);
  120. return $value;
  121. }
  122. throw new InvalidArgumentException('无效的rule配置项');
  123. }
  124. private function getRules($key) {
  125. return isset($this->rules[$key]) ? $this->rules[$key] : array();
  126. }
  127. public function valid() {
  128. $this->errors = array();
  129. foreach ($this->data as $key => $value) {
  130. $rules = $this->getRules($key);
  131. foreach ($rules as $rule) {
  132. $this->doValid($key, $value, $rule);
  133. }
  134. }
  135. return $this->isError() ? error(1, $this->message()) : error(0);
  136. }
  137. private function doSingle($callback, $dataKey, $value, $rule) {
  138. $valid = call_user_func($callback, $dataKey, $value, $rule['params']);
  139. if (!$valid) {
  140. $this->errors[$dataKey][$rule['name']] = $this->getMessage($dataKey, $rule);
  141. return false;
  142. }
  143. return true;
  144. }
  145. private function doCustom($callback, $dataKey, $value, $rule) {
  146. $valid = call_user_func($callback, $dataKey, $value, $rule['params'], $this);
  147. if (!$valid) {
  148. $this->errors[$dataKey][$rule['name']] = $this->getMessage($dataKey, $rule);
  149. return false;
  150. }
  151. return true;
  152. }
  153. private function doValid($dataKey, $value, $rule) {
  154. $ruleName = $rule['name'];
  155. if (isset($this->defaults[$ruleName])) {
  156. $callback = array($this, 'valid' . ucfirst($ruleName));
  157. return $this->doSingle($callback, $dataKey, $value, $rule);
  158. }
  159. if (isset($this->custom[$ruleName])) {
  160. $callback = $this->custom[$ruleName];
  161. return $this->doCustom($callback, $dataKey, $value, $rule, $this);
  162. }
  163. throw new InvalidArgumentException('valid' . $rule['name'] . ' 方法未定义');
  164. }
  165. private function getValue($key) {
  166. return isset($this->data[$key]) ? $this->data[$key] : null;
  167. }
  168. protected function getMessage($dataKey, $rule) {
  169. $message = $this->getErrorMessage($dataKey, $rule['name']);
  170. if ($message) {
  171. $message = str_replace(':attribute', $dataKey, $message);
  172. $message = vsprintf($message, $rule['params']);
  173. }
  174. return $message;
  175. }
  176. protected function getErrorMessage($dataKey, $ruleName) {
  177. $dr = $dataKey . '.' . $ruleName;
  178. if ($this->messages[$dr]) {
  179. return $this->messages[$dr];
  180. }
  181. if (isset($this->messages[$dataKey])) {
  182. return $this->messages[$dataKey];
  183. }
  184. return isset($this->defaults[$ruleName]) ? $this->defaults[$ruleName] : '错误';
  185. }
  186. public function validRequired($key, $value, $params) {
  187. if (is_null($value)) {
  188. return false;
  189. }
  190. if (is_array($value)) {
  191. return 0 != count($value);
  192. }
  193. if (is_string($value)) {
  194. return '' !== $value;
  195. }
  196. return true;
  197. }
  198. public function validInteger($key, $value, $params) {
  199. return false !== filter_var($value, FILTER_VALIDATE_INT);
  200. }
  201. public function validInt($key, $value, $params) {
  202. return $this->validInteger($key, $value, $params);
  203. }
  204. public function validNumeric($key, $value, $params) {
  205. return is_numeric($value);
  206. }
  207. public function validString($key, $value, $params) {
  208. return is_string($value);
  209. }
  210. public function validJson($key, $value, $params) {
  211. if (!is_scalar($value) && !method_exists($value, '__toString')) {
  212. return false;
  213. }
  214. json_decode($value);
  215. return JSON_ERROR_NONE === json_last_error();
  216. }
  217. public function validArray($key, $value, $params) {
  218. return is_array($value);
  219. }
  220. public function validFile($key, $value, $params) {
  221. return is_file($value);
  222. }
  223. public function validImage($key, $value, $params) {
  224. return $this->isImage($value);
  225. }
  226. public function validEmail($key, $value, $params) {
  227. return filter_var($value, FILTER_VALIDATE_EMAIL);
  228. }
  229. public function validMobile($key, $value, $params) {
  230. return $this->validRegex($key, $value, array('/^1[34578]\d{9}$/'));
  231. }
  232. public function validRegex($key, $value, $params) {
  233. $this->checkParams(1, $params, 'regex');
  234. return preg_match($params[0], $value);
  235. }
  236. public function validIp($key, $value, $params) {
  237. if (!is_null($value)) {
  238. return filter_var($value, FILTER_VALIDATE_IP);
  239. }
  240. return false;
  241. }
  242. public function validSize($key, $value, $params) {
  243. $this->checkParams(1, $params, 'size');
  244. return $this->getSize($key, $value) == $params[0];
  245. }
  246. public function validMax($key, $value, $params) {
  247. $this->checkParams(1, $params, 'max');
  248. $size = $this->getSize($key, $value);
  249. return $size <= $params[0];
  250. }
  251. public function validMin($key, $value, $params) {
  252. $this->checkParams(1, $params, 'min');
  253. $size = $this->getSize($key, $value);
  254. return $size >= $params[0];
  255. }
  256. public function validUrl($key, $value, $params) {
  257. if (!filter_var($value, FILTER_VALIDATE_URL)) {
  258. return false;
  259. }
  260. $parseData = parse_url($value);
  261. $scheme = $parseData['scheme'];
  262. $allowSchemes = array('http', 'https');
  263. if (!in_array($scheme, $allowSchemes)) {
  264. return false;
  265. }
  266. if (!isset($parseData['host'])) {
  267. return false;
  268. }
  269. $host = $parseData['host'];
  270. if (strexists($host, '@')) {
  271. return false;
  272. }
  273. $pattern = '/^(10|172|192|127)/';
  274. if (preg_match($pattern, $host)) {
  275. return false;
  276. }
  277. return parse_path($value);
  278. }
  279. public function validDate($key, $value, $params) {
  280. return $this->checkDate($value);
  281. }
  282. public function validIn($key, $value, $params) {
  283. if (is_array($params)) {
  284. return in_array($value, $params, true);
  285. }
  286. return false;
  287. }
  288. public function validNotin($key, $value, $params) {
  289. return !$this->validIn($key, $value, $params);
  290. }
  291. public function validSame($key, $value, $params) {
  292. $this->checkParams(1, $params, 'same');
  293. $otherField = $params[0];
  294. $otherValue = isset($this->data[$otherField]) ? $this->data[$otherField] : null;
  295. return (is_string($value) || is_numeric($value)) && $value === $otherValue;
  296. }
  297. public function validBetween($key, $value, $params) {
  298. $this->checkParams(2, $params, 'between');
  299. $size = $this->getSize($key, $value);
  300. return $size >= $params[0] && $size <= $params[1];
  301. }
  302. public function validAfter($key, $value, $params) {
  303. $this->checkParams(1, $params, 'afterdate');
  304. $date = $params[0];
  305. return $this->compareDate($value, $date, '>');
  306. }
  307. public function validBefore($key, $value, $params) {
  308. $this->checkParams(1, $params, 'beforedate');
  309. $date = $params[0];
  310. return $this->compareDate($value, $date, '<');
  311. }
  312. private function compareDate($value, $param, $operator = '=') {
  313. if (!$this->checkDate($param)) {
  314. $param = $this->getValue($param);
  315. }
  316. if ($this->checkDate($value) && $this->checkDate($param)) {
  317. $currentTime = $this->getDateTimestamp($value);
  318. $paramTime = $this->getDateTimestamp($param);
  319. return $this->compare($currentTime, $paramTime, $operator);
  320. }
  321. return false;
  322. }
  323. public function validBool($key, $value, $params) {
  324. $acceptable = array(true, false, 0, 1, '0', '1');
  325. return in_array($value, $acceptable, true);
  326. }
  327. public function validPath($key, $value, $params) {
  328. return parse_path($value);
  329. }
  330. protected function getSize($key, $value) {
  331. if (is_numeric($value)) {
  332. return $value;
  333. } elseif (is_array($value)) {
  334. return count($value);
  335. } elseif (is_file($value)) {
  336. return filesize($value) / 1024;
  337. } elseif ($value instanceof SplFileInfo) {
  338. return $value->getSize() / 1024;
  339. } elseif (is_string($value)) {
  340. return mb_strlen($value);
  341. }
  342. return false;
  343. }
  344. private function isImage($value) {
  345. if (is_file($value)) {
  346. $filename = $value;
  347. if ($value instanceof SplFileInfo) {
  348. $filename = $value->getFilename();
  349. }
  350. if (is_string($filename)) {
  351. $pathinfo = pathinfo($filename);
  352. $extension = strtolower($pathinfo['extension']);
  353. return !empty($extension) && in_array($extension, array('jpg', 'jpeg', 'gif', 'png'));
  354. }
  355. }
  356. return false;
  357. }
  358. private function mimeTypeIsImage($mimeType) {
  359. $imgMimeType = explode(',', static::IMG_MIMETYPE);
  360. return in_array($mimeType, $imgMimeType);
  361. }
  362. private function checkDate($value) {
  363. if ($value instanceof DateTimeInterface) {
  364. return true;
  365. }
  366. if ((!is_string($value) && !is_numeric($value)) || false === strtotime($value)) {
  367. return false;
  368. }
  369. $date = date_parse($value);
  370. return checkdate($date['month'], $date['day'], $date['year']);
  371. }
  372. private function checkParams($count, $params, $ruleName) {
  373. if (count($params) != $count) {
  374. throw new InvalidArgumentException("$ruleName 参数个数必须为 $count 个");
  375. }
  376. }
  377. private function getDateTimestamp($date) {
  378. return $date instanceof DateTimeInterface ? $date->getTimestamp() : strtotime($date);
  379. }
  380. protected function compare($first, $second, $operator) {
  381. switch ($operator) {
  382. case '<':
  383. return $first < $second;
  384. case '>':
  385. return $first > $second;
  386. case '<=':
  387. return $first <= $second;
  388. case '>=':
  389. return $first >= $second;
  390. case '=':
  391. return $first == $second;
  392. default:
  393. throw new InvalidArgumentException();
  394. }
  395. }
  396. }