Created
November 24, 2012 23:51
-
-
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
This file contains 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 | |
/* | |
* This file is part of the Symfony package. | |
* | |
* (c) Fabien Potencier <[email protected]> | |
* | |
* 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); | |
} | |
} |
This file contains 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 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