From 2e7d2f2452306d156ad01f0b024203b8b5985b78 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Wed, 27 Mar 2013 17:19:50 -0400 Subject: [PATCH] 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. --- Resources/config/config.xml | 3 + Tests/Persister/ObjectPersisterTest.php | 47 ++++++---- .../ModelToElasticaAutoTransformerTest.php | 51 +++++++---- .../ModelToElasticaAutoTransformer.php | 91 ++++++++++++++----- composer.json | 7 +- 5 files changed, 143 insertions(+), 56 deletions(-) diff --git a/Resources/config/config.xml b/Resources/config/config.xml index 0a2fd05..a0819fb 100644 --- a/Resources/config/config.xml +++ b/Resources/config/config.xml @@ -57,6 +57,9 @@ + + + diff --git a/Tests/Persister/ObjectPersisterTest.php b/Tests/Persister/ObjectPersisterTest.php index 38f8307..0a46553 100644 --- a/Tests/Persister/ObjectPersisterTest.php +++ b/Tests/Persister/ObjectPersisterTest.php @@ -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; + } } diff --git a/Tests/Transformer/ModelToElasticaAutoTransformerTest.php b/Tests/Transformer/ModelToElasticaAutoTransformerTest.php index cb6c99e..798ea38 100644 --- a/Tests/Transformer/ModelToElasticaAutoTransformerTest.php +++ b/Tests/Transformer/ModelToElasticaAutoTransformerTest.php @@ -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; + } } diff --git a/Transformer/ModelToElasticaAutoTransformer.php b/Transformer/ModelToElasticaAutoTransformer.php index 42b6bb3..4565199 100644 --- a/Transformer/ModelToElasticaAutoTransformer.php +++ b/Transformer/ModelToElasticaAutoTransformer.php @@ -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 * diff --git a/composer.json b/composer.json index 68d6c1b..a4969a0 100644 --- a/composer.json +++ b/composer.json @@ -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.*",