-
-
Save RubtsovAV/fcc2701ee4c7e8dbe125dbea880012b2 to your computer and use it in GitHub Desktop.
It's the actual version of the 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 [ | |
'service_manager' => [ | |
'factories' => [ | |
\ModuleName\QueryValidation\QueryValidationListener::class => \ModuleName\QueryValidation\QueryValidationListenerFactory::class, | |
// ... other factories | |
], | |
], | |
'zf-rest' => [ | |
'ModuleName\\V1\\Rest\\User\\Controller' => [ | |
// Don't forget add the query parameters in whitelist | |
'collection_query_whitelist' => [ | |
0 => 'sort_key', | |
], | |
], | |
], | |
'zf-content-validation' => [ | |
'ModuleName\\V1\\Rest\\User\\Controller' => [ | |
'input_filter' => 'ModuleName\\V1\\Rest\\User\\Validator', | |
'query_filter' => 'ModuleName\\V1\\Rest\\User\\QueryValidator', | |
], | |
], | |
'input_filter_specs' => [ | |
'ModuleName\\V1\\Rest\\User\\Validator' => [ | |
], | |
'ModuleName\\V1\\Rest\\User\\QueryValidator' => [ | |
// Write your query validation rules here. | |
// For example: | |
0 => [ | |
'name' => 'sort_key', | |
'required' => true, | |
'filters' => [], | |
'validators' => [ | |
0 => [ | |
'name' => 'Zend\\Validator\\InArray', | |
'options' => [ | |
'haystack' => [ | |
0 => 'first_name', | |
1 => 'last_name', | |
2 => 'email', | |
], | |
], | |
], | |
], | |
], | |
], | |
], | |
]; |
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 ModuleName; | |
use ModuleName\QueryValidation\QueryValidationListener; | |
use ZF\Apigility\Provider\ApigilityProviderInterface; | |
use Zend\Mvc\MvcEvent; | |
class Module implements ApigilityProviderInterface | |
{ | |
public function getConfig() | |
{ | |
return include __DIR__ . '/config/module.config.php'; | |
} | |
public function getAutoloaderConfig() | |
{ | |
return [ | |
'ZF\Apigility\Autoloader' => [ | |
'namespaces' => [ | |
__NAMESPACE__ => __DIR__ . '/src', | |
], | |
], | |
]; | |
} | |
public function onBootstrap(MvcEvent $e) | |
{ | |
$app = $e->getTarget(); | |
$services = $app->getServiceManager(); | |
$events = $app->getEventManager(); | |
$sharedEvents = $events->getSharedManager(); | |
$services->get(QueryValidationListener::class)->attachShared($sharedEvents); | |
} | |
} |
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 ModuleName\QueryValidation; | |
use Zend\EventManager\EventManagerInterface; | |
use Zend\EventManager\ListenerAggregateInterface; | |
use Zend\EventManager\ListenerAggregateTrait; | |
use Zend\EventManager\SharedEventManagerInterface; | |
use Zend\InputFilter\InputFilterInterface; | |
use Zend\Router\Http\RouteMatch; | |
use Zend\ServiceManager\ServiceLocatorInterface; | |
use ZF\ApiProblem\ApiProblem; | |
use ZF\ApiProblem\ApiProblemResponse; | |
use ZF\Rest\ResourceEvent; | |
use ZF\Rest\Resource; | |
class QueryValidationListener implements ListenerAggregateInterface | |
{ | |
use ListenerAggregateTrait; | |
/** | |
* @var array | |
*/ | |
protected $config = []; | |
/** | |
* @var ServiceLocatorInterface | |
*/ | |
protected $inputFilterManager; | |
/** | |
* Cache of input filter service names/instances | |
* | |
* @var array | |
*/ | |
protected $inputFilters = []; | |
/** | |
* @var \Zend\Stdlib\CallbackHandler[] | |
*/ | |
protected $sharedListeners = []; | |
/** | |
* @param array $config | |
* @param null|ServiceLocatorInterface $inputFilterManager | |
*/ | |
public function __construct(array $config = [], ServiceLocatorInterface $inputFilterManager = null) | |
{ | |
$this->config = $config; | |
$this->inputFilterManager = $inputFilterManager; | |
} | |
/** | |
* @param EventManagerInterface $events | |
*/ | |
public function attach(EventManagerInterface $events, $priority = 1) | |
{ | |
$this->listeners[] = $events->attach('fetchAll', [$this, 'onFetchAll'], 10); | |
} | |
/** | |
* 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(Resource::class, 'fetchAll', [$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(Resource::class, $listener)) { | |
unset($this->sharedListeners[$index]); | |
} | |
} | |
} | |
/** | |
* @param ResourceEvent $e | |
* @return ApiProblemResponse | |
*/ | |
public function onFetchAll($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, [ | |
'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 ModuleName\QueryValidation; | |
class QueryValidationListenerFactory | |
{ | |
/** | |
* @param ServiceLocatorInterface $services | |
* @return QueryValidationListener | |
*/ | |
public function createService($services) | |
{ | |
$config = []; | |
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')); | |
} | |
public function __invoke($services) | |
{ | |
return $this->createService($services); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment