By telling Symfony that the getUserAction()
argument is a User
instance, it will take for granted that the {id}
url parameter must be matched to the as primary key, handing it over to the Doctrine ParamConverter to fetch the corresponding User
.
There are at least two workarounds.
1. Use the ParamConverter repository_method
config
In the controller function's comment, we can add the @ParamConverter
annotation and tell it to use the repository_method
option.
This way Symfony will hand the url parameter to a function in our entity repository, from which we'll be able to check the integrity of the url parameter.
In UserRepository
, let's create a function getting an entity by primary key, checking first the integrity of the argument. That is, $id
must not be larger than the largest integer that PHP can handle (the PHP_INT_MAX
constant).
Please note: $id
is a string, so it's safe to compare it to PHP_INT_MAX
, because PHP will automatically typecast PHP_INT_MAX
to a string and compare it to $id
. If it were an integer, the test would always fail (by design, all integers are less than or equal to PHP_INT_MAX
).
// ...
use Symfony\Component\Form\Exception\OutOfBoundsException;
class UserRepository extends ...
{
// ...
public function findSafeById($id) {
if ($id > PHP_INT_MAX) {
throw new OutOfBoundsException($id . " is too large to fit in an integer");
}
return $this->find($id);
}
}
This is only an example: we can do anything we like before throwing the exception (for example logging the failed attempt).
Then, in our controller, let's include the ParamConverter
annotation:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
and modify the function comment adding the annotation:
@ParamConverter("id", class="App:User", options={"repository_method" = "findSafeById"})
Our controller function should look like:
/**
* @Get("users/{id}")
* @ParamConverter("id", class="App:User", options={"repository_method" = "findSafeById"})
*/
public function getUserAction(User $user) {
// Return a "OK" response with the content you like
}
This technique allows customizing the exception, but does not give you control over the response - you'll still get a 500 error in production.
Documentation: see here.
2. Parse the route "the old way"
This way was the only viable one up to Symfony 3, and gives you a more fine-grained control over the generated response.
Let's change the action prototype like this:
/**
* @Route\Get("users/{id}", requirements={"id" = "(\d+)"})
*/
public function getUserAction($id)
{
}
Now, in the action we'll receive the requested $id
and we'll be able to check whether it's ok. If not, we throw an exception and/or return some error response (we can choose the HTTP status code, the format and anything else).
Below you find a sample implementation of this procedure.
use FOS\RestBundle\Controller\Annotations\Get;
use FOS\RestBundle\Controller\FOSRestController;
use Symfony\Component\Form\Exception\OutOfBoundsException;
use Symfony\Component\HttpFoundation\JsonResponse;
class MyRestController extends FOSRestController {
/**
* @Get("users/{id}", requirements={"id" = "(\d+)"})
*/
public function getUserAction($id) {
try {
if ($id > PHP_INT_MAX) {
throw new OutOfBoundsException($id . " is too large to fit in an integer");
}
// Replace App\Entity\User with your actual Entity alias
$user = $this->getDoctrine()->getRepository('App\Entity\User')->find($id);
if (!$user) {
throw new \Doctrine\ORM\NoResultException("User not found");
}
// Return a "OK" response with the content you like
return new JsonResponse(['key' => 123]);
} catch (Exception $e) {
return new JsonResponse(['message' => $e->getMessage()], 400);
}
}