FOSRestBundle icon indicating copy to clipboard operation
FOSRestBundle copied to clipboard

PUT Controller - ConstraintViolationList as parameter detect the constraint uniqueEntity - No Form

Open JuanLuisGarciaBorrego opened this issue 7 years ago • 10 comments

PUT Controller - ConstraintViolationList as parameter detect the constraint uniqueEntity

Hi, I'm trying update an object without form, I have defined a constraint unique for mail field. When I use this in PostController it's working.

#config.yml
fos_rest:
    ...
    body_converter:
        enabled: true
        validate: true
        validation_errors_argument: validation
//AppBundle\Entity\Sample.php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Hateoas\Configuration\Annotation as Hateoas;
use Symfony\Component\Validator\Constraints as Assert;
use JMS\Serializer\Annotation as Serial;

use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
 * Sample
 * ...
 * @UniqueEntity("mail")
 */
class Sample 
{
    ....
    /**
     * @var string
     *
     * @ORM\Column(name="mail", type="string", length=255, unique=true)
     *
     * @Assert\Email(
     *     message = "The email '{{ value }}' is not a valid email.",
     *     checkMX = true
     * )
     */
    private $mail;
    ....
//PUT CONTROLLER 
/**
     * @Rest\Put(path = "/samples/{id}", name = "api_put_samples")
     * @ParamConverter("sample", class="AppBundle\Entity\Sample")
     * @ParamConverter("sampleUpdate", converter="fos_rest.request_body")
     *
     * @Rest\View(StatusCode = Response::HTTP_NO_CONTENT)
     */
    public function putSampleAction(Sample $sample, Sample $sampleUpdate, ConstraintViolationList $validation)
    {

        if($validation->count()) {
            return $this->view($validation, Response::HTTP_BAD_REQUEST);
        }

        $em = $this->getDoctrine()->getManager();
        //setter update object
        $update = $this->setSample($sample, $sampleUpdate)
        $em->persist($update);
        $em->flush();

        return $this->view($sample, Response::HTTP_NO_CONTENT,
            [
                'Location' => $this->generateUrl('api_sample', ['id' => $sample->getId()])
            ]
        );
    }

When i send by PUT a json body object for update with the mail field equal (without update), the ConstraintViolationList $validation detected a constraint.

    public function putSampleAction(Sample $sample, Sample $sampleUpdate, ConstraintViolationList $validation)
    {
        dump($validation); die;
       ....
ConstraintViolationList {#1088
  -violations: array:1 [
    0 => ConstraintViolation {#1061
      -message: "This value is already used."
      -messageTemplate: "This value is already used."
      -parameters: array:1 [
        "{{ value }}" => ""[email protected]""
      ]

NOTE: friendsofsymfony/rest-bundle": "^2.2 symfony/symfony": "3.3.*

Any idea? Thanks

JuanLuisGarciaBorrego avatar Aug 20 '17 11:08 JuanLuisGarciaBorrego

Any alternative or example? thanks!

JuanLuisGarciaBorrego avatar Sep 16 '17 10:09 JuanLuisGarciaBorrego

Not sure I completely understand. You mean that you don't get any violation when issuing POST requests, but it's emitted when using PUT instead?

xabbuh avatar Sep 19 '17 10:09 xabbuh

Hi @xabbuh Yes, when I use POST the Constraint @UniqueEntity("mail") it's works, but when using PUT jumps the constraint because it says that the email already exists (it exists because I am editing all the properties of that object and I have not modified it).

All this without using form.

JuanLuisGarciaBorrego avatar Sep 21 '17 09:09 JuanLuisGarciaBorrego

This looks weird. Can you create a small example project that allows to reproduce your issue?

xabbuh avatar Sep 21 '17 10:09 xabbuh

Ok! I go to create a small example project and I notify you Thanks

JuanLuisGarciaBorrego avatar Sep 21 '17 16:09 JuanLuisGarciaBorrego

Hi, I had the same issue with UniqueEntity constrainst for POST and PUT requests. My hypothesis : For PUT request :

  • the constrainst violations checks your first Entity\Sample -> Ok it is the one from the database,
  • the constrainst violations checks your second Entity\Sample from body -> It is a new one (not saved in DB) and failed because email already exists in DB.

For POST request, there is no probleme because on each checks the mail doesn't exist in database. To bypass that you can use a form or a specific object to catch data from body : Example https://github.com/jfx/ci-report/blob/master/src/AppBundle/Controller/ProjectApiController.php (method : putProjectAction)

jfx avatar Sep 23 '17 06:09 jfx

@xabbuh here has the small example project with error

Project issue

@jfx I go to try your solution and then i notify you

Thanks!

JuanLuisGarciaBorrego avatar Sep 27 '17 07:09 JuanLuisGarciaBorrego

@jfx I'm trying with your solution, but in my $personUpdate ($projectDTO in your project) always there is an error. My code

    /**
     * @Rest\Put(path = "/people/{id}", name = "api_put")
     * @ParamConverter("person", class="AppBundle\Entity\Person")
     * @ParamConverter("personUpdate", converter="fos_rest.request_body")
     *
     * @Rest\View(StatusCode = Response::HTTP_NO_CONTENT)
     */
    public function putPersonAction(Person $person, Person $personUpdate, Request $request)
    {

        $validator = $this->get('validator');
        $validationUpdate = $validator->validate($person);


        if($validationUpdate->count()) {
            return $this->view($validationUpdate, Response::HTTP_BAD_REQUEST);
        }
      ...

your code:

/**
* @ParamConverter("projectDB", options={"mapping": {"prefid": "refid"}})
* @ParamConverter("projectDTO", converter="fos_rest.request_body"
*/
public function putProjectAction(ProjectDTO $projectDTO, Project $projectDB, Request $request)
    {
        if ($this->isInvalidToken($request, $projectDB->getToken())) {
            return $this->getInvalidTokenView();
        }
        $validator = $this->get('validator');
        $violationsDTO = $validator->validate($projectDTO);
        if (count($violationsDTO) > 0) {
            return $this->view($violationsDTO, Response::HTTP_BAD_REQUEST);
        }
        ...

JuanLuisGarciaBorrego avatar Sep 27 '17 07:09 JuanLuisGarciaBorrego

@JuanLuisGarciaBorrego : sure, $projectDTO is an instance of ProjectDTO class that is more or less identical to Project class except there is no constraint on unique field. This constrainst will be check on the second validation on Project class object.

jfx avatar Sep 27 '17 10:09 jfx

Hello, I have the same problem, you have to find a solution? if so, thank you for sharing it. thank you

RaphaelBlehoue avatar Dec 04 '17 13:12 RaphaelBlehoue