Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] Added the constraint option on the ParamFetcher #700

Merged
merged 1 commit into from
Mar 10, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Controller/Annotations/Param.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ abstract class Param
public $name;
/** @var string */
public $key = null;
/** @var string */
public $requirements = '';
/** @var mixed */
public $requirements = null;
/** @var mixed */
public $default = null;
/** @var string */
Expand Down
68 changes: 46 additions & 22 deletions Request/ParamFetcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
use Doctrine\Common\Util\ClassUtils;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Validator\Constraints\Regex;
use Symfony\Component\Validator\ValidatorInterface;

/**
* Helper to validate parameters of the active request.
Expand Down Expand Up @@ -48,16 +50,23 @@ class ParamFetcher implements ParamFetcherInterface
*/
private $controller;

/**
* @var ValidatorInterface
*/
private $validator;

/**
* Initializes fetcher.
*
* @param ParamReader $paramReader Query param reader
* @param Request $request Active request
* @param ParamReader $paramReader Query param reader
* @param Request $request Active request
* @param ValidatorInterface $validator The validator service
*/
public function __construct(ParamReader $paramReader, Request $request)
public function __construct(ParamReader $paramReader, Request $request, ValidatorInterface $validator)
{
$this->paramReader = $paramReader;
$this->request = $request;
$this->request = $request;
$this->validator = $validator;
}

/**
Expand Down Expand Up @@ -103,17 +112,9 @@ public function get($name, $strict = null)
}

if ($config->array) {
$failMessage = null;

if (!is_array($param)) {
$failMessage = sprintf("Query parameter value of '%s' is not an array", $name);
} elseif (count($param) !== count($param, COUNT_RECURSIVE)) {
$failMessage = sprintf("Query parameter value of '%s' must not have a depth of more than one", $name);
}

if (null !== $failMessage) {
if ($strict) {
throw new BadRequestHttpException($failMessage);
throw new BadRequestHttpException(sprintf("Query parameter value of '%s' is not an array", $name));
}

return $default;
Expand Down Expand Up @@ -159,16 +160,39 @@ public function cleanParamWithRequirements(Param $config, $param, $strict)
{
$default = $config->default;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should refactor this method and split it up to make things more readable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lsmith77 And what about using the same option for requirements and constraints ? I mean, requirements === Constraints\Regex no ? So we could use always "requirements" and in case requirements is not an instance of Constraint we replace it by a Regex constraint with the requirements argument.

If I do this this part of the method will be way more simple so I'll not have to split it up.

I'm not 100% sure that it is possible to TypeHint an annotation on mixed string|\Classname.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm that might be an idea to simplify the code indeed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, i'll do the job this week-end.

if ('' !== $config->requirements
&& ($param !== $default || null === $default)
&& !preg_match('#^'.$config->requirements.'$#xsu', $param)
) {
if ($strict) {
$paramType = $config instanceof QueryParam ? 'Query' : 'Request';
if (null === $config->requirements || ($param === $default && null !== $default)) {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can remove this empty line?

return $param;
}

$constraint = $config->requirements;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you remove this empty line?

throw new BadRequestHttpException(
$paramType . " parameter value '$param', does not match requirements '{$config->requirements}'"
);
if (is_scalar($constraint)) {
if (is_array($param)) {
if ($strict) {
throw new BadRequestHttpException("Query parameter is an array");
}

return $default;
}

$constraint = new Regex(array(
'pattern' => '#^'.preg_quote($config->requirements).'$#xsu',
'message' => sprintf(
"%s parameter value '%s', does not match requirements '%s'",
$config instanceof QueryParam ? 'Query' : 'Request',
$param,
$config->requirements
)
));
}

$errors = $this->validator->validateValue($param, $constraint);

if (0 !== count($errors)) {

if ($strict) {
throw new BadRequestHttpException((string) $errors);
}

return null === $default ? '' : $default;
Expand Down
1 change: 1 addition & 0 deletions Resources/config/request.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<service id="fos_rest.request.param_fetcher" class="%fos_rest.request.param_fetcher.class%" scope="request">
<argument type="service" id="fos_rest.request.param_fetcher.reader"/>
<argument type="service" id="request"/>
<argument type="service" id="validator"/>
</service>

<service id="fos_rest.request.param_fetcher.reader" class="%fos_rest.request.param_fetcher.reader.class%">
Expand Down
9 changes: 8 additions & 1 deletion Resources/doc/3-listener-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ fos_rest:
use FOS\RestBundle\Request\ParamFetcher;
use FOS\RestBundle\Controller\Annotations\RequestParam;
use FOS\RestBundle\Controller\Annotations\QueryParam;
use Acme\FooBundle\Validation\Constraints\MyComplexConstraint

class FooController extends Controller
{
Expand Down Expand Up @@ -461,7 +462,13 @@ class FooController extends Controller
* If ids is not defined, array(1) will be given
*
* Array must have a single depth or it will return default value. It's difficult to validate with
* preg_match each deeps of array, if you want to deal with that, use another validation system.
* preg_match each deeps of array, if you want to deal with that, you can use a constraint:
*
* @QueryParam(array=true, name="filters", requirements=@MyComplexConstraint, description="List of complex filters")
*
* In this example, the ParamFetcher will validate each value of the array with the constraint, returning the
* default value if you are in safe mode or throw a BadRequestHttpResponse containing the constraint violation
* messages in the message.
*
* @param ParamFetcher $paramFetcher
*/
Expand Down
Loading