Support Symfony 2.2 PropertyAccess with BC for 2.1 (closes #218)

By abstracting the property access, we can easily support Symfony 2.1's Form component and 2.2's PropertyAccess component.

The PropertyAccessor service will be injected into transformers if it is available. Tests were modified to do the same, and expect the appropriate exception depending on which implementation is available.
This commit is contained in:
Jeremy Mikola 2013-03-27 17:19:50 -04:00
parent 91090ed37a
commit 2e7d2f2452
5 changed files with 143 additions and 56 deletions

View file

@ -57,6 +57,9 @@
<service id="fos_elastica.model_to_elastica_transformer.prototype.auto" class="FOS\ElasticaBundle\Transformer\ModelToElasticaAutoTransformer" public="false" abstract="true"> <service id="fos_elastica.model_to_elastica_transformer.prototype.auto" class="FOS\ElasticaBundle\Transformer\ModelToElasticaAutoTransformer" public="false" abstract="true">
<argument /> <!-- options --> <argument /> <!-- options -->
<call method="setPropertyAccessor">
<argument type="service" id="property_accessor" on-invalid="null" />
</call>
</service> </service>
<service id="fos_elastica.elastica_to_model_transformer.collection.prototype" class="%fos_elastica.elastica_to_model_transformer.collection.class%" public="true" abstract="true"> <service id="fos_elastica.elastica_to_model_transformer.collection.prototype" class="%fos_elastica.elastica_to_model_transformer.collection.class%" public="true" abstract="true">

View file

@ -4,6 +4,7 @@ namespace FOS\ElasticaBundle\Tests\ObjectPersister;
use FOS\ElasticaBundle\Persister\ObjectPersister; use FOS\ElasticaBundle\Persister\ObjectPersister;
use FOS\ElasticaBundle\Transformer\ModelToElasticaAutoTransformer; use FOS\ElasticaBundle\Transformer\ModelToElasticaAutoTransformer;
use Symfony\Component\PropertyAccess\PropertyAccess;
class POPO class POPO
{ {
@ -39,7 +40,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
public function testThatCanReplaceObject() public function testThatCanReplaceObject()
{ {
$modelTransformer = new ModelToElasticaAutoTransformer(); $transformer = $this->getTransformer();
/** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */ /** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */
$typeMock = $this->getMockBuilder('Elastica_Type') $typeMock = $this->getMockBuilder('Elastica_Type')
@ -53,7 +54,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
$fields = array('name' => array()); $fields = array('name' => array());
$objectPersister = new ObjectPersister($typeMock, $modelTransformer, 'SomeClass', $fields); $objectPersister = new ObjectPersister($typeMock, $transformer, 'SomeClass', $fields);
$objectPersister->replaceOne(new POPO()); $objectPersister->replaceOne(new POPO());
} }
@ -62,7 +63,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
*/ */
public function testThatErrorIsHandledWhenCannotReplaceObject() public function testThatErrorIsHandledWhenCannotReplaceObject()
{ {
$modelTransformer = new ModelToElasticaAutoTransformer(); $transformer = $this->getTransformer();
/** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */ /** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */
$typeMock = $this->getMockBuilder('Elastica_Type') $typeMock = $this->getMockBuilder('Elastica_Type')
@ -75,13 +76,13 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
$fields = array('name' => array()); $fields = array('name' => array());
$objectPersister = new InvalidObjectPersister($typeMock, $modelTransformer, 'SomeClass', $fields); $objectPersister = new InvalidObjectPersister($typeMock, $transformer, 'SomeClass', $fields);
$objectPersister->replaceOne(new POPO()); $objectPersister->replaceOne(new POPO());
} }
public function testThatCanInsertObject() public function testThatCanInsertObject()
{ {
$modelTransformer = new ModelToElasticaAutoTransformer(); $transformer = $this->getTransformer();
/** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */ /** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */
$typeMock = $this->getMockBuilder('Elastica_Type') $typeMock = $this->getMockBuilder('Elastica_Type')
@ -94,7 +95,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
$fields = array('name' => array()); $fields = array('name' => array());
$objectPersister = new ObjectPersister($typeMock, $modelTransformer, 'SomeClass', $fields); $objectPersister = new ObjectPersister($typeMock, $transformer, 'SomeClass', $fields);
$objectPersister->insertOne(new POPO()); $objectPersister->insertOne(new POPO());
} }
@ -103,7 +104,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
*/ */
public function testThatErrorIsHandledWhenCannotInsertObject() public function testThatErrorIsHandledWhenCannotInsertObject()
{ {
$modelTransformer = new ModelToElasticaAutoTransformer(); $transformer = $this->getTransformer();
/** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */ /** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */
$typeMock = $this->getMockBuilder('Elastica_Type') $typeMock = $this->getMockBuilder('Elastica_Type')
@ -116,13 +117,13 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
$fields = array('name' => array()); $fields = array('name' => array());
$objectPersister = new InvalidObjectPersister($typeMock, $modelTransformer, 'SomeClass', $fields); $objectPersister = new InvalidObjectPersister($typeMock, $transformer, 'SomeClass', $fields);
$objectPersister->insertOne(new POPO()); $objectPersister->insertOne(new POPO());
} }
public function testThatCanDeleteObject() public function testThatCanDeleteObject()
{ {
$modelTransformer = new ModelToElasticaAutoTransformer(); $transformer = $this->getTransformer();
/** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */ /** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */
$typeMock = $this->getMockBuilder('Elastica_Type') $typeMock = $this->getMockBuilder('Elastica_Type')
@ -135,7 +136,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
$fields = array('name' => array()); $fields = array('name' => array());
$objectPersister = new ObjectPersister($typeMock, $modelTransformer, 'SomeClass', $fields); $objectPersister = new ObjectPersister($typeMock, $transformer, 'SomeClass', $fields);
$objectPersister->deleteOne(new POPO()); $objectPersister->deleteOne(new POPO());
} }
@ -144,7 +145,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
*/ */
public function testThatErrorIsHandledWhenCannotDeleteObject() public function testThatErrorIsHandledWhenCannotDeleteObject()
{ {
$modelTransformer = new ModelToElasticaAutoTransformer(); $transformer = $this->getTransformer();
/** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */ /** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */
$typeMock = $this->getMockBuilder('Elastica_Type') $typeMock = $this->getMockBuilder('Elastica_Type')
@ -157,13 +158,13 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
$fields = array('name' => array()); $fields = array('name' => array());
$objectPersister = new InvalidObjectPersister($typeMock, $modelTransformer, 'SomeClass', $fields); $objectPersister = new InvalidObjectPersister($typeMock, $transformer, 'SomeClass', $fields);
$objectPersister->deleteOne(new POPO()); $objectPersister->deleteOne(new POPO());
} }
public function testThatCanInsertManyObjects() public function testThatCanInsertManyObjects()
{ {
$modelTransformer = new ModelToElasticaAutoTransformer(); $transformer = $this->getTransformer();
/** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */ /** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */
$typeMock = $this->getMockBuilder('Elastica_Type') $typeMock = $this->getMockBuilder('Elastica_Type')
@ -178,7 +179,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
$fields = array('name' => array()); $fields = array('name' => array());
$objectPersister = new ObjectPersister($typeMock, $modelTransformer, 'SomeClass', $fields); $objectPersister = new ObjectPersister($typeMock, $transformer, 'SomeClass', $fields);
$objectPersister->insertMany(array(new POPO(), new POPO())); $objectPersister->insertMany(array(new POPO(), new POPO()));
} }
@ -187,7 +188,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
*/ */
public function testThatErrorIsHandledWhenCannotInsertManyObject() public function testThatErrorIsHandledWhenCannotInsertManyObject()
{ {
$modelTransformer = new ModelToElasticaAutoTransformer(); $transformer = $this->getTransformer();
/** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */ /** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */
$typeMock = $this->getMockBuilder('Elastica_Type') $typeMock = $this->getMockBuilder('Elastica_Type')
@ -202,7 +203,21 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
$fields = array('name' => array()); $fields = array('name' => array());
$objectPersister = new InvalidObjectPersister($typeMock, $modelTransformer, 'SomeClass', $fields); $objectPersister = new InvalidObjectPersister($typeMock, $transformer, 'SomeClass', $fields);
$objectPersister->insertMany(array(new POPO(), new POPO())); $objectPersister->insertMany(array(new POPO(), new POPO()));
} }
/**
* @return ModelToElasticaAutoTransformer
*/
private function getTransformer()
{
$transformer = new ModelToElasticaAutoTransformer();
if (class_exists('Symfony\Component\PropertyAccess\PropertyAccess')) {
$transformer->setPropertyAccessor(PropertyAccess::getPropertyAccessor());
}
return $transformer;
}
} }

View file

@ -3,6 +3,7 @@
namespace FOS\ElasticaBundle\Tests\Transformer\ModelToElasticaAutoTransformer; namespace FOS\ElasticaBundle\Tests\Transformer\ModelToElasticaAutoTransformer;
use FOS\ElasticaBundle\Transformer\ModelToElasticaAutoTransformer; use FOS\ElasticaBundle\Transformer\ModelToElasticaAutoTransformer;
use Symfony\Component\PropertyAccess\PropertyAccess;
class POPO class POPO
{ {
@ -119,7 +120,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
public function testThatCanTransformObject() public function testThatCanTransformObject()
{ {
$transformer = new ModelToElasticaAutoTransformer(); $transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array('name' => array())); $document = $transformer->transform(new POPO(), array('name' => array()));
$data = $document->getData(); $data = $document->getData();
@ -130,7 +131,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
public function testThatCanTransformObjectWithCorrectTypes() public function testThatCanTransformObjectWithCorrectTypes()
{ {
$transformer = new ModelToElasticaAutoTransformer(); $transformer = $this->getTransformer();
$document = $transformer->transform( $document = $transformer->transform(
new POPO(), array( new POPO(), array(
'name' => array(), 'name' => array(),
@ -154,7 +155,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
public function testThatCanTransformObjectWithIteratorValue() public function testThatCanTransformObjectWithIteratorValue()
{ {
$transformer = new ModelToElasticaAutoTransformer(); $transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array('iterator' => array())); $document = $transformer->transform(new POPO(), array('iterator' => array()));
$data = $document->getData(); $data = $document->getData();
@ -163,7 +164,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
public function testThatCanTransformObjectWithArrayValue() public function testThatCanTransformObjectWithArrayValue()
{ {
$transformer = new ModelToElasticaAutoTransformer(); $transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array('array' => array())); $document = $transformer->transform(new POPO(), array('array' => array()));
$data = $document->getData(); $data = $document->getData();
@ -177,7 +178,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
public function testThatCanTransformObjectWithMultiDimensionalArrayValue() public function testThatCanTransformObjectWithMultiDimensionalArrayValue()
{ {
$transformer = new ModelToElasticaAutoTransformer(); $transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array('multiArray' => array())); $document = $transformer->transform(new POPO(), array('multiArray' => array()));
$data = $document->getData(); $data = $document->getData();
@ -193,25 +194,29 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
public function testThatNullValuesAreNotFilteredOut() public function testThatNullValuesAreNotFilteredOut()
{ {
$transformer = new ModelToElasticaAutoTransformer(); $transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array('nullValue' => array())); $document = $transformer->transform(new POPO(), array('nullValue' => array()));
$data = $document->getData(); $data = $document->getData();
$this->assertTrue(array_key_exists('nullValue', $data)); $this->assertTrue(array_key_exists('nullValue', $data));
} }
/**
* @expectedException \Symfony\Component\Form\Exception\PropertyAccessDeniedException
*/
public function testThatCannotTransformObjectWhenGetterDoesNotExistForPrivateMethod() public function testThatCannotTransformObjectWhenGetterDoesNotExistForPrivateMethod()
{ {
$transformer = new ModelToElasticaAutoTransformer(); // Support both Symfony 2.1 (Form component) and 2.2 (PropertyAccess component)
$expectedException = class_exists('Symfony\Component\PropertyAccess\PropertyAccess')
? 'Symfony\Component\PropertyAccess\Exception\PropertyAccessDeniedException'
: 'Symfony\Component\Form\Exception\PropertyAccessDeniedException';
$this->setExpectedException($expectedException);
$transformer = $this->getTransformer();
$transformer->transform(new POPO(), array('desc' => array())); $transformer->transform(new POPO(), array('desc' => array()));
} }
public function testFileAddedForAttachmentMapping() public function testFileAddedForAttachmentMapping()
{ {
$transformer = new ModelToElasticaAutoTransformer(); $transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array('file' => array('type' => 'attachment'))); $document = $transformer->transform(new POPO(), array('file' => array('type' => 'attachment')));
$data = $document->getData(); $data = $document->getData();
@ -220,7 +225,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
public function testFileContentsAddedForAttachmentMapping() public function testFileContentsAddedForAttachmentMapping()
{ {
$transformer = new ModelToElasticaAutoTransformer(); $transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array('fileContents' => array('type' => 'attachment'))); $document = $transformer->transform(new POPO(), array('fileContents' => array('type' => 'attachment')));
$data = $document->getData(); $data = $document->getData();
@ -231,7 +236,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
public function testNestedMapping() public function testNestedMapping()
{ {
$transformer = new ModelToElasticaAutoTransformer(); $transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array( $document = $transformer->transform(new POPO(), array(
'sub' => array( 'sub' => array(
'type' => 'nested', 'type' => 'nested',
@ -250,7 +255,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
public function tesObjectMapping() public function tesObjectMapping()
{ {
$transformer = new ModelToElasticaAutoTransformer(); $transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array( $document = $transformer->transform(new POPO(), array(
'sub' => array( 'sub' => array(
'type' => 'object', 'type' => 'object',
@ -269,7 +274,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
public function testParentMapping() public function testParentMapping()
{ {
$transformer = new ModelToElasticaAutoTransformer(); $transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array( $document = $transformer->transform(new POPO(), array(
'upper' => array( 'upper' => array(
'_parent' => array('type' => 'upper', 'identifier' => 'id'), '_parent' => array('type' => 'upper', 'identifier' => 'id'),
@ -281,7 +286,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
public function testParentMappingWithCustomIdentifier() public function testParentMappingWithCustomIdentifier()
{ {
$transformer = new ModelToElasticaAutoTransformer(); $transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array( $document = $transformer->transform(new POPO(), array(
'upper' => array( 'upper' => array(
'_parent' => array('type' => 'upper', 'identifier' => 'name'), '_parent' => array('type' => 'upper', 'identifier' => 'name'),
@ -290,4 +295,18 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
$this->assertEquals("a random name", $document->getParent()); $this->assertEquals("a random name", $document->getParent());
} }
/**
* @return ModelToElasticaAutoTransformer
*/
private function getTransformer()
{
$transformer = new ModelToElasticaAutoTransformer();
if (class_exists('Symfony\Component\PropertyAccess\PropertyAccess')) {
$transformer->setPropertyAccessor(PropertyAccess::getPropertyAccessor());
}
return $transformer;
}
} }

View file

@ -3,6 +3,7 @@
namespace FOS\ElasticaBundle\Transformer; namespace FOS\ElasticaBundle\Transformer;
use Symfony\Component\Form\Util\PropertyPath; use Symfony\Component\Form\Util\PropertyPath;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
/** /**
* Maps Elastica documents with Doctrine objects * Maps Elastica documents with Doctrine objects
@ -20,6 +21,13 @@ class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterf
'identifier' => 'id' 'identifier' => 'id'
); );
/**
* PropertyAccessor instance (will be used if available)
*
* @var PropertyAccessorInterface
*/
private $propertyAccessor;
/** /**
* Instanciates a new Mapper * Instanciates a new Mapper
* *
@ -30,6 +38,16 @@ class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterf
$this->options = array_merge($this->options, $options); $this->options = array_merge($this->options, $options);
} }
/**
* Set the PropertyAccessor
*
* @param PropertyAccessorInterface $propertyAccessor
*/
public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor = null)
{
$this->propertyAccessor = $propertyAccessor;
}
/** /**
* Transforms an object into an elastica object having the required keys * Transforms an object into an elastica object having the required keys
* *
@ -40,33 +58,64 @@ class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterf
**/ **/
public function transform($object, array $fields) public function transform($object, array $fields)
{ {
$identifierProperty = new PropertyPath($this->options['identifier']); $identifier = $this->getPropertyValue($object, $this->options['identifier']);
$identifier = $identifierProperty->getValue($object); $document = new \Elastica_Document($identifier);
$document = new \Elastica_Document($identifier);
foreach ($fields as $key => $mapping) { foreach ($fields as $key => $mapping) {
$property = new PropertyPath($key); $value = $this->getPropertyValue($object, $key);
if (!empty($mapping['_parent']) && $mapping['_parent'] !== '~') {
$parent = $property->getValue($object); if (isset($mapping['_parent']['identifier'])) {
$identifierProperty = new PropertyPath($mapping['_parent']['identifier']); /* $value is the parent. Read its identifier and set that as the
$document->setParent($identifierProperty->getValue($parent)); * document's parent.
} else if (isset($mapping['type']) && in_array($mapping['type'], array('nested', 'object'))) { */
$submapping = $mapping['properties']; $document->setParent($this->getPropertyValue($value, $mapping['_parent']['identifier']));
$subcollection = $property->getValue($object); continue;
$document->add($key, $this->transformNested($subcollection, $submapping, $document));
} else if (isset($mapping['type']) && $mapping['type'] == 'attachment') {
$attachment = $property->getValue($object);
if ($attachment instanceof \SplFileInfo) {
$document->addFile($key, $attachment->getPathName());
} else {
$document->addFileContent($key, $attachment);
}
} else {
$document->add($key, $this->normalizeValue($property->getValue($object)));
} }
if (isset($mapping['type']) && in_array($mapping['type'], array('nested', 'object'))) {
/* $value is a nested document or object. Transform $value into
* an array of documents, respective the mapped properties.
*/
$document->add($key, $this->transformNested($value, $mapping['properties'], $document));
continue;
}
if (isset($mapping['type']) && $mapping['type'] == 'attachment') {
// $value is an attachment. Add it to the document.
if ($value instanceof \SplFileInfo) {
$document->addFile($key, $value->getPathName());
} else {
$document->addFileContent($key, $value);
}
continue;
}
$document->add($key, $this->normalizeValue($value));
} }
return $document; return $document;
} }
/**
* Get the value of an object property.
*
* This method will use Symfony 2.2's PropertyAccessor if it is available.
*
* @param object $object
* @param string $property
* @return mixed
*/
protected function getPropertyValue($object, $property)
{
if (isset($this->propertyAccessor)) {
return $this->propertyAccessor->getValue($object, $property);
}
$propertyPath = new PropertyPath($property);
return $propertyPath->getValue($object);
}
/** /**
* transform a nested document or an object property into an array of ElasticaDocument * transform a nested document or an object property into an array of ElasticaDocument
* *

View file

@ -12,9 +12,9 @@
], ],
"require": { "require": {
"php": ">=5.3.2", "php": ">=5.3.2",
"symfony/framework-bundle": "2.1.*", "symfony/framework-bundle": ">=2.1.0,<2.3.0-dev",
"symfony/console": "2.1.*", "symfony/console": ">=2.1.0,<2.3.0-dev",
"symfony/form": "2.1.*", "symfony/form": ">=2.1.0,<2.3.0-dev",
"ruflin/elastica": "0.19.8" "ruflin/elastica": "0.19.8"
}, },
"require-dev":{ "require-dev":{
@ -25,6 +25,7 @@
"knplabs/knp-components": "1.2.*" "knplabs/knp-components": "1.2.*"
}, },
"suggest": { "suggest": {
"symfony/property-access": "2.2.*",
"doctrine/orm": ">=2.2,<2.5-dev", "doctrine/orm": ">=2.2,<2.5-dev",
"doctrine/mongodb-odm": "1.0.*@dev", "doctrine/mongodb-odm": "1.0.*@dev",
"propel/propel1": "1.6.*", "propel/propel1": "1.6.*",