Skip to content

Instantly share code, notes, and snippets.

@beberlei
Created November 24, 2012 23:51
Show Gist options
  • Save beberlei/4141842 to your computer and use it in GitHub Desktop.
Save beberlei/4141842 to your computer and use it in GitHub Desktop.
Symfony Form DataMapper that uses a command method to write data back on object
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\Core\DataMapper;
use Symfony\Component\Form\Util\VirtualFormAwareIterator;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Form\Exception\ErrorMappingException;
/**
* DataMapper using a command method to set values on object.
*
* Behaves like PropertyPathMapper with regard to getting values from
* an object through "get*" methods, but uses a single command method to
* set all the values from a form. The argument names are inferred through
* reflection and have to be named after the property path values.
*/
class CommandMethodMapper extends PropertyPathMapper
{
/**
* Method to call on data_class of the form to set all values.
*
* @var string
*/
private $methodName;
public function __construct($methodName)
{
$this->methodName = $methodName;
}
/**
* {@inheritdoc}
*/
public function mapFormsToData(array $forms, &$data)
{
if (null === $data) {
return;
}
if (!is_object($data)) {
throw new UnexpectedTypeException($data, 'object or empty');
}
$iterator = new VirtualFormAwareIterator($forms);
$iterator = new \RecursiveIteratorIterator($iterator);
$args = new \stdClass;
foreach ($iterator as $form) {
/* @var FormInterface $form */
$propertyPath = $form->getPropertyPath();
$config = $form->getConfig();
$elements = $propertyPath->getElements();
$firstElement = array_shift($elements);
if (!isset($args->$firstElement)) {
$args->$firstElement = null;
}
// Write-back is disabled if the form is not synchronized (transformation failed)
// and if the form is disabled (modification not allowed)
if (null !== $propertyPath && $config->getMapped() && $form->isSynchronized() && !$form->isDisabled()) {
// If the data is identical to the value in $data, we are
// dealing with a reference
if (!is_object($data) || !$config->getByReference()) {
$propertyPath->setValue($args, $form->getData());
}
}
}
$this->invokeCommand($data, get_object_vars($args));
}
protected function invokeCommand($data, array $args)
{
$reflObject = new \ReflectionObject($data);
$reflMethod = $reflObject->getMethod($this->methodName);
$sortedArgs = array();
foreach ($reflMethod->getParameters() as $parameter) {
$name = $parameter->getName();
if (!isset($args[$name])) {
throw new ErrorMappingException(
"No field " . $name . " exists on form for method ".
"'" . $this->methodName . "' argument."
);
}
$sortedArgs[] = $args[$name];
unset($args[$name]);
}
if ($args) {
throw new ErrorMappingException(
"Form still has arguments (" . implode(", ", array_keys($args)) . ") ".
"that cannot be mapped onto arguments of method " . $this->methodName
);
}
$reflMethod->invokeArgs($data, $sortedArgs);
}
}
<?php
namespace Symfony\Component\Form\Tests\Extension\Core\DataMapper;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormConfigBuilder;
use Symfony\Component\Form\FormConfigInterface;
use Symfony\Component\Form\Util\PropertyPath;
use Symfony\Component\Form\Extension\Core\DataMapper\CommandMethodMapper;
class CommandMethodMapperTest extends \PHPUnit_Framework_TestCase
{
/**
* @var PropertyPathMapper
*/
private $mapper;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $dispatcher;
protected function setUp()
{
if (!class_exists('Symfony\Component\EventDispatcher\Event')) {
$this->markTestSkipped('The "EventDispatcher" component is not available');
}
$this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
}
/**
* @param $path
* @return \PHPUnit_Framework_MockObject_MockObject
*/
private function getPropertyPath($path)
{
return new \Symfony\Component\Form\Util\PropertyPath($path);
}
/**
* @param FormConfigInterface $config
* @param Boolean $synchronized
* @return \PHPUnit_Framework_MockObject_MockObject
*/
private function getForm(FormConfigInterface $config, $synchronized = true)
{
$form = $this->getMockBuilder('Symfony\Component\Form\Form')
->setConstructorArgs(array($config))
->setMethods(array('isSynchronized'))
->getMock();
$form->expects($this->any())
->method('isSynchronized')
->will($this->returnValue($synchronized));
return $form;
}
/**
* @return \PHPUnit_Framework_MockObject_MockObject
*/
private function getDataMapper()
{
return $this->getMock('Symfony\Component\Form\DataMapperInterface');
}
public function testMapFormsToData()
{
$car = new CommandMapperCar;
$engine = new \stdClass();
$propertyPath = $this->getPropertyPath('engine');
$config = new FormConfigBuilder('name', '\stdClass', $this->dispatcher);
$config->setByReference(false);
$config->setPropertyPath($propertyPath);
$config->setData($engine);
$form = $this->getForm($config);
$this->mapper = new CommandMethodMapper("setEngine");
$this->mapper->mapFormsToData(array($form), $car);
$this->assertEquals($engine, $car->engine);
}
public function testMapFormsToDataWritesBackIfNotByReference()
{
$car = new CommandMapperCar;
$config = new FormConfigBuilder('name', null, $this->dispatcher);
$config->setByReference(false);
$config->setPropertyPath($this->getPropertyPath('foo'));
$config->setData("1");
$fooForm = $this->getForm($config);
$config = new FormConfigBuilder('name', null, $this->dispatcher);
$config->setByReference(false);
$config->setPropertyPath($this->getPropertyPath('bar'));
$config->setData("2");
$barForm = $this->getForm($config);
$config = new FormConfigBuilder('name', null, $this->dispatcher);
$config->setByReference(false);
$config->setPropertyPath($this->getPropertyPath('engine'));
$config->setData("3");
$engineForm = $this->getForm($config);
$this->mapper = new CommandMethodMapper("command");
$this->mapper->mapFormsToData(array($fooForm, $barForm, $engineForm), $car);
$this->assertEquals(1, $car->foo);
$this->assertEquals(2, $car->bar);
$this->assertEquals(3, $car->engine);
}
}
class CommandMapperCar
{
public $foo;
public $bar;
public $engine;
public function setEngine($engine)
{
$this->engine = $engine;
}
public function command($foo, $bar, $engine)
{
$this->foo = $foo;
$this->bar = $bar;
$this->engine = $engine;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment