1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
16:
17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46:
47: class Pinoco_Validator extends Pinoco_DynamicVars
48: {
49: protected $_tests;
50: protected $_filters;
51: protected $_messages;
52:
53: private $_target;
54: private $_result;
55: private $_errors;
56: private $_values;
57:
58: 59: 60: 61: 62: 63:
64: public function __construct($target, $messages=array())
65: {
66: parent::__construct();
67:
68: $this->_tests = array();
69: $this->_setupBuiltinTests();
70:
71: $this->_filters = array();
72: $this->_setupBuiltinFilters();
73:
74: $this->_messages = array();
75: $this->overrideErrorMessages($messages);
76:
77: $this->_target = $target;
78: $this->_result = new Pinoco_Vars();
79: $this->_errors = null;
80: $this->_values = null;
81: }
82:
83: private function _setupBuiltinTests()
84: {
85:
86: $this->defineValidityTest('pass', array($this, '_testPassComplex'),
87: "Valid.", true);
88: $this->defineValidityTest('fail', array($this, '_testFailComplex'),
89: "Invalid.", true);
90: $this->defineValidityTest('empty', array($this, '_testEmptyComplex'),
91: "Leave as empty.", true);
92: $this->defineValidityTest('not-empty', array($this, '_testNotEmptyComplex'),
93: "Required.", true);
94: $this->defineValidityTest('max-length', array($this, '_testMaxLength'),
95: "In {param} letters.");
96: $this->defineValidityTest('min-length', array($this, '_testMinLength'),
97: "At least {param} letters.");
98: $this->defineValidityTest('in', array($this, '_testIn'),
99: "Choose in {param}.");
100: $this->defineValidityTest('not-in', array($this, '_testNotIn'),
101: "Choose else of {param}.");
102: $this->defineValidityTest('numeric', array($this, '_testNumeric'),
103: "By number.");
104: $this->defineValidityTest('integer', array($this, '_testInteger'),
105: "By integer number.");
106: $this->defineValidityTest('alpha', array($this, '_testAlpha'),
107: "Alphabet only.");
108: $this->defineValidityTest('alpha-numeric', array($this, '_testAlphaNumeric'),
109: "Alphabet or number.");
110: $this->defineValidityTest('array', array($this, '_testArray'),
111: "By Array.");
112: $this->defineValidityTest('==', array($this, '_testEqual'),
113: "Shuld equal to {param}.");
114: $this->defineValidityTest('!=', array($this, '_testNotEqual'),
115: "Should not equal to {param}.");
116: $this->defineValidityTest('>', array($this, '_testGreaterThan'),
117: "Greater than {param}.");
118: $this->defineValidityTest('>=', array($this, '_testGreaterThanOrEqual'),
119: "Greater than or equals to {param}.");
120: $this->defineValidityTest('<', array($this, '_testLessorThan'),
121: "Lessor than {param}.");
122: $this->defineValidityTest('<=', array($this, '_testLessorThanOrEqual'),
123: "Lessor than or equals to {param}.");
124: $this->defineValidityTest('match', array($this, '_testMatch'),
125: "Invalid pattern.");
126: $this->defineValidityTest('not-match', array($this, '_testNotMatch'),
127: "Not allowed pattern.");
128: $this->defineValidityTest('email', array($this, '_testEmail'),
129: "Email only.");
130: $this->defineValidityTest('url', array($this, '_testUrl'),
131: "URL only.");
132: }
133:
134: private function _setupBuiltinFilters()
135: {
136:
137: $this->defineFilter('trim', array($this, '_filterTrim'));
138: $this->defineFilter('ltrim', array($this, '_filterLtrim'));
139: $this->defineFilter('rtrim', array($this, '_filterRtrim'));
140: }
141:
142: 143: 144: 145: 146: 147: 148: 149: 150:
151: public function defineValidityTest($testName, $callable, $message, $complex=false)
152: {
153: $this->_tests[$testName] = array(
154: 'callback' => $callable,
155: 'message' => $message,
156: 'complex' => $complex,
157: );
158: }
159:
160: 161: 162: 163: 164: 165: 166: 167:
168: public function defineFilter($filterName, $callable, $complex=false)
169: {
170: $this->_filters[$filterName] = array(
171: 'callback' => $callable,
172: 'complex' => $complex,
173: );
174: }
175:
176: 177: 178: 179: 180: 181:
182: public function overrideErrorMessages($messages)
183: {
184: foreach ($messages as $test=>$msg) {
185: $this->_messages[$test] = $msg;
186: }
187: }
188:
189: 190: 191: 192: 193: 194:
195: public function getMessageFor($testName)
196: {
197: if (is_scalar($testName)) {
198: if (isset($this->_messages[$testName])) {
199: return $this->_messages[$testName];
200: }
201: elseif (isset($this->_tests[$testName])) {
202: return $this->_tests[$testName]['message'];
203: }
204: else {
205: return 'not registered';
206: }
207: }
208: else {
209: return 'invalid';
210: }
211: }
212:
213: 214: 215: 216: 217: 218: 219:
220: public function fetchExistenceAndValue($name)
221: {
222:
223: if ($this->_target instanceof Pinoco_Vars) {
224: $exists = $this->_target->has($name);
225: $value = $this->_target->get($name);
226: }
227: elseif ($this->_target instanceof Pinoco_List) {
228: $exists = intval($name) < $this->_target->count();
229: $value = $exists ? $this->_target[$name] : null;
230: }
231: elseif (is_array($this->_target)) {
232: $exists = isset($this->_target[$name]);
233: $value = $exists ? $this->_target[$name] : null;
234: }
235: elseif (is_object($this->_target)) {
236: $exists = isset($this->_target->$name);
237: $value = $exists ? $this->_target->$name : null;
238: }
239: else {
240: $exists = false;
241: $value = null;
242: }
243: return array($exists, $value);
244: }
245:
246: private function prepareCallback($methods, $methodName, $param)
247: {
248: $this->_errors = null;
249: $this->_values = null;
250: if (is_scalar($methodName) && isset($methods[$methodName])) {
251: return array(
252: $methods[$methodName]['callback'],
253: $methods[$methodName]['complex'],
254: array($param)
255: );
256: }
257: elseif (is_callable($methodName)) {
258: return array(
259: $methodName,
260: false,
261: $param ? explode(' ', $param) : array()
262: );
263: }
264: else {
265:
266: return array(
267: null,
268: false,
269: array()
270: );
271: }
272: }
273:
274: private function prepareValue($field, $filtered, $filteredValue)
275: {
276:
277: if ($filtered) {
278: return array(
279: true,
280: $filteredValue
281: );
282: }
283: else {
284: return $this->fetchExistenceAndValue($field);
285: }
286: }
287:
288: private function callMethod($callable, $complex, $params, $exists, $value, $forFilter=false)
289: {
290: if ($complex) {
291:
292:
293: $args = array($exists, $value);
294: }
295: else {
296: if (!$forFilter) {
297:
298: if (!$exists || empty($value) && !($value === "0" || $value === 0 || $value === false || $value === array())) {
299:
300: return array(true, $value);
301: }
302: }
303: $args = array($value);
304: }
305: foreach ($params as $p) {
306: $args[] = $p;
307: }
308: return call_user_func_array($callable, $args);
309: }
310:
311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321:
322: public function execValidityTest($field, $filtered, $filteredValue, $testName, $param)
323: {
324: list($callable, $complex, $params) = $this->prepareCallback($this->_tests, $testName, $param);
325: list($exists, $value) = $this->prepareValue($field, $filtered, $filteredValue);
326: if ($callable == null) {
327: return array(false, $value);
328: }
329: else {
330: return array(
331: $this->callMethod($callable, $complex, $params, $exists, $value),
332: $value
333: );
334: }
335: }
336:
337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347:
348: public function execValidityTestAll($field, $filtered, $filteredValue, $testName, $param)
349: {
350: list($callable, $complex, $params) = $this->prepareCallback($this->_tests, $testName, $param);
351: list($exists, $value) = $this->prepareValue($field, $filtered, $filteredValue);
352: if ($callable == null || !(is_array($value) || $value instanceof Traversable)) {
353: return array(false, $value);
354: }
355: else {
356: foreach ($value as $v) {
357: $result = $this->callMethod($callable, $complex, $params, $exists, $v);
358: if (!$result) {
359: return array(false, $value);
360: }
361: }
362: return array(true, $value);
363: }
364: }
365:
366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376:
377: public function execValidityTestAny($field, $filtered, $filteredValue, $testName, $param)
378: {
379: list($callable, $complex, $params) = $this->prepareCallback($this->_tests, $testName, $param);
380: list($exists, $value) = $this->prepareValue($field, $filtered, $filteredValue);
381: if ($callable == null || !(is_array($value) || $value instanceof Traversable)) {
382: return array(false, $value);
383: }
384: else {
385: foreach ($value as $v) {
386: $result = $this->callMethod($callable, $complex, $params, $exists, $v);
387: if ($result) {
388: return array(true, $value);
389: }
390: }
391: return array(false, $value);
392: }
393: }
394:
395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405:
406: public function execFilter($field, $filtered, $filteredValue, $filterName, $param)
407: {
408: list($callable, $complex, $params) = $this->prepareCallback($this->_filters, $filterName, $param);
409: list($exists, $value) = $this->prepareValue($field, $filtered, $filteredValue);
410: if ($callable == null) {
411: return array(true, null);
412: }
413: else {
414: return array(
415: true,
416: $this->callMethod($callable, $complex, $params, $exists, $value, true)
417: );
418: }
419: }
420:
421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431:
432: public function execFilterMap($field, $filtered, $filteredValue, $filterName, $param)
433: {
434: list($callable, $complex, $params) = $this->prepareCallback($this->_filters, $filterName, $param);
435: list($exists, $value) = $this->prepareValue($field, $filtered, $filteredValue);
436: if ($callable == null || !(is_array($value) || $value instanceof Traversable)) {
437: return array(true, null);
438: }
439: else {
440: if ($value instanceof Pinoco_List) {
441: $result = new Pinoco_List();
442: foreach ($value as $v) {
443: $result->push($this->callMethod($callable, $complex, $params, $exists, $v, true));
444: }
445: }
446: else {
447: $result = array();
448: foreach ($value as $v) {
449: $result[] = $this->callMethod($callable, $complex, $params, $exists, $v, true);
450: }
451: }
452: return array(true, $result);
453: }
454: }
455:
456: 457: 458: 459: 460: 461: 462:
463: public function contextFor($name, $label=false)
464: {
465: return new Pinoco_ValidatorContext($this, $name, $label);
466: }
467:
468: 469: 470: 471: 472: 473: 474:
475: public function check($name, $label=false)
476: {
477: $this->_errors = null;
478: $this->_values = null;
479: if (!$this->_result->has($name)) {
480: $this->_result->set($name, $this->contextFor($name, $label));
481: }
482: return $this->_result->get($name);
483: }
484:
485: 486: 487: 488: 489: 490: 491:
492: public function recheck($name, $label=false)
493: {
494: $this->_errors = null;
495: $this->_values = null;
496: $this->_result->set($name, $this->contextFor($name, $label));
497: return $this->_result->get($name);
498: }
499:
500: 501: 502: 503: 504: 505:
506: public function uncheck($name)
507: {
508: $this->_errors = null;
509: $this->_values = null;
510: if ($this->_result->has($name)) {
511: $this->_result->remove($name);
512: }
513: }
514:
515: 516: 517: 518: 519:
520: public function get_result()
521: {
522: return $this->_result;
523: }
524:
525: 526: 527: 528: 529:
530: public function get_errors()
531: {
532: if ($this->_errors === null) {
533: $this->_errors = new Pinoco_Vars();
534: foreach ($this->_result->keys() as $field) {
535: $result = $this->_result->get($field);
536: if ($result->invalid) {
537: $this->_errors->set($field, $result);
538: }
539: }
540: }
541: return $this->_errors;
542: }
543:
544: 545: 546: 547: 548:
549: public function get_values()
550: {
551: if ($this->_values === null) {
552: $this->_values = new Pinoco_Vars();
553: foreach ($this->_result->keys() as $field) {
554: $result = $this->_result->get($field);
555: $this->_values->set($field, $result->value);
556: }
557: }
558: return $this->_values;
559: }
560:
561: 562: 563: 564: 565:
566: public function get_valid()
567: {
568: return ($this->get_errors()->count() == 0);
569: }
570:
571: 572: 573: 574: 575:
576: public function get_invalid()
577: {
578: return !$this->get_valid();
579: }
580:
581: 582: 583: 584: 585: 586: 587: 588:
589: public static function emptyResult($values=array())
590: {
591: $validator = new self($values);
592: foreach ($values as $name=>$value) {
593: $validator->check($name)->is('pass');
594: }
595: $result = $validator->result;
596: $result->setDefault($validator->contextFor('any')->is('pass'));
597: $result->setLoose(true);
598: return $result;
599: }
600:
601:
602:
603: private function _testPassComplex($exists, $value)
604: {
605: return true;
606: }
607: private function _testFailComplex($exists, $value)
608: {
609: return false;
610: }
611: private function _testEmptyComplex($exists, $value)
612: {
613: if (!$exists || $value === null) { return true; }
614: if ($value === "0" || $value === 0 || $value === false || $value === array()) {
615: return false;
616: }
617: return empty($value);
618: }
619: private function _testNotEmptyComplex($exists, $value)
620: {
621: return !$this->_testEmptyComplex($exists, $value);
622: }
623:
624: private function _testMaxLength($value, $param=0)
625: {
626: return strlen(strval($value)) <= $param;
627: }
628: private function _testMinLength($value, $param=0)
629: {
630: return strlen(strval($value)) >= $param;
631: }
632: private function _testIn($value, $param='')
633: {
634: $as = explode(',', $param);
635: foreach ($as as $a) {
636: if ($value == trim($a)) { return true; }
637: }
638: return false;
639: }
640: private function _testNotIn($value, $param='')
641: {
642: return !$this->_testIn($value, $param);
643: }
644: private function _testNumeric($value)
645: {
646: return is_numeric($value);
647: }
648: private function _testInteger($value)
649: {
650: return is_integer($value);
651: }
652: private function _testAlpha($value)
653: {
654: return ctype_alpha($value);
655: }
656: private function _testAlphaNumeric($value)
657: {
658: return ctype_alnum($value);
659: }
660: private function _testArray($value)
661: {
662: return is_array($value) || $value instanceof Traversable;
663: }
664: private function _testEqual($value, $param=null)
665: {
666: return $value == $param;
667: }
668: private function _testNotEqual($value, $param=null)
669: {
670: return !$this->_testEqual($value, $param);
671: }
672: private function _testGreaterThan($value, $param=0)
673: {
674: return $value > $param;
675: }
676: private function _testGreaterThanOrEqual($value, $param=0)
677: {
678: return $value >= $param;
679: }
680: private function _testLessorThan($value, $param=0)
681: {
682: return $value < $param;
683: }
684: private function _testLessorThanOrEqual($value, $param=0)
685: {
686: return $value <= $param;
687: }
688: private function _testMatch($value, $param='/^$/')
689: {
690: return preg_match($param, $value);
691: }
692: private function _testNotMatch($value, $param='/^$/')
693: {
694: return !$this->_testMatch($value, $param);
695: }
696: private function _testEmail($value)
697: {
698: return preg_match('/@[A-Z0-9][A-Z0-9_-]*(\.[A-Z0-9][A-Z0-9_-]*)*$/i', $value);
699: }
700: private function _testUrl($value)
701: {
702: return preg_match('/^[A-Z]+:\/\/([A-Z0-9][A-Z0-9_-]*(?:\.[A-Z0-9][A-Z0-9_-]*)*):?(\d+)?\/?/i', $value);
703: }
704:
705:
706:
707: private function _filterTrim($value)
708: {
709: return trim($value);
710: }
711: private function _filterLtrim($value)
712: {
713: return ltrim($value);
714: }
715: private function _filterRtrim($value)
716: {
717: return rtrim($value);
718: }
719: }
720:
721: