8

I'm going to write REST API for my project. I'm using symfony 4. I saw several examples, but non of them fit me.

  1. Validation with Form object. It doesn't work for me, because it's API, there are no forms. I don't want to write dummy classes just to support this functionality.
  2. On this page https://symfony.com/doc/current/validation.html they suggest 4 ways: Annotation, yml, xml, php. This solution doesn't fit me because this validation is related to the entity, API - is much mode wider: it has limit, offset, filters and other fields, that doesn't belong to an entity.

So, I think I need to write validator which has an array of constraints for all possible fields. I just don't know what is the best way to present this. Have you ever seen something similar?

P.S. Before writing this post I used stackoverflow search. I didn't find useful answers.

Hevyweb
  • 402
  • 1
  • 5
  • 18
  • Could you provide an example where the symfony validator wouldn't fit? How does your data look? – mholle Feb 17 '19 at 21:27
  • For example i trigger Rest API and want to get list of categories. So I send Get request `example.com/api/categories?limit=20&offset=300&filter=something`. "Limit", "offset" and "filter" doesn't belong to entity "category", but I have to validate them. – Hevyweb Feb 18 '19 at 07:46

2 Answers2

14

Looking at your example (example.com/api/categories?limit=20&offset=300&filter=something) I guess your action would look something like this:

public function getCategories(?int $limit, ?int $offset, ?string $filter)
{
    //...
}

Collection validation

You can define your constraints as an array (and later abstract it away into its own class), and pass it as the second argument to your validator.

$constraint = new Assert\Collection([
    'limit' => [
        new Assert\Range(['min' => 0, 'max' => 999]),
        new Assert\DivisibleBy(0.5)
    ],
    'offset' => new Assert\Range(['min' => 0, 'max' => 999]),
    'filter' => new Assert\Regex("/^\w+/")
]);

$validationResult = $this->validator->validate(
    ['limit' => $limit, 'offset' => $offset, 'filter' => $filter],
    $constraint
);

Documentation link.

Validate one by one

Pass the constraint to the validator as second argument, for every parameter you want to validate.

$offsetValidationResult = $this->validator->validate(
    $offset,
    new Assert\Range(['min' => 0, 'max' => 999])
);
//...

Documentation link.

Object validation

Create a class with the 3 fields in it.

class FilterParameters
{
    public function __construct($limit, $offset, $filter)
    {
        $this->limit = $limit;
        $this->offset = $offset;
        $this->filter = $filter;
    }

    // No getters/setters for brevity
    /**
     * @Assert\DivisibleBy(0.25)
     */
    public $limit;
    /**
     * @Assert\Range(min = 0, max = 999)
     */
    public $offset;
    /**
     * @Assert\Regex("/^\w+/")
     */
    public $filter;
}

Instantiate and validate it.

$validationResult = $this->validator->validate(
    new FilterParameters($limit, $offset, $filter)
);

Documentation link.

mholle
  • 577
  • 1
  • 10
  • 19
-1

I think to use forms as usual is the very clean and nice. https://codereviewvideos.com/course/beginners-guide-back-end-json-api-front-end-2018/video/validating-json-data-symfony

I choose this api, because it was the fastest in my tests. You do not have to buy the course (but you might if you like the code), just follow the "raw symfony 4" articles in this series (you also dont need the behat part)

"Limit", "offset" and "filter" functionality belongs to your repositories. Same way as you pass the id here to the repository

/**
 * Class AlbumController
 * @package App\Controller
 */
class AlbumController extends AbstractController
{
    // ....

    /**
     * @Route(
     *     path         = "/api/album/{id}",
     *     name         = "get_album",
     *     methods      = {"GET"},
     *     requirements = {"id"="\d+"}
     * )
     * @param int $id
     *
     * @return JsonResponse
     */
    public function get($id)
    {
        return new JsonResponse($this->findAlbumById($id), JsonResponse::HTTP_OK);
    }  

    /**
     * @param $id
     *
     * @return Album|null
     * @throws NotFoundHttpException
     */
    private function findAlbumById($id)
    {
        $album = $this->albumRepository->find($id);

        if ($album === null) {
            throw new NotFoundHttpException();
        }

        return $album;
    }
ABSimon
  • 651
  • 1
  • 6
  • 18