1: <?php
2:
3: /**
4: * Jyxo PHP Library
5: *
6: * LICENSE
7: *
8: * This source file is subject to the new BSD license that is bundled
9: * with this package in the file license.txt.
10: * It is also available through the world-wide-web at this URL:
11: * https://github.com/jyxo/php/blob/master/license.txt
12: */
13:
14: /**
15: * Class implementing "fluent" design pattern for Jyxo_Input.
16: *
17: * Allows chaining multiple validators and checking multiple values in one validation cycle.
18: *
19: * @category Jyxo
20: * @package Jyxo_Input
21: * @copyright Copyright (c) 2005-2011 Jyxo, s.r.o.
22: * @license https://github.com/jyxo/php/blob/master/license.txt
23: * @author Jakub Tománek
24: */
25: class Jyxo_Input_Fluent
26: {
27: /**
28: * Validator class names prefix.
29: *
30: * @var string
31: */
32: const VALIDATORS_PREFIX = 'Jyxo_Input_Validator_';
33:
34: /**
35: * Filter class names prefix.
36: *
37: * @var string
38: */
39: const FILTERS_PREFIX = 'Jyxo_Input_Filter_';
40:
41: /**
42: * All chains.
43: *
44: * @var array
45: */
46: private $chains = array();
47:
48: /**
49: * All values.
50: *
51: * @var array
52: */
53: private $values = array();
54:
55: /**
56: * Default variable values.
57: *
58: * @var array
59: */
60: private $default = array();
61:
62: /**
63: * Current variable.
64: *
65: * @var string
66: */
67: private $currentName;
68:
69: /**
70: * Current chain.
71: *
72: * @var Jyxo_Input_Chain
73: */
74: private $chain = null;
75:
76: /**
77: * Errors.
78: *
79: * @var array
80: */
81: private $errors = array();
82:
83: /**
84: * Jyxo_Input objects factory.
85: *
86: * @var Jyxo_Input_Factory
87: */
88: private $factory;
89:
90: /**
91: * Constructor.
92: */
93: public function __construct()
94: {
95: $this->factory = new Jyxo_Input_Factory();
96: $this->all();
97: }
98:
99: /**
100: * Starts a new value checking.
101: *
102: * @param mixed $var Value to check.
103: * @param string $name Variable name
104: * @return Jyxo_Input_Fluent
105: */
106: public function check($var, $name)
107: {
108: $this->chain = new Jyxo_Input_Chain();
109: $this->chains[$name] = $this->chain;
110: $this->values[$name] = $var;
111: $this->default[$name] = null;
112: $this->currentName = $name;
113: return $this;
114: }
115:
116: /**
117: * Validates all variables.
118: *
119: * @return Jyxo_Input_Fluent
120: */
121: public function all()
122: {
123: $this->chain = new Jyxo_Input_Chain();
124: $this->chains[uniqid('fluent:')] = $this->chain;
125: $this->currentName = null;
126: return $this;
127: }
128:
129: /**
130: * Sets a default value in case the validation fails.
131: *
132: * @param mixed $value Default value
133: * @return Jyxo_Input_Fluent
134: * @throws BadMethodCallException There is no active variable.
135: */
136: public function defaultValue($value)
137: {
138: if (null === $this->currentName) {
139: throw new BadMethodCallException('No active variable');
140: }
141:
142: $this->default[$this->currentName] = $value;
143:
144: return $this;
145: }
146:
147: /**
148: * Adds a validator to the chain.
149: *
150: * @param string $name Validator name
151: * @param string $errorMessage Validator error message
152: * @param mixed $param Additional validator parameter
153: * @return Jyxo_Input_Fluent
154: */
155: public function validate($name, $errorMessage = null, $param = null)
156: {
157: $this->chain->addValidator($this->factory->getValidatorByName($name, $param), $errorMessage);
158: return $this;
159: }
160:
161: /**
162: * Adds a filter to the chain.s
163: *
164: * @param string $name Filter name
165: * @param mixed $param Additional filter parameter
166: * @return Jyxo_Input_Fluent
167: */
168: public function filter($name, $param = null)
169: {
170: $this->chain->addFilter($this->factory->getFilterByName($name, $param));
171: return $this;
172: }
173:
174: /**
175: * Adds a subchain to the current chain that treats the value a an array.
176: * Automatically adds the isArray validator.
177: *
178: * @param boolean $addFilter Add the Trim filter (removes empty elements)
179: * @return Jyxo_Input_Fluent
180: */
181: public function walk($addFilter = true)
182: {
183: $this->validate('isArray');
184: if (false != $addFilter) {
185: $this->filter('trim');
186: }
187: $this->chain = $this->chain->addWalk();
188: return $this;
189: }
190:
191: /**
192: * Adds a conditional chain.
193: *
194: * If there are conditions in the current chain, adds the condition as a subchain.
195: *
196: * @param string $name Validator name
197: * @param mixed $param Additional validator parameter
198: * @return Jyxo_Input_Fluent
199: * @throws BadMethodCallException There is no active variable
200: */
201: public function condition($name, $param = null)
202: {
203: $condChain = new Jyxo_Input_Chain_Conditional($this->factory->getValidatorByName($name, $param));
204: if (true === $this->chain->isEmpty()) {
205: // The actual chain is empty, can be replaced by the condition
206: $this->chain = $condChain;
207: if (null === $this->currentName) {
208: throw new BadMethodCallException('No active variable');
209: }
210: $this->chains[$this->currentName] = $condChain;
211: } else {
212: $this->chain = $this->chain->addCondition($condChain);
213: }
214: return $this;
215: }
216:
217: /**
218: * Closes a chain.
219: *
220: * @return Jyxo_Input_Fluent
221: */
222: public function close()
223: {
224: $this->chain = $this->chain->close();
225: return $this;
226: }
227:
228: /**
229: * Performs validation and filtering of all variables.
230: *
231: * @param boolean $assocErrors Return error messages in an associative array
232: * @return boolean
233: */
234: public function isValid($assocErrors = false)
235: {
236: $valid = true;
237: foreach ($this->chains as $name => $chain) {
238: /* @var $chain Jyxo_Input_Chain */
239: if (array_key_exists($name, $this->values)) {
240: // Variable
241: if (!$this->checkChain($chain, $this->values[$name], $this->default[$name], $assocErrors ? $name : null)) {
242: $valid = false;
243: }
244: } elseif (!$chain->isEmpty()) {
245: foreach ($this->values as $name => &$value) {
246: if (!$this->checkChain($chain, $value, $this->default[$name])) {
247: $valid = false;
248: // No need to check other variables
249: break;
250: }
251: }
252: }
253: }
254:
255: return $valid;
256: }
257:
258: /**
259: * Calls isValid(), but throws an exception on error.
260: *
261: * The exception contains only the first validation error message.
262: *
263: * @throws Jyxo_Input_Validator_Exception Validation failed
264: */
265: public function validateAll()
266: {
267: if (!$this->isValid()) {
268: throw new Jyxo_Input_Validator_Exception(reset($this->errors));
269: }
270: }
271:
272: /**
273: * Checks a chain.
274: *
275: * @param Jyxo_Input_Chain $chain Validation chain
276: * @param mixed $value Input value
277: * @param mixed $default Default value to be used in case the validation fails
278: * @param string $name Chain name to be used in the error array
279: * @return boolean
280: */
281: private function checkChain(Jyxo_Input_Chain $chain, &$value, $default, $name = null)
282: {
283: $valid = true;
284: if ($chain->isValid($value)) {
285: $value = $chain->getValue();
286: } elseif (null !== $default) {
287: $value = $default;
288: } else {
289: $valid = false;
290: // If we have $name set, we want an associative array
291: $errors = empty($name) ? $chain->getErrors() : array($name => $chain->getErrors());
292: $this->errors = array_merge($this->errors, $errors);
293: }
294: return $valid;
295: }
296:
297: /**
298: * Returns all values.
299: *
300: * @return array
301: */
302: public function getValues()
303: {
304: return $this->values;
305: }
306:
307: /**
308: * Returns a value by name.
309: *
310: * @param string $name Variable name
311: * @return mixed
312: * @throws Jyxo_Input_Exception No variable with the given name
313: */
314: public function getValue($name)
315: {
316: if (!array_key_exists($name, $this->values)) {
317: throw new Jyxo_Input_Exception('Value is not present');
318: }
319:
320: return $this->values[$name];
321: }
322:
323: /**
324: * Returns errors.
325: *
326: * @return array
327: */
328: public function getErrors()
329: {
330: return $this->errors;
331: }
332:
333: /**
334: * Checks a POST variable.
335: *
336: * @param string $name Variable name
337: * @param mixed $default Default value
338: * @return Jyxo_Input_Fluent
339: */
340: public function post($name, $default = null)
341: {
342: $this->addToCheck($_POST, $name, $default);
343: return $this;
344: }
345:
346: /**
347: * Checks a GET variable.
348: *
349: * @param string $name Variable name
350: * @param mixed $default Default value
351: * @return Jyxo_Input_Fluent
352: */
353: public function query($name, $default = null)
354: {
355: $this->addToCheck($_GET, $name, $default);
356: return $this;
357: }
358:
359: /**
360: * Checks a POST/GET variable
361: *
362: * @param string $name Variable name
363: * @param mixed $default Default value
364: * @return Jyxo_Input_Fluent
365: */
366: public function request($name, $default = null)
367: {
368: $this->addToCheck($_REQUEST, $name, $default);
369: return $this;
370: }
371:
372: /**
373: * Checks file upload.
374: *
375: * Requires Jyxo_Input_Upload.
376: *
377: * @param string $index File index
378: * @see Jyxo_Input_Upload
379: * @return Jyxo_Input_Fluent
380: */
381: public function file($index)
382: {
383: $validator = new Jyxo_Input_Validator_Upload();
384: $file = new Jyxo_Input_Upload($index);
385: $this
386: ->check($file, $index)
387: ->validate($validator)
388: ->filter($validator);
389: return $this;
390: }
391:
392: /**
393: * Adds a variable to the chain.
394: *
395: * @param array $global Variable array
396: * @param string $name Variable name
397: * @param mixed $default Default value
398: */
399: private function addToCheck(array $global, $name, $default = null)
400: {
401: $var = isset($global[$name]) ? $global[$name] : $default;
402: $this->check($var, $name);
403: }
404:
405: /**
406: * Magic getter for easier retrieving of values.
407: *
408: * @param string $offset Value name
409: * @return mixed
410: * @throws Jyxo_Input_Exception No variable with the given name
411: */
412: public function __get($offset)
413: {
414: return $this->getValue($offset);
415: }
416: }
417: