1: <?php
2: /**
3: * Wei Framework
4: *
5: * @copyright Copyright (c) 2008-2015 Twin Huang
6: * @license http://opensource.org/licenses/mit-license.php MIT License
7: */
8:
9: namespace Wei\Validator;
10:
11: use Wei\Base;
12:
13: /**
14: * The base class of validator
15: *
16: * @author Twin Huang <twinhuang@qq.com>
17: * @method string t(string $message, array $parameters = array()) Translates a message
18: * @property \Wei\T $t The translator wei
19: */
20: abstract class BaseValidator extends Base
21: {
22: /**
23: * The message added when the input required a stringify value
24: *
25: * @var string
26: */
27: protected $notStringMessage = '%name% must be a string';
28:
29: /**
30: * The common message for negative validator
31: *
32: * @var string
33: */
34: protected $negativeMessage = '%name% is not valid';
35:
36: /**
37: * The name display in error message
38: *
39: * @var string
40: */
41: protected $name = 'This value';
42:
43: /**
44: * The summary message that would overwrite all $this->xxxMessage messages
45: * when called addError, which MAY useful when you want to set message for
46: * the validator
47: *
48: * @var string
49: * @internal
50: */
51: protected $message;
52:
53: /**
54: * Whether it's a negative validator, for example, notDigit is digit's
55: * negative validator. The negative validator will returns $this->negativeMessage
56: * as the error message currently
57: *
58: * @var bool
59: */
60: protected $negative = false;
61:
62: /**
63: * The error definition
64: *
65: * @var array
66: */
67: protected $errors = array();
68:
69: /**
70: * The validate wei, available when this rule validator is invoked from validate wei
71: *
72: * @var \Wei\Validate
73: */
74: protected $validator;
75:
76: /**
77: * An array contains the validator original property values
78: *
79: * @var array
80: * @internal
81: */
82: protected $backup = array();
83:
84: /**
85: * The array to store previous and current called parameters from __invoke
86: *
87: * @var array
88: * @internal
89: */
90: protected $store = array(array());
91:
92: /**
93: * Validate the input value
94: *
95: * @param mixed $input
96: * @return bool
97: */
98: public function __invoke($input)
99: {
100: return $this->isValid($input);
101: }
102:
103: /**
104: * {@inheritdoc}
105: */
106: public function isValid($input)
107: {
108: // Clean previous status
109: $this->reset();
110: return $this->negative xor $this->doValidate($input);
111: }
112:
113: /**
114: * Validate the input value (ignore the $negative property)
115: *
116: * @param mixed $input The input to be validated
117: * @return boolean
118: */
119: abstract protected function doValidate($input);
120:
121: /**
122: * Returns the error messages
123: *
124: * @param string $name The name display in error message
125: * @return array
126: * @throws \UnexpectedValueException When message contains unknown parameter
127: */
128: public function getMessages($name = null)
129: {
130: !$name && $name = $this->name;
131: $this->negative && $this->addError('negative');
132:
133: $this->loadTranslationMessages();
134:
135: $messages = array();
136: foreach ($this->errors as $optionName => $message) {
137: preg_match_all('/\%(.+?)\%/', $message, $matches);
138: $parameters = array();
139: foreach ($matches[1] as $match) {
140: if ('name' == $match) {
141: $parameters['%name%'] = $this->t($name);
142: } else {
143: if (!property_exists($this, $match)) {
144: throw new \UnexpectedValueException(sprintf('Unknown parameter "%%%s%%" in message "%s"', $match, $message));
145: }
146: $parameters['%' . $match . '%'] = is_array($this->$match) ?
147: implode(', ', $this->$match) : $this->$match;
148: }
149: }
150: $messages[$optionName] = $this->t($message, $parameters);
151: }
152: return $messages;
153: }
154:
155: /**
156: * Returns error message string
157: *
158: * @param string $separator The string to connect messages
159: * @param string $name The name display in error message
160: * @return string
161: */
162: public function getJoinedMessage($separator = "\n", $name = null)
163: {
164: return implode($separator, $this->getMessages($name));
165: }
166:
167: /**
168: * Returns the first error message
169: *
170: * @param string $name
171: * @return string
172: */
173: public function getFirstMessage($name = null)
174: {
175: return current($this->getMessages($name));
176: }
177:
178: /**
179: * Sets the specified messages
180: *
181: * @param array $messages
182: * @return BaseValidator
183: */
184: public function setMessages(array $messages)
185: {
186: foreach ($messages as $name => $message) {
187: $this->{$name . 'Message'} = $message;
188: }
189: return $this;
190: }
191:
192: /**
193: * Returns message name
194: *
195: * @return string
196: */
197: public function getName()
198: {
199: return $this->name;
200: }
201:
202: /**
203: * Sets message name
204: *
205: * @param string $name
206: * @return BaseValidator
207: */
208: public function setName($name)
209: {
210: $this->name = $name;
211: return $this;
212: }
213:
214: /**
215: * Returns error definition
216: *
217: * @return array
218: */
219: public function getErrors()
220: {
221: return $this->errors;
222: }
223:
224: /**
225: * Returns whether the error defined
226: *
227: * @param string $name
228: * @return bool
229: */
230: public function hasError($name)
231: {
232: return isset($this->errors[$name]);
233: }
234:
235: /**
236: * Adds error definition
237: *
238: * @param string $name The name of error
239: * @param string $customMessage The custom error message
240: */
241: protected function addError($name, $customMessage = null)
242: {
243: $this->errors[$name] = $customMessage ?: $this->message ?: $this->{$name . 'Message'};
244: }
245:
246: /**
247: * Loads the validator translation messages
248: */
249: protected function loadTranslationMessages()
250: {
251: $this->t->loadFromFile(__DIR__ . '/i18n/%s.php');
252: }
253:
254: /**
255: * Checks if the input value could be convert to string
256: *
257: * @param mixed $input
258: * @return bool
259: */
260: protected function isString($input)
261: {
262: return is_scalar($input) || is_null($input) || (is_object($input) && method_exists($input, '__toString'));
263: }
264:
265: /**
266: * Set property value
267: *
268: * @param string|array $name The name of property
269: * @param mixed $value The value of property
270: * @internal This method should be use to set __invoke arguments only
271: */
272: protected function storeOption($name, $value = null)
273: {
274: // Handle array
275: if (is_array($name)) {
276: foreach ($name as $key => $value) {
277: $this->storeOption($key, $value);
278: }
279: return;
280: }
281:
282: if (property_exists($this, $name)) {
283: if (!array_key_exists($name, $this->backup)) {
284: $this->backup[$name] = $this->$name;
285: }
286: $this->store[count($this->store) - 1][$name] = $value;
287: }
288: $this->setOption($name, $value);
289: }
290:
291: /**
292: * Reset validator status
293: *
294: * @internal
295: */
296: protected function reset()
297: {
298: $this->errors = array();
299:
300: if (count($this->store) >= 2) {
301: $last = end($this->store);
302: foreach ($this->backup as $name => $value) {
303: if (!array_key_exists($name, $last)) {
304: $this->$name = $value;
305: }
306: }
307: array_shift($this->store);
308: }
309: $this->store[] = array();
310: }
311: }
312: