diff --git a/src/ZfrRest/Mvc/Controller/AbstractRestfulController.php b/src/ZfrRest/Mvc/Controller/AbstractRestfulController.php index a2900bd..09cf57e 100755 --- a/src/ZfrRest/Mvc/Controller/AbstractRestfulController.php +++ b/src/ZfrRest/Mvc/Controller/AbstractRestfulController.php @@ -20,8 +20,6 @@ use Doctrine\Common\Collections\Collection; use Zend\Http\Request as HttpRequest; -use Zend\InputFilter\InputFilterInterface; -use Zend\InputFilter\InputFilterPluginManager; use Zend\Mvc\Controller\AbstractController; use Zend\Mvc\MvcEvent; use Zend\Paginator\Paginator; @@ -48,25 +46,6 @@ class AbstractRestfulController extends AbstractController */ protected $methodHandlerManager; - /** - * If this is set to true, then controller will automatically instantiate the input filter specified in - * resource metadata (if there is one) - from service locator first, or directly instantiate it if not found -, - * and validate data. If data is incorrect, it will return a 400 HTTP error (Bad Request) with the failed - * validation messages in it). - * - * @var bool - */ - protected $autoValidate = true; - - /** - * If this is set to true, then controller will automatically instantiate the hydrator specified in resource - * metadata (if there is one) - from service locator first, or directly instantiate it if not found - and - * hydrate resource object with previously validated data - * - * @var bool - */ - protected $autoHydrate = true; - /** * {@inheritDoc} */ @@ -133,36 +112,4 @@ public function getMethodHandlerManager() return $this->methodHandlerManager; } - - /** - * Hook to configure an input filter fetched/created by ZfrRest - * - * @param InputFilterPluginManager $inputFilterPluginManager - * @param string $inputFilterName - * @return InputFilterInterface - */ - public function getInputFilter(InputFilterPluginManager $inputFilterPluginManager, $inputFilterName) - { - return $inputFilterPluginManager->get($inputFilterName); - } - - /** - * Should auto validate? - * - * @return bool - */ - public function getAutoValidate() - { - return $this->autoValidate; - } - - /** - * Should auto hydrate? - * - * @return bool - */ - public function getAutoHydrate() - { - return $this->autoHydrate; - } } diff --git a/src/ZfrRest/Mvc/Controller/Event/HydrationEvent.php b/src/ZfrRest/Mvc/Controller/Event/HydrationEvent.php new file mode 100644 index 0000000..9c35fe3 --- /dev/null +++ b/src/ZfrRest/Mvc/Controller/Event/HydrationEvent.php @@ -0,0 +1,115 @@ + + * @licence MIT + */ +class HydrationEvent extends Event +{ + /** + * Event names + */ + const EVENT_HYDRATE_PRE = 'hydrate.pre'; + const EVENT_HYDRATE_POST = 'hydrate.post'; + + /** + * @var bool + */ + protected $autoHydrate = true; + + /** + * @var ResourceInterface + */ + protected $resource; + + /** + * @var HydratorPluginManager + */ + protected $hydratorManager; + + /** + * @var null|HydratorInterface + */ + protected $hydrator; + + /** + * @param ResourceInterface $resource + * @param HydratorPluginManager $hydratorManager + */ + public function __construct(ResourceInterface $resource, HydratorPluginManager $hydratorManager) + { + $this->resource = $resource; + $this->hydratorManager = $hydratorManager; + } + + /** + * @param bool $autoHydrate + */ + public function setAutoHydrate($autoHydrate) + { + $this->autoHydrate = (bool) $autoHydrate; + } + + /** + * @return bool + */ + public function getAutoHydrate() + { + return $this->autoHydrate; + } + + /** + * @return ResourceInterface + */ + public function getResource() + { + return $this->resource; + } + + /** + * @return HydratorPluginManager + */ + public function getHydratorManager() + { + return $this->hydratorManager; + } + + /** + * @param HydratorInterface $hydrator + */ + public function setHydrator(HydratorInterface $hydrator) + { + $this->hydrator = $hydrator; + } + + /** + * @return null|HydratorInterface + */ + public function getHydrator() + { + return $this->hydrator; + } +} diff --git a/src/ZfrRest/Mvc/Controller/Event/ValidationEvent.php b/src/ZfrRest/Mvc/Controller/Event/ValidationEvent.php new file mode 100644 index 0000000..6d1774f --- /dev/null +++ b/src/ZfrRest/Mvc/Controller/Event/ValidationEvent.php @@ -0,0 +1,116 @@ + + * @licence MIT + */ +class ValidationEvent extends Event +{ + /** + * Event names + */ + const EVENT_VALIDATE_PRE = 'validate.pre'; + const EVENT_VALIDATE_ERROR = 'validate.error'; + const EVENT_VALIDATE_SUCCESS = 'validate.success'; + + /** + * @var bool + */ + protected $autoValidate = true; + + /** + * @var ResourceInterface + */ + protected $resource; + + /** + * @var InputFilterPluginManager + */ + protected $inputFilterManager; + + /** + * @var null|InputFilterInterface + */ + protected $inputFilter; + + /** + * @param ResourceInterface $resource + * @param InputFilterPluginManager $inputFilterManager + */ + public function __construct(ResourceInterface $resource, InputFilterPluginManager $inputFilterManager) + { + $this->resource = $resource; + $this->inputFilterManager = $inputFilterManager; + } + + /** + * @param bool $autoValidate + */ + public function setAutoValidate($autoValidate) + { + $this->autoValidate = (bool) $autoValidate; + } + + /** + * @return bool + */ + public function getAutoValidate() + { + return $this->autoValidate; + } + + /** + * @return ResourceInterface + */ + public function getResource() + { + return $this->resource; + } + + /** + * @return InputFilterPluginManager + */ + public function getInputFilterManager() + { + return $this->inputFilterManager; + } + + /** + * @param InputFilterInterface $inputFilter + */ + public function setInputFilter(InputFilterInterface $inputFilter) + { + $this->inputFilter = $inputFilter; + } + + /** + * @return null|InputFilterInterface + */ + public function getInputFilter() + { + return $this->inputFilter; + } +} diff --git a/src/ZfrRest/Mvc/Controller/MethodHandler/DataHydrationTrait.php b/src/ZfrRest/Mvc/Controller/MethodHandler/DataHydrationTrait.php index a25546c..485f1ae 100644 --- a/src/ZfrRest/Mvc/Controller/MethodHandler/DataHydrationTrait.php +++ b/src/ZfrRest/Mvc/Controller/MethodHandler/DataHydrationTrait.php @@ -18,7 +18,10 @@ namespace ZfrRest\Mvc\Controller\MethodHandler; +use Zend\EventManager\EventManagerAwareInterface; +use Zend\Stdlib\Hydrator\HydratorInterface; use Zend\Stdlib\Hydrator\HydratorPluginManager; +use ZfrRest\Mvc\Controller\Event\HydrationEvent; use ZfrRest\Mvc\Exception\RuntimeException; use ZfrRest\Resource\ResourceInterface; @@ -38,18 +41,40 @@ trait DataHydrationTrait /** * Hydrate the object bound to the data * - * @param ResourceInterface $resource - * @param array $data + * @param ResourceInterface $resource + * @param array $data + * @param EventManagerAwareInterface $controller * @return array|object * @throws RuntimeException */ - public function hydrateData(ResourceInterface $resource, array $data) + public function hydrateData(ResourceInterface $resource, array $data, EventManagerAwareInterface $controller) { - if (!($hydratorName = $resource->getMetadata()->getHydratorName())) { - throw new RuntimeException('No hydrator name has been found in resource metadata'); + /* @var EventManagerInterface $eventManager */ + $eventManager = $controller->getEventManager(); + + $event = new HydrationEvent($resource, $this->hydratorPluginManager); + $event->setTarget($controller); + + $eventManager->trigger(HydrationEvent::EVENT_HYDRATE_PRE, $event); + + if (!$event->getAutoHydrate()) { + return $data; + } + + /* @var HydratorInterface $inputFilter */ + $hydrator = $event->getHydrator(); + + if (!$hydrator instanceof HydratorInterface) { + if (!($hydratorName = $resource->getMetadata()->getHydratorName())) { + throw new RuntimeException('No hydrator name has been found in resource metadata'); + } + + $hydrator = $this->hydratorPluginManager->get($hydratorName); + + $event->setHydrator($hydrator); } - $hydrator = $this->hydratorPluginManager->get($hydratorName); + $eventManager->trigger(HydrationEvent::EVENT_HYDRATE_POST, $event); return $hydrator->hydrate($data, $resource->getData()); } diff --git a/src/ZfrRest/Mvc/Controller/MethodHandler/DataValidationTrait.php b/src/ZfrRest/Mvc/Controller/MethodHandler/DataValidationTrait.php index 44366b5..b770ca4 100644 --- a/src/ZfrRest/Mvc/Controller/MethodHandler/DataValidationTrait.php +++ b/src/ZfrRest/Mvc/Controller/MethodHandler/DataValidationTrait.php @@ -18,10 +18,12 @@ namespace ZfrRest\Mvc\Controller\MethodHandler; +use Zend\EventManager\EventManagerAwareInterface; +use Zend\EventManager\EventManagerInterface; use Zend\InputFilter\InputFilterInterface; use Zend\InputFilter\InputFilterPluginManager; use ZfrRest\Http\Exception\Client\UnprocessableEntityException; -use ZfrRest\Mvc\Controller\AbstractRestfulController; +use ZfrRest\Mvc\Controller\Event\ValidationEvent; use ZfrRest\Mvc\Exception\RuntimeException; use ZfrRest\Resource\ResourceInterface; @@ -41,32 +43,53 @@ trait DataValidationTrait /** * Filter and validate the data * - * @param ResourceInterface $resource - * @param array $data - * @param AbstractRestfulController $controller + * @param ResourceInterface $resource + * @param array $data + * @param EventManagerAwareInterface $controller * @return array * @throws RuntimeException If no input filter is bound to the resource * @throws UnprocessableEntityException If validation fails */ - public function validateData(ResourceInterface $resource, array $data, AbstractRestfulController $controller) + public function validateData(ResourceInterface $resource, array $data, EventManagerAwareInterface $controller) { - if (!($inputFilterName = $resource->getMetadata()->getInputFilterName())) { - throw new RuntimeException('No input filter name has been found in resource metadata'); + /* @var EventManagerInterface $eventManager */ + $eventManager = $controller->getEventManager(); + + $event = new ValidationEvent($resource, $this->inputFilterPluginManager); + $event->setTarget($controller); + + $eventManager->trigger(ValidationEvent::EVENT_VALIDATE_PRE, $event); + + if (!$event->getAutoValidate()) { + return $data; + } + + /* @var InputFilterInterface $inputFilter */ + $inputFilter = $event->getInputFilter(); + + if (!$inputFilter instanceof InputFilterInterface) { + if (!($inputFilterName = $resource->getMetadata()->getInputFilterName())) { + throw new RuntimeException('No input filter name has been found in resource metadata'); + } + + $inputFilter = $this->inputFilterPluginManager->get($inputFilterName); + + $event->setInputFilter($inputFilter); } - /* @var \Zend\InputFilter\InputFilter $inputFilter */ - $inputFilter = $controller->getInputFilter($this->inputFilterPluginManager, $inputFilterName); $inputFilter->setData($data); - $validationContext = $resource->getData(); + if (!$inputFilter->isValid($resource->getData())) { + $eventManager->trigger(ValidationEvent::EVENT_VALIDATE_ERROR, $event); - if (!$inputFilter->isValid($validationContext)) { throw new UnprocessableEntityException( 'Validation error', $this->extractErrorMessages($inputFilter) ); } + $eventManager->trigger(ValidationEvent::EVENT_VALIDATE_SUCCESS, $event); + return $inputFilter->getValues(); } diff --git a/src/ZfrRest/Mvc/Controller/MethodHandler/PostHandler.php b/src/ZfrRest/Mvc/Controller/MethodHandler/PostHandler.php index c926ee6..d560adb 100644 --- a/src/ZfrRest/Mvc/Controller/MethodHandler/PostHandler.php +++ b/src/ZfrRest/Mvc/Controller/MethodHandler/PostHandler.php @@ -80,15 +80,10 @@ public function handleMethod(AbstractRestfulController $controller, ResourceInte } $singleResource = $resource->getMetadata()->createResource(); - $data = json_decode($controller->getRequest()->getContent(), true) ?: []; - if ($controller->getAutoValidate()) { - $data = $this->validateData($singleResource, $data, $controller); - } - - if ($controller->getAutoHydrate()) { - $data = $this->hydrateData($singleResource, $data); - } + $data = json_decode($controller->getRequest()->getContent(), true) ?: []; + $data = $this->validateData($singleResource, $data, $controller); + $data = $this->hydrateData($singleResource, $data, $controller); $result = $controller->post($data); diff --git a/src/ZfrRest/Mvc/Controller/MethodHandler/PutHandler.php b/src/ZfrRest/Mvc/Controller/MethodHandler/PutHandler.php index bfdd19a..63faac2 100644 --- a/src/ZfrRest/Mvc/Controller/MethodHandler/PutHandler.php +++ b/src/ZfrRest/Mvc/Controller/MethodHandler/PutHandler.php @@ -74,14 +74,8 @@ public function handleMethod(AbstractRestfulController $controller, ResourceInte } $data = json_decode($controller->getRequest()->getContent(), true) ?: []; - - if ($controller->getAutoValidate()) { - $data = $this->validateData($resource, $data, $controller); - } - - if ($controller->getAutoHydrate()) { - $data = $this->hydrateData($resource, $data); - } + $data = $this->validateData($resource, $data, $controller); + $data = $this->hydrateData($resource, $data); return $controller->put($data); } diff --git a/tests/ZfrRestTest/Asset/Mvc/DataHydrationObject.php b/tests/ZfrRestTest/Asset/Mvc/DataHydrationObject.php index 28115b5..d9802d6 100644 --- a/tests/ZfrRestTest/Asset/Mvc/DataHydrationObject.php +++ b/tests/ZfrRestTest/Asset/Mvc/DataHydrationObject.php @@ -20,7 +20,6 @@ use Zend\Stdlib\Hydrator\HydratorPluginManager; use ZfrRest\Mvc\Controller\MethodHandler\DataHydrationTrait; -use ZfrRest\Options\ControllerBehavioursOptions; class DataHydrationObject { diff --git a/tests/ZfrRestTest/Mvc/Controller/AbstractRestfulControllerTest.php b/tests/ZfrRestTest/Mvc/Controller/AbstractRestfulControllerTest.php index b3a92dc..c24f6e6 100644 --- a/tests/ZfrRestTest/Mvc/Controller/AbstractRestfulControllerTest.php +++ b/tests/ZfrRestTest/Mvc/Controller/AbstractRestfulControllerTest.php @@ -40,14 +40,6 @@ public function testThrowExceptionIfNotHttpRequest() $controller->dispatch($this->getMock('Zend\Stdlib\RequestInterface')); } - public function testAssertAutoHydrateAndAutoValidateAreTrueByDefault() - { - $controller = new AbstractRestfulController(); - - $this->assertTrue($controller->getAutoValidate()); - $this->assertTrue($controller->getAutoHydrate()); - } - public function testThrowNotFoundExceptionIfNoResourceIsMatched() { $this->setExpectedException('ZfrRest\Http\Exception\Client\NotFoundException'); diff --git a/tests/ZfrRestTest/Mvc/Controller/Event/HydrationEventTest.php b/tests/ZfrRestTest/Mvc/Controller/Event/HydrationEventTest.php new file mode 100644 index 0000000..e6ce84d --- /dev/null +++ b/tests/ZfrRestTest/Mvc/Controller/Event/HydrationEventTest.php @@ -0,0 +1,87 @@ + + * + * @group Coverage + * @covers \ZfrRest\Mvc\Controller\Event\HydrationEvent + */ +class HydrationEventTest extends TestCase +{ + public function testConstructorStoreParameters() + { + $resource = $this->getMock('ZfrRest\Resource\ResourceInterface'); + $hydratorManager = $this->getMock('Zend\Stdlib\Hydrator\HydratorPluginManager'); + $event = new HydrationEvent($resource, $hydratorManager); + + $this->assertAttributeEquals($resource, 'resource', $event); + $this->assertAttributeEquals($hydratorManager, 'hydratorManager', $event); + } + + public function testSetGetAutoHydrate() + { + $resource = $this->getMock('ZfrRest\Resource\ResourceInterface'); + $hydratorManager = $this->getMock('Zend\Stdlib\Hydrator\HydratorPluginManager'); + $event = new HydrationEvent($resource, $hydratorManager); + + $this->assertTrue($event->getAutoHydrate()); + + $event->setAutoHydrate(0); + + $this->assertFalse($event->getAutoHydrate()); + } + + public function testSetGetHydrator() + { + $resource = $this->getMock('ZfrRest\Resource\ResourceInterface'); + $hydratorManager = $this->getMock('Zend\Stdlib\Hydrator\HydratorPluginManager'); + $hydrator = $this->getMock('Zend\Stdlib\Hydrator\HydratorInterface'); + $event = new HydrationEvent($resource, $hydratorManager); + + $this->assertNull($event->getHydrator()); + + $event->setHydrator($hydrator); + + $this->assertSame($hydrator, $event->getHydrator()); + } + + public function testGetResource() + { + $resource = $this->getMock('ZfrRest\Resource\ResourceInterface'); + $hydratorManager = $this->getMock('Zend\Stdlib\Hydrator\HydratorPluginManager'); + $event = new HydrationEvent($resource, $hydratorManager); + + $this->assertSame($resource, $event->getResource()); + } + + public function testSetGetHydratorManager() + { + $resource = $this->getMock('ZfrRest\Resource\ResourceInterface'); + $hydratorManager = $this->getMock('Zend\Stdlib\Hydrator\HydratorPluginManager'); + $event = new HydrationEvent($resource, $hydratorManager); + + $this->assertSame($hydratorManager, $event->gethydratorManager()); + } +} diff --git a/tests/ZfrRestTest/Mvc/Controller/Event/ValidationEventTest.php b/tests/ZfrRestTest/Mvc/Controller/Event/ValidationEventTest.php new file mode 100644 index 0000000..eac09e5 --- /dev/null +++ b/tests/ZfrRestTest/Mvc/Controller/Event/ValidationEventTest.php @@ -0,0 +1,87 @@ + + * + * @group Coverage + * @covers \ZfrRest\Mvc\Controller\Event\ValidationEvent + */ +class ValidationEventTest extends TestCase +{ + public function testConstructorStoreParameters() + { + $resource = $this->getMock('ZfrRest\Resource\ResourceInterface'); + $inputFilterManager = $this->getMock('Zend\InputFilter\InputFilterPluginManager'); + $event = new ValidationEvent($resource, $inputFilterManager); + + $this->assertAttributeEquals($resource, 'resource', $event); + $this->assertAttributeEquals($inputFilterManager, 'inputFilterManager', $event); + } + + public function testSetGetAutoValidate() + { + $resource = $this->getMock('ZfrRest\Resource\ResourceInterface'); + $inputFilterManager = $this->getMock('Zend\InputFilter\InputFilterPluginManager'); + $event = new ValidationEvent($resource, $inputFilterManager); + + $this->assertTrue($event->getAutoValidate()); + + $event->setAutoValidate(0); + + $this->assertFalse($event->getAutoValidate()); + } + + public function testSetGetInputFilter() + { + $resource = $this->getMock('ZfrRest\Resource\ResourceInterface'); + $inputFilterManager = $this->getMock('Zend\InputFilter\InputFilterPluginManager'); + $inputFilter = $this->getMock('Zend\InputFilter\InputFilterInterface'); + $event = new ValidationEvent($resource, $inputFilterManager); + + $this->assertNull($event->getInputFilter()); + + $event->setInputFilter($inputFilter); + + $this->assertSame($inputFilter, $event->getInputFilter()); + } + + public function testGetResource() + { + $resource = $this->getMock('ZfrRest\Resource\ResourceInterface'); + $inputFilterManager = $this->getMock('Zend\InputFilter\InputFilterPluginManager'); + $event = new ValidationEvent($resource, $inputFilterManager); + + $this->assertSame($resource, $event->getResource()); + } + + public function testGetInputFilterManager() + { + $resource = $this->getMock('ZfrRest\Resource\ResourceInterface'); + $inputFilterManager = $this->getMock('Zend\InputFilter\InputFilterPluginManager'); + $event = new ValidationEvent($resource, $inputFilterManager); + + $this->assertSame($inputFilterManager, $event->getInputFilterManager()); + } +} diff --git a/tests/ZfrRestTest/Mvc/Controller/MethodHandler/DataHydrationTraitTest.php b/tests/ZfrRestTest/Mvc/Controller/MethodHandler/DataHydrationTraitTest.php index d5cbe69..82a3eb1 100644 --- a/tests/ZfrRestTest/Mvc/Controller/MethodHandler/DataHydrationTraitTest.php +++ b/tests/ZfrRestTest/Mvc/Controller/MethodHandler/DataHydrationTraitTest.php @@ -19,7 +19,7 @@ namespace ZfrRestTest\Mvc\Controller\MethodHandler; use PHPUnit_Framework_TestCase; -use ZfrRest\Options\ControllerBehavioursOptions; +use ZfrRest\Mvc\Controller\Event\HydrationEvent; use ZfrRestTest\Asset\Mvc\DataHydrationObject; /** @@ -36,55 +36,112 @@ class DataHydrationTraitTest extends PHPUnit_Framework_TestCase */ protected $dataHydration; - /** - * @var \Zend\Stdlib\Hydrator\HydratorPluginManager - */ - protected $hydratorPluginManager; - public function setUp() { - $this->hydratorPluginManager = $this->getMock('Zend\Stdlib\Hydrator\HydratorPluginManager'); - $this->dataHydration = new DataHydrationObject($this->hydratorPluginManager); + $this->resource = $this->getMock('ZfrRest\Resource\ResourceInterface'); + $this->controller = $this->getMock('Zend\EventManager\EventManagerAwareInterface'); + $this->eventManager = $this->getMock('Zend\EventManager\EventManagerInterface'); + $this->hydratorManager = $this->getMock('Zend\Stdlib\Hydrator\HydratorPluginManager'); + $this->dataHydration = new DataHydrationObject($this->hydratorManager); + + $this->controller->expects($this->once())->method('getEventManager')->will($this->returnValue($this->eventManager)); + } + + public function testTriggerEventAndSkipIfAutoHydrationDisabled() + { + $data = ['foo' => 'bar']; + + $callback = function ($event) { + return ($event instanceof HydrationEvent) + && ($event->getTarget() === $this->controller) + && ($event->getResource() === $this->resource) + && ($event->getHydratorManager() === $this->hydratorManager); + }; + + $callback->bindTo($this); + + $this->eventManager->expects($this->once())->method('trigger')->with( + $this->equalTo(HydrationEvent::EVENT_HYDRATE_PRE), + $this->callback($callback) + )->will($this->returnCallback(function ($name, $event) { + // Disable auto hydration + $event->setAutoHydrate(false); + })); + + $result = $this->dataHydration->hydrateData($this->resource, $data, $this->controller); + + $this->assertSame($data, $result); } public function testThrowExceptionIfNoHydratorNameIsDefined() { $this->setExpectedException('ZfrRest\Mvc\Exception\RuntimeException'); - $resource = $this->getMock('ZfrRest\Resource\ResourceInterface'); $metadata = $this->getMock('ZfrRest\Resource\Metadata\ResourceMetadataInterface'); - $resource->expects($this->once())->method('getMetadata')->will($this->returnValue($metadata)); + $this->resource->expects($this->once())->method('getMetadata')->will($this->returnValue($metadata)); $metadata->expects($this->once())->method('getHydratorName')->will($this->returnValue(null)); - $this->dataHydration->hydrateData($resource, []); + $this->dataHydration->hydrateData($this->resource, [], $this->controller); } - public function testCanHydrateData() + public function testHydrateWithCustomHydrator() + { + $data = ['foo' => 'bar']; + $hydrator = $this->getMock('Zend\Stdlib\Hydrator\HydratorInterface'); + $object = new \stdClass(); + + $this->eventManager->expects($this->at(0))->method('trigger')->with( + $this->equalTo(HydrationEvent::EVENT_HYDRATE_PRE) + )->will($this->returnCallback(function ($name, $event) use ($hydrator) { + // Set custom hydrator + $event->setHydrator($hydrator); + })); + + $this->resource->expects($this->once())->method('getData')->will($this->returnValue($object)); + $hydrator->expects($this->once()) + ->method('hydrate') + ->with($data, $object) + ->will($this->returnValue($object)); + + $this->eventManager->expects($this->at(1))->method('trigger')->with(HydrationEvent::EVENT_HYDRATE_POST); + + $result = $this->dataHydration->hydrateData($this->resource, $data, $this->controller); + + $this->assertSame($object, $result); + } + + public function testHydrateWithDefaultHydrator() { - $resource = $this->getMock('ZfrRest\Resource\ResourceInterface'); $metadata = $this->getMock('ZfrRest\Resource\Metadata\ResourceMetadataInterface'); - $resource->expects($this->once())->method('getMetadata')->will($this->returnValue($metadata)); - $metadata->expects($this->once())->method('getHydratorName')->will($this->returnValue('hydrator')); + $this->resource->expects($this->once())->method('getMetadata')->will($this->returnValue($metadata)); + $metadata->expects($this->once())->method('getHydratorName')->will($this->returnValue('FooHydrator')); $data = ['foo']; $resourceData = new \stdClass(); - $resource->expects($this->once())->method('getData')->will($this->returnValue($resourceData)); + $this->resource->expects($this->once())->method('getData')->will($this->returnValue($resourceData)); $hydrator = $this->getMock('Zend\Stdlib\Hydrator\HydratorInterface'); $hydrator->expects($this->once()) ->method('hydrate') ->with($data, $resourceData) - ->will($this->returnValue(['bar'])); + ->will($this->returnValue($resourceData)); - $this->hydratorPluginManager->expects($this->once()) + $this->hydratorManager->expects($this->once()) ->method('get') - ->with('hydrator') + ->with('FooHydrator') ->will($this->returnValue($hydrator)); - $result = $this->dataHydration->hydrateData($resource, $data); + $this->eventManager->expects($this->at(1))->method('trigger')->with( + HydrationEvent::EVENT_HYDRATE_POST, + $this->callback(function ($event) use ($hydrator) { + return ($event instanceof HydrationEvent && $event->getHydrator() === $hydrator); + }) + ); + + $result = $this->dataHydration->hydrateData($this->resource, $data, $this->controller); - $this->assertEquals(['bar'], $result); + $this->assertSame($resourceData, $result); } } diff --git a/tests/ZfrRestTest/Mvc/Controller/MethodHandler/DataValidationTraitTest.php b/tests/ZfrRestTest/Mvc/Controller/MethodHandler/DataValidationTraitTest.php index 1cd1e52..390d971 100644 --- a/tests/ZfrRestTest/Mvc/Controller/MethodHandler/DataValidationTraitTest.php +++ b/tests/ZfrRestTest/Mvc/Controller/MethodHandler/DataValidationTraitTest.php @@ -21,6 +21,7 @@ use PHPUnit_Framework_TestCase; use Zend\InputFilter\InputFilter; use ZfrRest\Http\Exception\Client\UnprocessableEntityException; +use ZfrRest\Mvc\Controller\Event\ValidationEvent; use ZfrRestTest\Asset\Mvc\DataValidationObject; /** @@ -37,76 +38,123 @@ class DataValidationTraitTest extends PHPUnit_Framework_TestCase */ protected $dataValidation; - /** - * @var \Zend\InputFilter\InputFilterPluginManager - */ - protected $inputFilterPluginManager; - public function setUp() { - $this->inputFilterPluginManager = $this->getMock('Zend\InputFilter\InputFilterPluginManager'); - $this->dataValidation = new DataValidationObject($this->inputFilterPluginManager); + $this->resource = $this->getMock('ZfrRest\Resource\ResourceInterface'); + $this->controller = $this->getMock('Zend\EventManager\EventManagerAwareInterface'); + $this->eventManager = $this->getMock('Zend\EventManager\EventManagerInterface'); + $this->inputFilterManager = $this->getMock('Zend\InputFilter\InputFilterPluginManager'); + $this->dataValidation = new DataValidationObject($this->inputFilterManager); + + $this->controller->expects($this->once())->method('getEventManager')->will($this->returnValue($this->eventManager)); } public function testThrowExceptionIfNoInputFilterNameIsDefined() { $this->setExpectedException('ZfrRest\Mvc\Exception\RuntimeException'); - $resource = $this->getMock('ZfrRest\Resource\ResourceInterface'); $metadata = $this->getMock('ZfrRest\Resource\Metadata\ResourceMetadataInterface'); - $resource->expects($this->once())->method('getMetadata')->will($this->returnValue($metadata)); + $this->resource->expects($this->once())->method('getMetadata')->will($this->returnValue($metadata)); $metadata->expects($this->once())->method('getInputFilterName')->will($this->returnValue(null)); - $controller = $this->getMock('ZfrRest\Mvc\Controller\AbstractRestfulController'); - $controller->expects($this->never())->method('configureInputFilter'); + $this->dataValidation->validateData($this->resource, [], $this->controller); + } + + public function testTriggerEventAndSkipIfAutoValidationDisabled() + { + $rawData = ['foo' => 'bar']; + + $callback = function ($event) { + return ($event instanceof ValidationEvent) + && ($event->getTarget() === $this->controller) + && ($event->getResource() === $this->resource) + && ($event->getInputFilterManager() === $this->inputFilterManager); + }; + + $callback->bindTo($this); - $this->dataValidation->validateData($resource, [], $controller); + $this->eventManager->expects($this->once())->method('trigger')->with( + $this->equalTo(ValidationEvent::EVENT_VALIDATE_PRE), + $this->callback($callback) + )->will($this->returnCallback(function ($name, $event) { + // Disable auto validation + $event->setAutoValidate(false); + })); + + $result = $this->dataValidation->validateData($this->resource, $rawData, $this->controller); + + $this->assertSame($rawData, $result); } - public function testCanValidateData() + public function testValidateWithCustomInputFilter() { - $resource = $this->getMock('ZfrRest\Resource\ResourceInterface'); - $metadata = $this->getMock('ZfrRest\Resource\Metadata\ResourceMetadataInterface'); - $context = new \stdClass; + $rawData = ['foo' => 'bar']; + $validData = ['foo' => 'baz']; + $customInputFilter = $this->getMock('Zend\InputFilter\InputFilterInterface'); + + $this->eventManager->expects($this->at(0))->method('trigger')->with($this->equalTo(ValidationEvent::EVENT_VALIDATE_PRE))->will( + $this->returnCallback(function ($name, $event) use ($customInputFilter) { + // Set custom InputFilter + $event->setInputFilter($customInputFilter); + }) + ); + + $customInputFilter->expects($this->once())->method('setData')->with($this->equalTo($rawData)); + $customInputFilter->expects($this->once())->method('isValid')->will($this->returnValue(true)); + $customInputFilter->expects($this->once())->method('getValues')->will($this->returnValue($validData)); - $resource->expects($this->once())->method('getData')->will($this->returnValue($context)); - $resource->expects($this->once())->method('getMetadata')->will($this->returnValue($metadata)); - $metadata->expects($this->once())->method('getInputFilterName')->will($this->returnValue('inputFilter')); + $this->eventManager->expects($this->at(1))->method('trigger')->with($this->equalTo(ValidationEvent::EVENT_VALIDATE_SUCCESS)); - $data = ['foo']; + $result = $this->dataValidation->validateData($this->resource, $rawData, $this->controller); + $this->assertSame($validData, $result); + } + + public function testValidateWithDefaultInputFilter() + { + $metadata = $this->getMock('ZfrRest\Resource\Metadata\ResourceMetadataInterface'); $inputFilter = $this->getMock('Zend\InputFilter\InputFilterInterface'); - $inputFilter->expects($this->once())->method('setData')->with($data); - $inputFilter->expects($this->once()) - ->method('isValid') - ->with($context) - ->will($this->returnValue(true)); + $rawData = ['foo' => 'bar']; + $validData = ['foo' => 'baz']; + $context = new \stdClass(); - $inputFilter->expects($this->once()) - ->method('getValues') - ->will($this->returnValue(['filtered'])); + $this->resource->expects($this->once()) + ->method('getData') + ->will($this->returnValue($context)); - $controller = $this->getMock('ZfrRest\Mvc\Controller\AbstractRestfulController'); - $controller->expects($this->once()) - ->method('getInputFilter') - ->with($this->inputFilterPluginManager, 'inputFilter') - ->will($this->returnValue($inputFilter)); + $this->resource->expects($this->once()) + ->method('getMetadata') + ->will($this->returnValue($metadata)); - $result = $this->dataValidation->validateData($resource, $data, $controller); + $metadata->expects($this->once()) + ->method('getInputFilterName') + ->will($this->returnValue('FooInputFilter')); - $this->assertEquals(['filtered'], $result); + $this->inputFilterManager->expects($this->once()) + ->method('get') + ->with($this->equalTo('FooInputFilter')) + ->will($this->returnValue($inputFilter)); + + $inputFilter->expects($this->once())->method('setData')->with($this->equalTo($rawData)); + $inputFilter->expects($this->once())->method('isValid')->with($this->equalTo($context))->will($this->returnValue(true)); + $inputFilter->expects($this->once())->method('getValues')->will($this->returnValue($validData)); + + $this->eventManager->expects($this->at(1))->method('trigger')->with( + $this->equalTo(ValidationEvent::EVENT_VALIDATE_SUCCESS), + $this->callback(function ($event) use ($inputFilter) { + return ($event instanceof ValidationEvent && $event->getInputFilter() === $inputFilter); + }) + ); + + $result = $this->dataValidation->validateData($this->resource, $rawData, $this->controller); + + $this->assertSame($validData, $result); } public function testThrowExceptionOnFailedValidation() { - $resource = $this->getMock('ZfrRest\Resource\ResourceInterface'); - $metadata = $this->getMock('ZfrRest\Resource\Metadata\ResourceMetadataInterface'); - - $resource->expects($this->once())->method('getMetadata')->will($this->returnValue($metadata)); - $metadata->expects($this->once())->method('getInputFilterName')->will($this->returnValue('inputFilter')); - - $data = ['foo']; + $this->setExpectedException('ZfrRest\Http\Exception\Client\UnprocessableEntityException'); $inputFilter = new InputFilter(); $inputFilter->add([ @@ -114,34 +162,29 @@ public function testThrowExceptionOnFailedValidation() 'required' => true ]); - $controller = $this->getMock('ZfrRest\Mvc\Controller\AbstractRestfulController'); - $controller->expects($this->once()) - ->method('getInputFilter') - ->with($this->inputFilterPluginManager, 'inputFilter') - ->will($this->returnValue($inputFilter)); + $this->eventManager->expects($this->at(0))->method('trigger')->with($this->equalTo(ValidationEvent::EVENT_VALIDATE_PRE))->will( + $this->returnCallback(function ($name, $event) use ($inputFilter) { + // Set InputFilter + $event->setInputFilter($inputFilter); + }) + ); - $exceptionTriggered = false; - $errorMessages = ['email' => ['Value is required and can\'t be empty']]; + $this->eventManager->expects($this->at(1))->method('trigger')->with($this->equalTo(ValidationEvent::EVENT_VALIDATE_ERROR)); + + $errorMessages = ['email' => ['Value is required and can\'t be empty']]; try { - $this->dataValidation->validateData($resource, $data, $controller); - } catch(UnprocessableEntityException $exception) { - $exceptionTriggered = true; + $this->dataValidation->validateData($this->resource, ['foo'], $this->controller); + } catch (UnprocessableEntityException $exception) { $this->assertEquals($errorMessages, $exception->getErrors()); - } - $this->assertTrue($exceptionTriggered); + throw $exception; + } } public function testThrowExceptionOnFailedValidationWithNestedInputFilter() { - $resource = $this->getMock('ZfrRest\Resource\ResourceInterface'); - $metadata = $this->getMock('ZfrRest\Resource\Metadata\ResourceMetadataInterface'); - - $resource->expects($this->once())->method('getMetadata')->will($this->returnValue($metadata)); - $metadata->expects($this->once())->method('getInputFilterName')->will($this->returnValue('inputFilter')); - - $data = ['foo']; + $this->setExpectedException('ZfrRest\Http\Exception\Client\UnprocessableEntityException'); $inputFilter = new InputFilter(); $inputFilter->add([ @@ -156,14 +199,16 @@ public function testThrowExceptionOnFailedValidationWithNestedInputFilter() ] ], 'address'); - $controller = $this->getMock('ZfrRest\Mvc\Controller\AbstractRestfulController'); - $controller->expects($this->once()) - ->method('getInputFilter') - ->with($this->inputFilterPluginManager, 'inputFilter') - ->will($this->returnValue($inputFilter)); + $this->eventManager->expects($this->at(0))->method('trigger')->with($this->equalTo(ValidationEvent::EVENT_VALIDATE_PRE))->will( + $this->returnCallback(function ($name, $event) use ($inputFilter) { + // Set InputFilter + $event->setInputFilter($inputFilter); + }) + ); + + $this->eventManager->expects($this->at(1))->method('trigger')->with($this->equalTo(ValidationEvent::EVENT_VALIDATE_ERROR)); - $exceptionTriggered = false; - $errorMessages = [ + $errorMessages = [ 'email' => ['Value is required and can\'t be empty'], 'address' => [ 'city' => ['Value is required and can\'t be empty'] @@ -171,12 +216,11 @@ public function testThrowExceptionOnFailedValidationWithNestedInputFilter() ]; try { - $this->dataValidation->validateData($resource, $data, $controller); - } catch(UnprocessableEntityException $exception) { - $exceptionTriggered = true; + $this->dataValidation->validateData($this->resource, ['foo'], $this->controller); + } catch (UnprocessableEntityException $exception) { $this->assertEquals($errorMessages, $exception->getErrors()); - } - $this->assertTrue($exceptionTriggered); + throw $exception; + } } }