Validation annotations in doctrine entities

Viewed 1933

Hi I have a symfony project where I have controller called CompanyController. I have there function for saving new Company based on some data which I send by POST method.

Below I show this method:

/**
     * @param Request $request
     * @param ValidatorInterface $validator
     * @return string|Response
     */
    public function store(Request $request, ValidatorInterface $validator) {
                $company = new Company();
                $company->setName($request->get('name'));
                $company->setTaxNumber($request->get('taxNumber'));
                $company->setStreet($request->get('street'));
                $company->setCity($request->get('postalCode'));
                $company->setOfficeId($request->get('officeId'));
                $company->setPostalCode($request->get('postalCode'));
                $errors = $validator->validate($company);
                if(count($errors) > 0) {
                    /**
                     * some code
                     */
                }
        /**
         * some response
         */
    }

In this method I'm trying to validate data from POST response. I wonder if is it good practise to put annotation for validation in entity file Companies like below:

<?php

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(repositoryClass="App\Repository\CompanyRepository")
 */
class Company
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     * @Assert\NotBlank
     */
    private $name;

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\NotBlank
     */
    private $taxNumber;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     * @Assert\NotBlank
     */
    private $street;

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\NotBlank
     */
    private $city;

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\NotBlank
     */
    private $postalCode;

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\NotBlank
     *
     */
    private $officeId;

    /**
     * @var datetime $created
     *
     * @ORM\Column(type="datetime")
     */
    protected $createdAt;

    /**
     * @var datetime $updated
     *
     * @ORM\Column(type="datetime", nullable = true)
     */
    protected $updatedAt;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\Employee", mappedBy="company")
     */
    private $employees;

    public function __construct()
    {
        $this->employees = new ArrayCollection();
    }


    /**
     * Gets triggered only on insert
     * @ORM\PrePersist
     */
    public function onPrePersist()
    {
        $this->createdAt = new \DateTime("now");
    }

    /**
     * Gets triggered every time on update
     * @ORM\PreUpdate
     */
    public function onPreUpdate()
    {
        $this->updatedAt = new \DateTime("now");
    }


    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(?string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getTaxNumber(): ?string
    {
        return $this->taxNumber;
    }

    public function setTaxNumber(string $taxNumber): self
    {
        $this->taxNumber = $taxNumber;

        return $this;
    }

    public function getStreet(): ?string
    {
        return $this->street;
    }

    public function setStreet(?string $street): self
    {
        $this->street = $street;

        return $this;
    }

    public function getCity(): ?string
    {
        return $this->city;
    }

    public function setCity(string $city): self
    {
        $this->city = $city;

        return $this;
    }

    public function getPostalCode(): ?string
    {
        return $this->postalCode;
    }

    public function setPostalCode(string $postalCode): self
    {
        $this->postalCode = $postalCode;

        return $this;
    }

    public function getOfficeId(): ?string
    {
        return $this->officeId;
    }

    public function setOfficeId(string $officeId): self
    {
        $this->officeId = $officeId;

        return $this;
    }

    /**
     * @return Collection|Employee[]
     */
    public function getEmployees(): Collection
    {
        return $this->employees;
    }

    public function addEmployee(Employee $employee): self
    {
        if (!$this->employees->contains($employee)) {
            $this->employees[] = $employee;
            $employee->setCompany($this);
        }

        return $this;
    }

    public function removeEmployee(Employee $employee): self
    {
        if ($this->employees->contains($employee)) {
            $this->employees->removeElement($employee);
            // set the owning side to null (unless already changed)
            if ($employee->getCompany() === $this) {
                $employee->setCompany(null);
            }
        }

        return $this;
    }
}

Is this the correct approach? Second approach I've met i to put annotation in completely different Entity file. For example:

public function productPostAction(AccessChecker $checker, Request $request, JsonFromDbObjectConverter $converter){
        $productRequest = new ProductRequest();
                $productRequest->name = $request->get('name');
                $productRequest->users = $request->get('users');
                $productRequest->llc = (int)$request->get('llc');

                $validator = $this->get('validator');
                $errors = $validator->validate($productRequest);

                if (count($errors) > 0) {
                        /**
                        * some code
                        */

                }
                /**
                * some response
                */
}

Separate Entity class for validation:

<?php

namespace CblBundle\Request;

use Symfony\Component\Validator\Constraints as Assert;

class ProductRequest
{
    /**
     * @var string
     *
     * @Assert\NotBlank()
     */
    public $name;

    /**
     * @var array
     */
    public $users;

    /**
     * @var int
     * @Assert\Type("integer")
     */
    public $llc;

    /**
     * @Assert\IsTrue(message = "Wrong users data!")
     */
    public function isUsersTypeOfNullOrArray()
    {
        return (is_null($this->users) || is_array($this->users));
    }
}
1 Answers

Entities shouldn't know anything about input validation. It is not their job. I personally never do that. There are other options as well - first two depend on FormTypes if they are used.

  1. Defining form validation constraints in formtypes in symfony - This is ok but if you take out your FormType class your validation rules go as well so it is highly coupled approach. Not suggestion this!
  2. Defining form validation annotations in models in symfony - This is ok because there is a validation dedicated separate model class now. Neither Entity nor FormType know about validation. Only the validation model is aware of it so all decoupled from each other. Even if you take out FormType, you can still use the validation model class with Symfony Serializer and Validator. Example below does it.
  3. Validating, serialising and mapping json request to model classes - This is the final and preferred approach. As I mentioned above, whether you are using FormTypes or not, I would go with this option. You can use native Symfony Serializer instead of JMS if you wish. There are more examples on this in the same blog.
Related