diff --git a/src/Hydrator/Aggregate/AggregateHydrator.php b/src/Hydrator/Aggregate/AggregateHydrator.php new file mode 100644 index 000000000..bdcf1eb0a --- /dev/null +++ b/src/Hydrator/Aggregate/AggregateHydrator.php @@ -0,0 +1,87 @@ +getEventManager()->attachAggregate(new HydratorListener($hydrator), $priority); + } + + /** + * {@inheritDoc} + */ + public function extract($object) + { + $event = new ExtractEvent($this, $object); + + $this->getEventManager()->trigger($event); + + return $event->getExtractedData(); + } + + /** + * {@inheritDoc} + */ + public function hydrate(array $data, $object) + { + $event = new HydrateEvent($this, $object, $data); + + $this->getEventManager()->trigger($event); + + return $event->getHydratedObject(); + } + + /** + * {@inheritDoc} + */ + public function setEventManager(EventManagerInterface $eventManager) + { + $eventManager->setIdentifiers(array(__CLASS__, get_class($this))); + + $this->eventManager = $eventManager; + } + + /** + * {@inheritDoc} + */ + public function getEventManager() + { + if (null === $this->eventManager) { + $this->setEventManager(new EventManager()); + } + + return $this->eventManager; + } +} diff --git a/src/Hydrator/Aggregate/ExtractEvent.php b/src/Hydrator/Aggregate/ExtractEvent.php new file mode 100644 index 000000000..dfdfbcfb6 --- /dev/null +++ b/src/Hydrator/Aggregate/ExtractEvent.php @@ -0,0 +1,99 @@ +target = $target; + $this->extractionObject = $extractionObject; + } + + /** + * Retrieves the object from which data is extracted + * + * @return object + */ + public function getExtractionObject() + { + return $this->extractionObject; + } + + /** + * @param object $extractionObject + * + * @return void + */ + public function setExtractionObject($extractionObject) + { + $this->extractionObject = $extractionObject; + } + + /** + * Retrieves the data that has been extracted + * + * @return array + */ + public function getExtractedData() + { + return $this->extractedData; + } + + /** + * @param array $extractedData + * + * @return void + */ + public function setExtractedData(array $extractedData) + { + $this->extractedData = $extractedData; + } + + /** + * Merge provided data with the extracted data + * + * @param array $additionalData + * + * @return void + */ + public function mergeExtractedData(array $additionalData) + { + $this->extractedData = array_merge($this->extractedData, $additionalData); + } +} diff --git a/src/Hydrator/Aggregate/HydrateEvent.php b/src/Hydrator/Aggregate/HydrateEvent.php new file mode 100644 index 000000000..11bab0237 --- /dev/null +++ b/src/Hydrator/Aggregate/HydrateEvent.php @@ -0,0 +1,85 @@ +target = $target; + $this->hydratedObject = $hydratedObject; + $this->hydrationData = $hydrationData; + } + + /** + * Retrieves the object that is being hydrated + * + * @return object + */ + public function getHydratedObject() + { + return $this->hydratedObject; + } + + /** + * @param object $hydratedObject + */ + public function setHydratedObject($hydratedObject) + { + $this->hydratedObject = $hydratedObject; + } + + /** + * Retrieves the data that is being used for hydration + * + * @return array + */ + public function getHydrationData() + { + return $this->hydrationData; + } + + /** + * @param array $hydrationData + */ + public function setHydrationData(array $hydrationData) + { + $this->hydrationData = $hydrationData; + } +} diff --git a/src/Hydrator/Aggregate/HydratorListener.php b/src/Hydrator/Aggregate/HydratorListener.php new file mode 100644 index 000000000..68cc67274 --- /dev/null +++ b/src/Hydrator/Aggregate/HydratorListener.php @@ -0,0 +1,81 @@ +hydrator = $hydrator; + } + + /** + * {@inheritDoc} + */ + public function attach(EventManagerInterface $events) + { + $this->listeners[] = $events->attach(HydrateEvent::EVENT_HYDRATE, array($this, 'onHydrate')); + $this->listeners[] = $events->attach(ExtractEvent::EVENT_EXTRACT, array($this, 'onExtract')); + } + + /** + * Callback to be used when {@see \Zend\Stdlib\Hydrator\Aggregate\HydrateEvent::EVENT_HYDRATE} is triggered + * + * @param \Zend\Stdlib\Hydrator\Aggregate\HydrateEvent $event + * + * @return object + * + * @internal + */ + public function onHydrate(HydrateEvent $event) + { + $object = $this->hydrator->hydrate($event->getHydrationData(), $event->getHydratedObject()); + + $event->setHydratedObject($object); + + return $object; + } + + /** + * Callback to be used when {@see \Zend\Stdlib\Hydrator\Aggregate\ExtractEvent::EVENT_EXTRACT} is triggered + * + * @param \Zend\Stdlib\Hydrator\Aggregate\ExtractEvent $event + * + * @return array + * + * @internal + */ + public function onExtract(ExtractEvent $event) + { + $data = $this->hydrator->extract($event->getExtractionObject()); + + $event->mergeExtractedData($data); + + return $data; + } +} diff --git a/test/Hydrator/Aggregate/AggregateHydratorFunctionalTest.php b/test/Hydrator/Aggregate/AggregateHydratorFunctionalTest.php new file mode 100644 index 000000000..58c2c2d7a --- /dev/null +++ b/test/Hydrator/Aggregate/AggregateHydratorFunctionalTest.php @@ -0,0 +1,178 @@ +hydrator = new AggregateHydrator(); + } + + /** + * Verifies that no interaction happens when the aggregate hydrator is empty + */ + public function testEmptyAggregate() + { + $object = new ArrayObject(array('zaphod' => 'beeblebrox')); + + $this->assertSame(array(), $this->hydrator->extract($object)); + $this->assertSame($object, $this->hydrator->hydrate(array('arthur' => 'dent'), $object)); + + $this->assertSame(array('zaphod' => 'beeblebrox'), $object->getArrayCopy()); + } + + /** + * @dataProvider getHydratorSet + * + * Verifies that using a single hydrator will have the aggregate hydrator behave like that single hydrator + */ + public function testSingleHydratorExtraction(HydratorInterface $comparisonHydrator, $object) + { + $blueprint = clone $object; + + $this->hydrator->add($comparisonHydrator); + + $this->assertSame($comparisonHydrator->extract($blueprint), $this->hydrator->extract($object)); + } + + /** + * @dataProvider getHydratorSet + * + * Verifies that using a single hydrator will have the aggregate hydrator behave like that single hydrator + */ + public function testSingleHydratorHydration(HydratorInterface $comparisonHydrator, $object, $data) + { + $blueprint = clone $object; + + $this->hydrator->add($comparisonHydrator); + + $hydratedBlueprint = $comparisonHydrator->hydrate($data, $blueprint); + $hydrated = $this->hydrator->hydrate($data, $object); + + $this->assertEquals($hydratedBlueprint, $hydrated); + + if ($hydratedBlueprint === $blueprint) { + $this->assertSame($hydrated, $object); + } + } + + /** + * Verifies that multiple hydrators in an aggregate merge the extracted data + */ + public function testExtractWithMultipleHydrators() + { + $this->hydrator->add(new ClassMethods()); + $this->hydrator->add(new ArraySerializable()); + + $object = new AggregateObject(); + + $extracted = $this->hydrator->extract($object); + + $this->assertArrayHasKey('maintainer', $extracted); + $this->assertArrayHasKey('president', $extracted); + $this->assertSame('Marvin', $extracted['maintainer']); + $this->assertSame('Zaphod', $extracted['president']); + } + + /** + * Verifies that multiple hydrators in an aggregate merge the extracted data + */ + public function testHydrateWithMultipleHydrators() + { + $this->hydrator->add(new ClassMethods()); + $this->hydrator->add(new ArraySerializable()); + + $object = new AggregateObject(); + + $this->assertSame( + $object, + $this->hydrator->hydrate(array('maintainer' => 'Trillian', 'president' => '???'), $object) + ); + + $this->assertArrayHasKey('maintainer', $object->arrayData); + $this->assertArrayHasKey('president', $object->arrayData); + $this->assertSame('Trillian', $object->arrayData['maintainer']); + $this->assertSame('???', $object->arrayData['president']); + $this->assertSame('Trillian', $object->maintainer); + } + + /** + * Verifies that stopping propagation within a listener in the hydrator allows modifying how the + * hydrator behaves + */ + public function testStoppedPropagationInExtraction() + { + $object = new ArrayObject(array('president' => 'Zaphod')); + $callback = function (ExtractEvent $event) { + $event->setExtractedData(array('Ravenous Bugblatter Beast of Traal')); + $event->stopPropagation(); + }; + + $this->hydrator->add(new ArraySerializable()); + $this->hydrator->getEventManager()->attach(ExtractEvent::EVENT_EXTRACT, $callback, 1000); + + $this->assertSame(array('Ravenous Bugblatter Beast of Traal'), $this->hydrator->extract($object)); + } + + /** + * Verifies that stopping propagation within a listener in the hydrator allows modifying how the + * hydrator behaves + */ + public function testStoppedPropagationInHydration() + { + $object = new ArrayObject(); + $swappedObject = new stdClass(); + $callback = function (HydrateEvent $event) use ($swappedObject) { + $event->setHydratedObject($swappedObject); + $event->stopPropagation(); + }; + + $this->hydrator->add(new ArraySerializable()); + $this->hydrator->getEventManager()->attach(HydrateEvent::EVENT_HYDRATE, $callback, 1000); + + $this->assertSame($swappedObject, $this->hydrator->hydrate(array('president' => 'Zaphod'), $object)); + } + + /** + * Data provider method + * + * @return array + */ + public function getHydratorSet() + { + return array( + array(new ArraySerializable(), new ArrayObject(array('zaphod' => 'beeblebrox')), array('arthur' => 'dent')), + ); + } +} diff --git a/test/Hydrator/Aggregate/AggregateHydratorTest.php b/test/Hydrator/Aggregate/AggregateHydratorTest.php new file mode 100644 index 000000000..1d1dbc62f --- /dev/null +++ b/test/Hydrator/Aggregate/AggregateHydratorTest.php @@ -0,0 +1,115 @@ +eventManager = $this->getMock('Zend\EventManager\EventManagerInterface'); + $this->hydrator = new AggregateHydrator(); + + $this->hydrator->setEventManager($this->eventManager); + } + + /** + * @covers \Zend\Stdlib\Hydrator\Aggregate\AggregateHydrator::add + */ + public function testAdd() + { + $attached = $this->getMock('Zend\Stdlib\Hydrator\HydratorInterface'); + + $this + ->eventManager + ->expects($this->once()) + ->method('attachAggregate') + ->with($this->isInstanceOf('Zend\Stdlib\Hydrator\Aggregate\HydratorListener'), 123); + + $this->hydrator->add($attached, 123); + } + + /** + * @covers \Zend\Stdlib\Hydrator\Aggregate\AggregateHydrator::hydrate + */ + public function testHydrate() + { + $object = new stdClass(); + + $this + ->eventManager + ->expects($this->once()) + ->method('trigger') + ->with($this->isInstanceOf('Zend\Stdlib\Hydrator\Aggregate\HydrateEvent')); + + $this->assertSame($object, $this->hydrator->hydrate(array('foo' => 'bar'), $object)); + } + + /** + * @covers \Zend\Stdlib\Hydrator\Aggregate\AggregateHydrator::extract + */ + public function testExtract() + { + $object = new stdClass(); + + $this + ->eventManager + ->expects($this->once()) + ->method('trigger') + ->with($this->isInstanceOf('Zend\Stdlib\Hydrator\Aggregate\ExtractEvent')); + + $this->assertSame(array(), $this->hydrator->extract($object)); + } + + /** + * @covers \Zend\Stdlib\Hydrator\Aggregate\AggregateHydrator::getEventManager + * @covers \Zend\Stdlib\Hydrator\Aggregate\AggregateHydrator::setEventManager + */ + public function testGetSetManager() + { + $hydrator = new AggregateHydrator(); + $eventManager = $this->getMock('Zend\EventManager\EventManagerInterface'); + + $this->assertInstanceOf('Zend\EventManager\EventManagerInterface', $hydrator->getEventManager()); + + $eventManager + ->expects($this->once()) + ->method('setIdentifiers') + ->with( + array( + 'Zend\Stdlib\Hydrator\Aggregate\AggregateHydrator', + 'Zend\Stdlib\Hydrator\Aggregate\AggregateHydrator', + ) + ); + + $hydrator->setEventManager($eventManager); + + $this->assertSame($eventManager, $hydrator->getEventManager()); + } +} diff --git a/test/Hydrator/Aggregate/ExtractEventTest.php b/test/Hydrator/Aggregate/ExtractEventTest.php new file mode 100644 index 000000000..4063a6d5e --- /dev/null +++ b/test/Hydrator/Aggregate/ExtractEventTest.php @@ -0,0 +1,56 @@ + 'Marvin'); + $object2 = new stdClass(); + + $this->assertSame(ExtractEvent::EVENT_EXTRACT, $event->getName()); + $this->assertSame($target, $event->getTarget()); + $this->assertSame($object1, $event->getExtractionObject()); + $this->assertSame(array(), $event->getExtractedData()); + + $event->setExtractedData($data2); + + $this->assertSame($data2, $event->getExtractedData()); + + + $event->setExtractionObject($object2); + + $this->assertSame($object2, $event->getExtractionObject()); + + $event->mergeExtractedData(array('president' => 'Zaphod')); + + $extracted = $event->getExtractedData(); + + $this->assertCount(2, $extracted); + $this->assertSame('Marvin', $extracted['maintainer']); + $this->assertSame('Zaphod', $extracted['president']); + } +} diff --git a/test/Hydrator/Aggregate/HydrateEventTest.php b/test/Hydrator/Aggregate/HydrateEventTest.php new file mode 100644 index 000000000..a00ec1548 --- /dev/null +++ b/test/Hydrator/Aggregate/HydrateEventTest.php @@ -0,0 +1,48 @@ + 'Zaphod'); + $event = new HydrateEvent($target, $hydrated1, $data1); + $data2 = array('maintainer' => 'Marvin'); + $hydrated2 = new stdClass(); + + $this->assertSame(HydrateEvent::EVENT_HYDRATE, $event->getName()); + $this->assertSame($target, $event->getTarget()); + $this->assertSame($hydrated1, $event->getHydratedObject()); + $this->assertSame($data1, $event->getHydrationData()); + + $event->setHydrationData($data2); + + $this->assertSame($data2, $event->getHydrationData()); + + + $event->setHydratedObject($hydrated2); + + $this->assertSame($hydrated2, $event->getHydratedObject()); + } +} diff --git a/test/Hydrator/Aggregate/HydratorListenerTest.php b/test/Hydrator/Aggregate/HydratorListenerTest.php new file mode 100644 index 000000000..ce93a28ac --- /dev/null +++ b/test/Hydrator/Aggregate/HydratorListenerTest.php @@ -0,0 +1,118 @@ +hydrator = $this->getMock('Zend\Stdlib\Hydrator\HydratorInterface'); + $this->listener = new HydratorListener($this->hydrator); + } + + /** + * @covers \Zend\Stdlib\Hydrator\Aggregate\HydratorListener::attach + */ + public function testAttach() + { + $eventManager = $this->getMock('Zend\EventManager\EventManagerInterface'); + + $eventManager + ->expects($this->exactly(2)) + ->method('attach') + ->with( + $this->logicalOr(HydrateEvent::EVENT_HYDRATE, ExtractEvent::EVENT_EXTRACT), + $this->logicalAnd( + $this->callback('is_callable'), + $this->logicalOr(array($this->listener, 'onHydrate'), array($this->listener, 'onExtract')) + ) + ); + + $this->listener->attach($eventManager); + } + + /** + * @covers \Zend\Stdlib\Hydrator\Aggregate\HydratorListener::onHydrate + */ + public function testOnHydrate() + { + $object = new stdClass(); + $hydrated = new stdClass(); + $data = array('foo' => 'bar'); + $event = $this + ->getMockBuilder('Zend\Stdlib\Hydrator\Aggregate\HydrateEvent') + ->disableOriginalConstructor() + ->getMock(); + + $event->expects($this->any())->method('getHydratedObject')->will($this->returnValue($object)); + $event->expects($this->any())->method('getHydrationData')->will($this->returnValue($data)); + + $this + ->hydrator + ->expects($this->once()) + ->method('hydrate') + ->with($data, $object) + ->will($this->returnValue($hydrated)); + $event->expects($this->once())->method('setHydratedObject')->with($hydrated); + + $this->assertSame($hydrated, $this->listener->onHydrate($event)); + } + + /** + * @covers \Zend\Stdlib\Hydrator\Aggregate\HydratorListener::onExtract + */ + public function testOnExtract() + { + $object = new stdClass(); + $data = array('foo' => 'bar'); + $event = $this + ->getMockBuilder('Zend\Stdlib\Hydrator\Aggregate\ExtractEvent') + ->disableOriginalConstructor() + ->getMock(); + + + $event->expects($this->any())->method('getExtractionObject')->will($this->returnValue($object)); + + $this + ->hydrator + ->expects($this->once()) + ->method('extract') + ->with($object) + ->will($this->returnValue($data)); + $event->expects($this->once())->method('mergeExtractedData')->with($data); + + $this->assertSame($data, $this->listener->onExtract($event)); + } +} diff --git a/test/TestAsset/AggregateObject.php b/test/TestAsset/AggregateObject.php new file mode 100644 index 000000000..41745a6af --- /dev/null +++ b/test/TestAsset/AggregateObject.php @@ -0,0 +1,60 @@ + 'Zaphod'); + + /** + * @var string + */ + public $maintainer = 'Marvin'; + + /** + * @return string + */ + public function getMaintainer() + { + return $this->maintainer; + } + + /** + * @param string $maintainer + */ + public function setMaintainer($maintainer) + { + $this->maintainer = $maintainer; + } + + /** + * @return array + */ + public function getArrayCopy() + { + return $this->arrayData; + } + + /** + * @param array $data + */ + public function exchangeArray(array $data) + { + $this->arrayData = $data; + } +}