Skip to content

Instantly share code, notes, and snippets.

@jaytaph
Last active October 21, 2015 08:50
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jaytaph/a01c441b50c29f58ce14 to your computer and use it in GitHub Desktop.
Save jaytaph/a01c441b50c29f58ce14 to your computer and use it in GitHub Desktop.
<?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);
}
}
$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();
<?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;
}
}
<?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