Skip to content

Commit

Permalink
Merge pull request #2 from garyttierney/master
Browse files Browse the repository at this point in the history
Virtual Elements, Compatability with Symfony 2.2, Validation Updates
  • Loading branch information
dragoonis committed Jul 3, 2014
2 parents 7a082f7 + cc22c57 commit 1aac85b
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 16 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
},

"require": {
"symfony/Validator" : "*"
"symfony/Validator" : "2.2.*"
},

"require-dev": {
Expand All @@ -14,3 +14,4 @@

"minimum-stability": "dev"
}

25 changes: 21 additions & 4 deletions src/Element/Element.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -191,10 +193,12 @@ public function validate(ValidatorInterface $validator)
}
}

/**
/*
*
* Set element options
*
* @param array $options
*
*/
public function setOptions($options)
{
Expand Down Expand Up @@ -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);
}
}
3 changes: 2 additions & 1 deletion src/Element/Label.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
class Label extends BaseElement
{



protected $type = 'label';

/**
Expand All @@ -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 = '<label %s>%s</label>';
Expand Down
54 changes: 54 additions & 0 deletions src/Element/Virtual.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php


namespace PPI\Form\Element;

/**
* An abstract Element which encapsulates the rendering of several inputs and transforms the independent data into something
* suitable for validation.
*
* Class Virtual
* @package PPI\Form\Element
*/
abstract class Virtual extends Element
{
protected $elements = array();

public function addElement(Element $element)
{
$this->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);
}
82 changes: 72 additions & 10 deletions src/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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'));
}

}
Expand Down Expand Up @@ -280,6 +282,7 @@ public function hasElement($name)
return isset($this->elements[$name]);
}


/**
* Apply some bind data to this form.
*
Expand Down Expand Up @@ -341,28 +344,87 @@ public function end()
return '</form>';
}


/**
* 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();

/**
* @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;
}
}

0 comments on commit 1aac85b

Please sign in to comment.