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">
<argument /> <!-- options -->
<call method="setPropertyAccessor">
<argument type="service" id="property_accessor" on-invalid="null" />
</call>
</service>
<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\Transformer\ModelToElasticaAutoTransformer;
use Symfony\Component\PropertyAccess\PropertyAccess;
class POPO
{
@ -39,7 +40,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
public function testThatCanReplaceObject()
{
$modelTransformer = new ModelToElasticaAutoTransformer();
$transformer = $this->getTransformer();
/** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */
$typeMock = $this->getMockBuilder('Elastica_Type')
@ -53,7 +54,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
$fields = array('name' => array());
$objectPersister = new ObjectPersister($typeMock, $modelTransformer, 'SomeClass', $fields);
$objectPersister = new ObjectPersister($typeMock, $transformer, 'SomeClass', $fields);
$objectPersister->replaceOne(new POPO());
}
@ -62,7 +63,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
*/
public function testThatErrorIsHandledWhenCannotReplaceObject()
{
$modelTransformer = new ModelToElasticaAutoTransformer();
$transformer = $this->getTransformer();
/** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */
$typeMock = $this->getMockBuilder('Elastica_Type')
@ -75,13 +76,13 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
$fields = array('name' => array());
$objectPersister = new InvalidObjectPersister($typeMock, $modelTransformer, 'SomeClass', $fields);
$objectPersister = new InvalidObjectPersister($typeMock, $transformer, 'SomeClass', $fields);
$objectPersister->replaceOne(new POPO());
}
public function testThatCanInsertObject()
{
$modelTransformer = new ModelToElasticaAutoTransformer();
$transformer = $this->getTransformer();
/** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */
$typeMock = $this->getMockBuilder('Elastica_Type')
@ -94,7 +95,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
$fields = array('name' => array());
$objectPersister = new ObjectPersister($typeMock, $modelTransformer, 'SomeClass', $fields);
$objectPersister = new ObjectPersister($typeMock, $transformer, 'SomeClass', $fields);
$objectPersister->insertOne(new POPO());
}
@ -103,7 +104,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
*/
public function testThatErrorIsHandledWhenCannotInsertObject()
{
$modelTransformer = new ModelToElasticaAutoTransformer();
$transformer = $this->getTransformer();
/** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */
$typeMock = $this->getMockBuilder('Elastica_Type')
@ -116,13 +117,13 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
$fields = array('name' => array());
$objectPersister = new InvalidObjectPersister($typeMock, $modelTransformer, 'SomeClass', $fields);
$objectPersister = new InvalidObjectPersister($typeMock, $transformer, 'SomeClass', $fields);
$objectPersister->insertOne(new POPO());
}
public function testThatCanDeleteObject()
{
$modelTransformer = new ModelToElasticaAutoTransformer();
$transformer = $this->getTransformer();
/** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */
$typeMock = $this->getMockBuilder('Elastica_Type')
@ -135,7 +136,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
$fields = array('name' => array());
$objectPersister = new ObjectPersister($typeMock, $modelTransformer, 'SomeClass', $fields);
$objectPersister = new ObjectPersister($typeMock, $transformer, 'SomeClass', $fields);
$objectPersister->deleteOne(new POPO());
}
@ -144,7 +145,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
*/
public function testThatErrorIsHandledWhenCannotDeleteObject()
{
$modelTransformer = new ModelToElasticaAutoTransformer();
$transformer = $this->getTransformer();
/** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */
$typeMock = $this->getMockBuilder('Elastica_Type')
@ -157,13 +158,13 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
$fields = array('name' => array());
$objectPersister = new InvalidObjectPersister($typeMock, $modelTransformer, 'SomeClass', $fields);
$objectPersister = new InvalidObjectPersister($typeMock, $transformer, 'SomeClass', $fields);
$objectPersister->deleteOne(new POPO());
}
public function testThatCanInsertManyObjects()
{
$modelTransformer = new ModelToElasticaAutoTransformer();
$transformer = $this->getTransformer();
/** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */
$typeMock = $this->getMockBuilder('Elastica_Type')
@ -178,7 +179,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
$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()));
}
@ -187,7 +188,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
*/
public function testThatErrorIsHandledWhenCannotInsertManyObject()
{
$modelTransformer = new ModelToElasticaAutoTransformer();
$transformer = $this->getTransformer();
/** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\Elastica_Type */
$typeMock = $this->getMockBuilder('Elastica_Type')
@ -202,7 +203,21 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
$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()));
}
/**
* @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;
use FOS\ElasticaBundle\Transformer\ModelToElasticaAutoTransformer;
use Symfony\Component\PropertyAccess\PropertyAccess;
class POPO
{
@ -119,7 +120,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
public function testThatCanTransformObject()
{
$transformer = new ModelToElasticaAutoTransformer();
$transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array('name' => array()));
$data = $document->getData();
@ -130,7 +131,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
public function testThatCanTransformObjectWithCorrectTypes()
{
$transformer = new ModelToElasticaAutoTransformer();
$transformer = $this->getTransformer();
$document = $transformer->transform(
new POPO(), array(
'name' => array(),
@ -154,7 +155,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
public function testThatCanTransformObjectWithIteratorValue()
{
$transformer = new ModelToElasticaAutoTransformer();
$transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array('iterator' => array()));
$data = $document->getData();
@ -163,7 +164,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
public function testThatCanTransformObjectWithArrayValue()
{
$transformer = new ModelToElasticaAutoTransformer();
$transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array('array' => array()));
$data = $document->getData();
@ -177,7 +178,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
public function testThatCanTransformObjectWithMultiDimensionalArrayValue()
{
$transformer = new ModelToElasticaAutoTransformer();
$transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array('multiArray' => array()));
$data = $document->getData();
@ -193,25 +194,29 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
public function testThatNullValuesAreNotFilteredOut()
{
$transformer = new ModelToElasticaAutoTransformer();
$transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array('nullValue' => array()));
$data = $document->getData();
$this->assertTrue(array_key_exists('nullValue', $data));
}
/**
* @expectedException \Symfony\Component\Form\Exception\PropertyAccessDeniedException
*/
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()));
}
public function testFileAddedForAttachmentMapping()
{
$transformer = new ModelToElasticaAutoTransformer();
$transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array('file' => array('type' => 'attachment')));
$data = $document->getData();
@ -220,7 +225,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
public function testFileContentsAddedForAttachmentMapping()
{
$transformer = new ModelToElasticaAutoTransformer();
$transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array('fileContents' => array('type' => 'attachment')));
$data = $document->getData();
@ -231,7 +236,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
public function testNestedMapping()
{
$transformer = new ModelToElasticaAutoTransformer();
$transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array(
'sub' => array(
'type' => 'nested',
@ -250,7 +255,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
public function tesObjectMapping()
{
$transformer = new ModelToElasticaAutoTransformer();
$transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array(
'sub' => array(
'type' => 'object',
@ -269,7 +274,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
public function testParentMapping()
{
$transformer = new ModelToElasticaAutoTransformer();
$transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array(
'upper' => array(
'_parent' => array('type' => 'upper', 'identifier' => 'id'),
@ -281,7 +286,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
public function testParentMappingWithCustomIdentifier()
{
$transformer = new ModelToElasticaAutoTransformer();
$transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array(
'upper' => array(
'_parent' => array('type' => 'upper', 'identifier' => 'name'),
@ -290,4 +295,18 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
$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;
use Symfony\Component\Form\Util\PropertyPath;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
/**
* Maps Elastica documents with Doctrine objects
@ -20,6 +21,13 @@ class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterf
'identifier' => 'id'
);
/**
* PropertyAccessor instance (will be used if available)
*
* @var PropertyAccessorInterface
*/
private $propertyAccessor;
/**
* Instanciates a new Mapper
*
@ -30,6 +38,16 @@ class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterf
$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
*
@ -40,33 +58,64 @@ class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterf
**/
public function transform($object, array $fields)
{
$identifierProperty = new PropertyPath($this->options['identifier']);
$identifier = $identifierProperty->getValue($object);
$document = new \Elastica_Document($identifier);
$identifier = $this->getPropertyValue($object, $this->options['identifier']);
$document = new \Elastica_Document($identifier);
foreach ($fields as $key => $mapping) {
$property = new PropertyPath($key);
if (!empty($mapping['_parent']) && $mapping['_parent'] !== '~') {
$parent = $property->getValue($object);
$identifierProperty = new PropertyPath($mapping['_parent']['identifier']);
$document->setParent($identifierProperty->getValue($parent));
} else if (isset($mapping['type']) && in_array($mapping['type'], array('nested', 'object'))) {
$submapping = $mapping['properties'];
$subcollection = $property->getValue($object);
$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)));
$value = $this->getPropertyValue($object, $key);
if (isset($mapping['_parent']['identifier'])) {
/* $value is the parent. Read its identifier and set that as the
* document's parent.
*/
$document->setParent($this->getPropertyValue($value, $mapping['_parent']['identifier']));
continue;
}
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;
}
/**
* 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
*

View file

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