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