Last active
December 15, 2017 04:24
-
-
Save jdelisle/e10dfab05427e553a7d0 to your computer and use it in GitHub Desktop.
Query string parameters validation for Zend's Apigility resource fetchAll method using an event-based listener (inspired by zfcampus/zf-content-validation)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
return array( | |
'service_manager' => array( | |
'factories' => array( | |
'Application\\QueryValidation\\QueryValidationListener' => 'Application\\QueryValidation\\QueryValidationListenerFactory', | |
), | |
), | |
'zf-content-validation' => array( | |
'Application\\V1\\Rest\\User\\Controller' => array( | |
'input_filter' => 'Application\\V1\\Rest\\User\\Validator', | |
'query_filter' => 'Application\\V1\\Rest\\User\\QueryValidator', | |
), | |
), | |
'input_filter_specs' => array( | |
'Application\\V1\\Rest\\User\\Validator' => array( | |
), | |
'Application\\V1\\Rest\\User\\QueryValidator' => array( | |
0 => array( | |
'name' => 'sort_key', | |
'required' => false, | |
'filters' => array(), | |
'validators' => array( | |
0 => array( | |
'name' => 'Zend\\Validator\\InArray', | |
'options' => array( | |
'haystack' => array( | |
0 => 'first_name', | |
1 => 'last_name', | |
2 => 'email', | |
), | |
), | |
), | |
), | |
), | |
1 => array( | |
'name' => 'sort_order', | |
'required' => false, | |
'filters' => array(), | |
'validators' => array( | |
0 => array( | |
'name' => 'Zend\\Validator\\InArray', | |
'options' => array( | |
'haystack' => array( | |
0 => 'asc', | |
1 => 'desc', | |
), | |
), | |
), | |
), | |
), | |
2 => array( | |
'name' => 'active', | |
'fallback_value' => false, | |
'filters' => array( | |
0 => array( | |
'name' => 'Zend\\Filter\\Boolean', | |
'options' => array( | |
'type' => 511, | |
), | |
), | |
), | |
'validators' => array(), | |
), | |
), | |
), | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Application; | |
use Zend\Http\Request; | |
use Zend\Mvc\MvcEvent; | |
use ZF\Apigility\Provider\ApigilityProviderInterface; | |
class Module implements ApigilityProviderInterface | |
{ | |
public function getConfig() | |
{ | |
return include __DIR__ . '/../config/module.config.php'; | |
} | |
public function getAutoloaderConfig() | |
{ | |
return array( | |
'ZF\Apigility\Autoloader' => array( | |
'namespaces' => array( | |
__NAMESPACE__ => __DIR__, | |
), | |
), | |
); | |
} | |
public function onBootstrap(MvcEvent $e) | |
{ | |
$app = $e->getApplication(); | |
$services = $app->getServiceManager(); | |
$services->get('SharedEventManager')->attachAggregate($services->get('Application\QueryValidation\QueryValidationListener')); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Application\QueryValidation; | |
use Zend\EventManager\SharedEventManagerInterface; | |
use Zend\EventManager\SharedListenerAggregateInterface; | |
use Zend\InputFilter\InputFilterInterface; | |
use Zend\Mvc\Router\RouteMatch; | |
use Zend\ServiceManager\ServiceLocatorInterface; | |
use Zend\Stdlib\Parameters; | |
use ZF\ApiProblem\ApiProblem; | |
use ZF\ApiProblem\ApiProblemResponse; | |
use ZF\Rest\ResourceEvent; | |
class QueryValidationListener implements SharedListenerAggregateInterface | |
{ | |
/** | |
* @var array | |
*/ | |
protected $config = array(); | |
/** | |
* @var ServiceLocatorInterface | |
*/ | |
protected $inputFilterManager; | |
/** | |
* Cache of input filter service names/instances | |
* | |
* @var array | |
*/ | |
protected $inputFilters = array(); | |
/** | |
* @var \Zend\Stdlib\CallbackHandler[] | |
*/ | |
protected $sharedListeners = array(); | |
/** | |
* @param array $config | |
* @param null|ServiceLocatorInterface $inputFilterManager | |
*/ | |
public function __construct(array $config = array(), ServiceLocatorInterface $inputFilterManager = null) | |
{ | |
$this->config = $config; | |
$this->inputFilterManager = $inputFilterManager; | |
} | |
/** | |
* Attach one or more listeners | |
* | |
* Implementors may add an optional $priority argument; the SharedEventManager | |
* implementation will pass this to the aggregate. | |
* | |
* @param SharedEventManagerInterface $events | |
*/ | |
public function attachShared(SharedEventManagerInterface $events) | |
{ | |
// trigger before resource listener fetchAll event | |
$this->sharedListeners[] = $events->attach('ZF\Rest\RestController', 'fetchAll', array($this, 'onFetchAll'), 10); | |
} | |
/** | |
* Detach all previously attached listeners | |
* | |
* @param SharedEventManagerInterface $events | |
*/ | |
public function detachShared(SharedEventManagerInterface $events) | |
{ | |
foreach ($this->sharedListeners as $index => $listener) { | |
if ($events->detach('ZF\Rest\RestController', $listener)) { | |
unset($this->sharedListeners[$index]); | |
} | |
} | |
} | |
/** | |
* @param ResourceEvent $e | |
* @return ApiProblemResponse | |
*/ | |
public function onFetchAll(ResourceEvent $e) | |
{ | |
$routeMatches = $e->getRouteMatch(); | |
if (! $routeMatches instanceof RouteMatch) { | |
return; | |
} | |
$controllerService = $routeMatches->getParam('controller', false); | |
if (! $controllerService) { | |
return; | |
} | |
$inputFilterService = $this->getInputFilterService($controllerService); | |
if (! $inputFilterService) { | |
return; | |
} | |
if (! $this->hasInputFilter($inputFilterService)) { | |
return new ApiProblemResponse( | |
new ApiProblem( | |
500, | |
sprintf('Listed input filter "%s" does not exist; cannot validate request', $inputFilterService) | |
) | |
); | |
} | |
$inputFilter = $this->getInputFilter($inputFilterService); | |
$inputFilter->setData($e->getQueryParams()); | |
if ($inputFilter->isValid()) { | |
$e->getQueryParams()->fromArray($inputFilter->getValues()); | |
return; | |
} | |
return new ApiProblemResponse( | |
new ApiProblem(400, 'Failed Validation', null, null, array( | |
'validation_messages' => $inputFilter->getMessages(), | |
)) | |
); | |
} | |
/** | |
* Retrieve the query filter service name | |
* | |
* If not present, return boolean false. | |
* | |
* @param string $controllerService | |
* @return string|false | |
*/ | |
protected function getInputFilterService($controllerService) | |
{ | |
if (isset($this->config[$controllerService]['query_filter'])) { | |
return $this->config[$controllerService]['query_filter']; | |
} | |
return false; | |
} | |
/** | |
* Determine if we have an input filter matching the service name | |
* | |
* @param string $inputFilterService | |
* @return bool | |
*/ | |
protected function hasInputFilter($inputFilterService) | |
{ | |
if (array_key_exists($inputFilterService, $this->inputFilters)) { | |
return true; | |
} | |
if (! $this->inputFilterManager | |
|| ! $this->inputFilterManager->has($inputFilterService) | |
) { | |
return false; | |
} | |
$inputFilter = $this->inputFilterManager->get($inputFilterService); | |
if (! $inputFilter instanceof InputFilterInterface) { | |
return false; | |
} | |
$this->inputFilters[$inputFilterService] = $inputFilter; | |
return true; | |
} | |
/** | |
* Retrieve the named input filter service | |
* | |
* @param string $inputFilterService | |
* @return InputFilterInterface | |
*/ | |
protected function getInputFilter($inputFilterService) | |
{ | |
return $this->inputFilters[$inputFilterService]; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Application\QueryValidation; | |
use Zend\ServiceManager\FactoryInterface; | |
use Zend\ServiceManager\ServiceLocatorInterface; | |
class QueryValidationListenerFactory implements FactoryInterface { | |
/** | |
* @param ServiceLocatorInterface $services | |
* @return QueryValidationListener | |
*/ | |
public function createService(ServiceLocatorInterface $services) | |
{ | |
$config = array(); | |
if ($services->has('Config')) { | |
$allConfig = $services->get('Config'); | |
if (isset($allConfig['zf-content-validation'])) { | |
$config = $allConfig['zf-content-validation']; | |
} | |
} | |
return new QueryValidationListener($config, $services->get('InputFilterManager')); | |
} | |
} |
I did a corrected version https://gist.github.com/RubtsovAV/fcc2701ee4c7e8dbe125dbea880012b2
Is there any listener for Rpc query parameter validation?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It does not work on the latest version apigility (ZF2).