diff --git a/composer.json b/composer.json index 6158e56..266bae9 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ }, "require": { - "symfony/Validator" : "*" + "symfony/Validator" : "2.2.*" }, "require-dev": { @@ -14,3 +14,4 @@ "minimum-stability": "dev" } + diff --git a/src/Element/Element.php b/src/Element/Element.php index f65372c..60bfa4f 100644 --- a/src/Element/Element.php +++ b/src/Element/Element.php @@ -9,10 +9,11 @@ namespace PPI\Form\Element; use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Validator\ValidatorInterface; +use Symfony\Component\Validator\ValidatorInterface; abstract class Element implements ElementInterface { + protected $label; /** * The constructor @@ -174,12 +175,13 @@ public function getConstraints() * @todo garyttierney - do we want the Form to handle validation for every input or should that be left to an element? * if the latter, we should share a single Validator object between all of them. * + * @param $input string|array * @return ElementValidationResult */ - public function validate(ValidatorInterface $validator) + public function validate($input, ValidatorInterface $validator) { // @todo - note, this will be removed in Symfony 3.0 and there's currently no way around that - $validatorResult = $validator->validateValue($this->getValue(), $this->getConstraints()); + $validatorResult = $validator->validateValue($input, $this->getConstraints()); if(count($validatorResult) === 0) { return new ElementValidationResult(true); } else { @@ -191,10 +193,12 @@ public function validate(ValidatorInterface $validator) } } - /** + /* + * * Set element options * * @param array $options + * */ public function setOptions($options) { @@ -274,5 +278,18 @@ public function getType() return $this->type; } + public function getLabel() + { + return $this->label; + } + + public function setLabel($label) + { + $this->label = $label; + } + public function hasLabel() + { + return isset($this->label); + } } diff --git a/src/Element/Label.php b/src/Element/Label.php index bd28da3..d4cd563 100644 --- a/src/Element/Label.php +++ b/src/Element/Label.php @@ -13,6 +13,8 @@ class Label extends BaseElement { + + protected $type = 'label'; /** @@ -22,7 +24,6 @@ class Label extends BaseElement */ function render() { - // Auto-set the 'for' attribute of this to match the ID of the 'name' $html = ''; diff --git a/src/Element/Virtual.php b/src/Element/Virtual.php new file mode 100644 index 0000000..dc58744 --- /dev/null +++ b/src/Element/Virtual.php @@ -0,0 +1,54 @@ +elements[] = $element; + } + + public function __construct($options = array()) + { + parent::__construct($options); + } + + /** + * Render the tag + * + * @return string + */ + public function render() + { + $parts = array(); + + foreach($this->elements as $element) { + $parts[] = $element->render(); + } + + return implode("\n", $parts); + } + + /** + * Takes an array of input and returns a string. Should be used when you have 2 elements which should be concatenated into 1 variable. + * + * For example, if you want a TimeElement which allows the user to choose hours via a select element, and minutes via a select element + * then you should get the input as an array (input_x[0] -- hours, input_x[1] -- minutes) and implement this method + * to join both of those inputs. + * + * @param array $data + * @return string + */ + public abstract function transformInput(array $data); +} \ No newline at end of file diff --git a/src/Form.php b/src/Form.php index de4e7bb..c900187 100644 --- a/src/Form.php +++ b/src/Form.php @@ -10,6 +10,8 @@ use PPI\Form\Element\Element; use PPI\Form\Element\ElementInterface; +use PPI\Form\Element\Label; +use PPI\Form\Element\Virtual; use Symfony\Component\Validator\Validation; class Form @@ -132,7 +134,7 @@ public function submit($name, $value = 'Submit', array $options = array()) */ public function hidden($name, array $options = array()) { - return $this->add($name, 'hidden', $options); + return $this->add('hidden', 'hidden', $options); } /** @@ -221,8 +223,8 @@ public function add($elementType, $name, array $options = array()) // @todo - keep a list of "labels to process" so if you add an element at a later date, // it will find previously added labels and populate them // Setup the for="" for this label to pull from the element's ID matching $name - if (isset($this->elements[$name]) && $this->elements[$name]->hasAttribute('id')) { - $element->setAttribute('for', $this->elements[$name]->getAttribute('id')); + if (isset($this->elements[$name]) && $this->elements[$name]->hasAttr('id')) { + $element->setAttr('for', $this->elements[$name]->getAttr('id')); } } @@ -280,6 +282,7 @@ public function hasElement($name) return isset($this->elements[$name]); } + /** * Apply some bind data to this form. * @@ -341,13 +344,16 @@ public function end() return ''; } + /** * Validates all input elements within a form with their constraints. Returns a hashtable of element names mapped * to an array of their error messages as described in {@link ElementValidationResult::getErrorMessages} * - * @return array + * @param array $input The clients POST data + * + * @return array An array of error messages keyed by element name */ - public function validate() + public function validate(array $input) { $validator = Validation::createValidator(); $errors = array(); @@ -355,14 +361,70 @@ public function validate() /** * @var $element Element */ - foreach($this->elements as $elementName => $element) { - $validationResult = $element->validate($validator); - if($validationResult->isSuccessful()) { + foreach($this->elements as $key => $element) { + if($element instanceof Label) { continue; } - $errors[$elementName] = $validationResult->getErrorMessages(); + if(substr($key, -2) == "[]") { + $elementName = substr($key, 0, -2); + } else { + $elementName = $key; + } + + if(count($element->getConstraints()) > 0) { + $value = array_key_exists($elementName, $input) ? $input[$elementName] : null; // map elements with no value to null + if(is_array($value) && $element instanceof Virtual) { + $value = $element->transformData($value); + } + + $validationResult = $element->validate($value, $validator); + + if ($validationResult->isSuccessful()) { + continue; + } + + $errors[$elementName] = $validationResult->getErrorMessages(); + } + } + + $this->errorMessages = $errors; + + return count($errors) == 0; + } + + protected $errorMessages = array(); + + public function getErrorsForElement(Element $element) + { + return $this->errorMessages[$element->getName()]; + } + + public function hasErrorsForElement(Element $element) + { + return array_key_exists($element->getName(), $this->errorMessages); + } + + /** + * Takes an $input an array and checks if the input key corresponds to a Virtual element, if it does then it calls + * the {@see Virtual::transformInput} function to make the input value good for validation. + * + * @param array $input An array of $_POSTed variables. + */ + public function transformInput(array $input) + { + $output = array(); + + foreach($input as $key => $val) { + if(array_key_exists($key, $this->elements) && $this->elements[$key] instanceof Virtual) { + if (is_array($input)) { + $output[$key] = $this->elements[$key]->transformInput($val); + } + } else { + $output[$key] = $val; + } } - return $errors; + + return $output; } } \ No newline at end of file