Last active
October 21, 2015 08:50
-
-
Save jaytaph/a01c441b50c29f58ce14 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Rainbow\FormBundle\Form\DataMapper; | |
use Symfony\Component\Form\DataMapperInterface; | |
use Symfony\Component\Form\Exception; | |
use Symfony\Component\Form\Exception\UnexpectedTypeException; | |
use Symfony\Component\Form\FormInterface; | |
use Symfony\Component\PropertyAccess\PropertyAccess; | |
use Symfony\Component\PropertyAccess\PropertyAccessorInterface; | |
/** | |
* Data mapper that uses the constructor arguments to map form fields. This makes it independent on your | |
* form element order, plus it can use default values if form fields are missing, but that given constructor argument | |
* has a default value. | |
* | |
* When you don't attach a value object directly to the form, you must set the `data_class` to the actual VO | |
* class, PLUS you must set `empty_data` to `null` | |
*/ | |
class DynamicValueObjectMapper implements DataMapperInterface | |
{ | |
/** @var PropertyAccessorInterface */ | |
protected $accessor; | |
function __construct() | |
{ | |
$this->accessor = PropertyAccess::createPropertyAccessorBuilder() | |
->enableExceptionOnInvalidIndex() | |
->enableMagicCall() | |
->getPropertyAccessor() | |
; | |
} | |
/** | |
* Maps properties of some data to a list of forms. | |
* | |
* @param mixed $data Structured data. | |
* @param FormInterface[] $forms A list of {@link FormInterface} instances. | |
* | |
* @throws UnexpectedTypeException if the type of the data parameter is not supported. | |
*/ | |
public function mapDataToForms($data, $forms) | |
{ | |
// No data attached, so we don't need to do anything. | |
if ($data === null) { | |
return; | |
} | |
foreach (iterator_to_array($forms) as $form) { | |
/* @var $form FormInterface */ | |
if (! $form->getConfig()->getMapped()) { | |
continue; | |
} | |
$propertyPath = $form->getConfig()->getPropertyPath(); | |
if (! $propertyPath) { | |
$propertyPath = $form->getName(); | |
} | |
$value = $this->accessor->getValue($data, $propertyPath); | |
$form->setData($value); | |
} | |
} | |
/** | |
* Maps the data of a list of forms into the properties of some data. | |
* | |
* @param FormInterface[] $forms A list of {@link FormInterface} instances. | |
* @param mixed $data Structured data. | |
* | |
* @throws UnexpectedTypeException if the type of the data parameter is not supported. | |
*/ | |
public function mapFormsToData($forms, &$data) | |
{ | |
$className = ""; | |
// Store all form values into fieldName => value array | |
$formValues = array(); | |
foreach (iterator_to_array($forms) as $form) { | |
/* @var $form FormInterface */ | |
// Store class name for when we need to use it later (@TODO: we should do this once, but doesn't matter for now) | |
$className = $form->getRoot()->getConfig()->getDataClass(); | |
// Form element not mapped, don't process it | |
if (! $form->getConfig()->getMapped()) { | |
continue; | |
} | |
$formValues[$form->getName()] = $form->getData(); | |
} | |
// Data is not an object? Then no data was attached to begin with | |
if (! is_object($data)) { | |
// Create a new value object based on the class name configured in the form | |
$class = new \ReflectionClass($className); | |
} else { | |
// Create a new value object based on the class name inside the data | |
$class = new \ReflectionClass($data); | |
} | |
// Iterate all constructor arguments of our value object | |
$args = array(); | |
foreach ($class->getConstructor()->getParameters() as $param) { | |
$name = $param->getName(); | |
// If the argument has no form field matching, and no default value is set in the | |
// value object, throw exception. | |
if (! isset($formValues[$name]) && ! $param->isDefaultValueAvailable()) { | |
throw new Exception\InvalidArgumentException(sprintf("Form field '%s' is not found when trying to construct the value object '%s'", $name, get_class($data))); | |
} | |
// Add either the value, or the constructor's default value to the argument list | |
$args[] = isset($formValues[$name]) ? $formValues[$name] : $param->getDefaultValue(); | |
} | |
// instantiate a new value object with given arguments | |
$data = $class->newInstanceArgs($args); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$emailAddress = new ValueObject("info", "symfony-rainbow.com", new \DateTime()); | |
$form = $this->createFormBuilder($emailAddress) | |
->add('localPart', 'text', array('attr' => array('size' => 50))) | |
->add('domainPart', 'text', array('attr' => array('size' => 50))) | |
->add('validFrom', 'date', array('attr' => array('size' => 50))) | |
->setDataMapper(new ValueObjectMapper()) | |
->getForm(); | |
/* Or when you don't add an initial value object (note both data_class and empty_data must be configured like this!)*/ | |
$form = $this->createFormBuilder(null, array( | |
'data_class' => '\Rainbow\FormBundle\Entity\EmailAddress', | |
'empty_data' => null, | |
)) | |
->add('localPart', 'text', array('attr' => array('size' => 50))) | |
->add('domainPart', 'text', array('attr' => array('size' => 50))) | |
->add('validFrom', 'date', array('attr' => array('size' => 50))) | |
->setDataMapper(new ValueObjectMapper()) | |
->getForm(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Rainbow\FormBundle\Entity; | |
final class ValueObject | |
{ | |
private $localPart; | |
private $domainPart; | |
private $validFrom; | |
public function __construct($localPart, $domainPart, \DateTime $validFrom) | |
{ | |
$this->localPart = $localPart; | |
$this->domainPart = $domainPart; | |
$this->validFrom = $validFrom; | |
} | |
public function getDomainPart() | |
{ | |
return $this->domainPart; | |
} | |
public function getLocalPart() | |
{ | |
return $this->localPart; | |
} | |
public function getValidFrom() | |
{ | |
return $this->validFrom; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Rainbow\FormBundle\Form\DataMapper; | |
use Symfony\Component\Form\DataMapperInterface; | |
use Symfony\Component\Form\Exception; | |
use Symfony\Component\Form\FormInterface; | |
use Symfony\Component\PropertyAccess\PropertyAccess; | |
use Symfony\Component\PropertyAccess\PropertyAccessorInterface; | |
class ValueObjectMapper implements DataMapperInterface | |
{ | |
/** @var PropertyAccessorInterface */ | |
protected $accessor; | |
function __construct() | |
{ | |
$this->accessor = PropertyAccess::createPropertyAccessorBuilder() | |
->enableExceptionOnInvalidIndex() | |
->enableMagicCall() | |
->getPropertyAccessor() | |
; | |
} | |
/** | |
* Maps properties of some data to a list of forms. | |
* | |
* @param mixed $data Structured data. | |
* @param FormInterface[] $forms A list of {@link FormInterface} instances. | |
* | |
* @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported. | |
*/ | |
public function mapDataToForms($data, $forms) | |
{ | |
// No data attached, so we don't need to do anything. | |
if ($data === null) { | |
return; | |
} | |
foreach (iterator_to_array($forms) as $form) { | |
/* @var $form FormInterface */ | |
// Form element not mapped, don't process it | |
if (! $form->getConfig()->getMapped()) { | |
continue; | |
} | |
// Use either the property path option, or the name of the form field | |
$propertyPath = $form->getConfig()->getPropertyPath(); | |
if (! $propertyPath) { | |
$propertyPath = $form->getName(); | |
} | |
$value = $this->accessor->getValue($data, $propertyPath); | |
$form->setData($value); | |
} | |
} | |
/** | |
* Maps the data of a list of forms into the properties of some data. | |
* | |
* @param FormInterface[] $forms A list of {@link FormInterface} instances. | |
* @param mixed $data Structured data. | |
* | |
* @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported. | |
*/ | |
public function mapFormsToData($forms, &$data) | |
{ | |
$className = array(); | |
$args = array(); | |
foreach (iterator_to_array($forms) as $form) { | |
/* @var $form FormInterface */ | |
// Store class name for when we need to use it later (@TODO: we should do this once, but doesn't matter for now) | |
$className = $form->getRoot()->getConfig()->getDataClass(); | |
// Form element not mapped, don't process it | |
if (! $form->getConfig()->getMapped()) { | |
continue; | |
} | |
$args[] = $form->getData(); | |
} | |
// Data is not an object? Then no data was attached to begin with | |
if (! is_object($data)) { | |
// Create a new value object based on the class name configured in the form | |
$class = new \ReflectionClass($className); | |
} else { | |
// Create a new value object based on the class name inside the data | |
$class = new \ReflectionClass($data); | |
} | |
// Instantiate new value object with the given $args | |
$data = $class->newInstanceArgs($args); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment