Skip to content

Instantly share code, notes, and snippets.

@jdelisle
Last active December 15, 2017 04:24
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jdelisle/e10dfab05427e553a7d0 to your computer and use it in GitHub Desktop.
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)
<?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(),
),
),
),
);
<?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'));
}
}
<?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];
}
}
<?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'));
}
}
@RubtsovAV
Copy link

It does not work on the latest version apigility (ZF2).

@RubtsovAV
Copy link

@namjitharavind
Copy link

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