Skip to content

Example: params and validation ​

A user list with filters, GET /api/v1/users, and user creation with a JSON body, POST /api/v1/users.

Open the diagram at full size

Query params ​

php
<?php

declare(strict_types=1);

namespace App\Api;

use App\UserRepository;
use Tomaj\NetteApi\Handlers\BaseHandler;
use Tomaj\NetteApi\Output\JsonOutput;
use Tomaj\NetteApi\Params\GetInputParam;
use Tomaj\NetteApi\Response\JsonApiResponse;
use Tomaj\NetteApi\Response\ResponseInterface;
use Tomaj\NetteApi\Validation\InputType;

final class UserListHandler extends BaseHandler
{
    public function __construct(private UserRepository $users)
    {
        parent::__construct();
    }

    public function summary(): string
    {
        return 'List users';
    }

    public function tags(): array
    {
        return ['users'];
    }

    public function params(): array
    {
        return [
            // ?status=active, only the listed values pass
            (new GetInputParam('status'))
                ->setAvailableValues(['active', 'blocked'])
                ->setDescription('Only users with this status'),
            // ?id[]=1&id[]=3, every item must be an integer
            (new GetInputParam('id', InputType::INTEGER))
                ->setMulti()
                ->setDescription('Only users with these ids'),
            // ?limit=2, converted to int before handle() gets it
            (new GetInputParam('limit', InputType::INTEGER))
                ->setDefault(10)
                ->setDescription('Maximum number of users'),
        ];
    }

    public function handle(array $params): ResponseInterface
    {
        // a value that is not numeric (?limit=abc) arrives as null, not as an error
        $limit = $params['limit'] ?? 10;
        $users = $this->users->find($params['status'], $params['id'] ?? [], $limit);
        return new JsonApiResponse(200, ['users' => $users]);
    }

    public function outputs(): array
    {
        return [
            new JsonOutput(200, (string) file_get_contents(__DIR__ . '/schema/user-list.json'), 'The matching users'),
        ];
    }
}
  • status allows only active or inactive; ?status=deleted answers 400.
  • id is a multi value, ?id[]=2&id[]=3.
  • limit is an integer. ?limit=abc is not an error in 3.x: the value becomes null and the handler falls back to 10. See Params.

JSON body ​

php
<?php

declare(strict_types=1);

namespace App\Api;

use App\UserRepository;
use Tomaj\NetteApi\Handlers\BaseHandler;
use Tomaj\NetteApi\Output\JsonOutput;
use Tomaj\NetteApi\Params\JsonInputParam;
use Tomaj\NetteApi\Response\JsonApiResponse;
use Tomaj\NetteApi\Response\ResponseInterface;

final class UserCreateHandler extends BaseHandler
{
    public function __construct(private UserRepository $users)
    {
        parent::__construct();
    }

    public function summary(): string
    {
        return 'Create a user';
    }

    public function tags(): array
    {
        return ['users'];
    }

    public function params(): array
    {
        return [
            // the whole request body, validated against a JSON schema
            (new JsonInputParam('user', (string) file_get_contents(__DIR__ . '/schema/user-create.json')))
                ->setRequired()
                ->addExample('Alice', ['name' => 'Alice', 'email' => 'alice@example.com']),
        ];
    }

    public function handle(array $params): ResponseInterface
    {
        $user = $this->users->add($params['user']['name'], $params['user']['email']);
        return new JsonApiResponse(201, ['user' => $user]);
    }

    public function outputs(): array
    {
        return [
            new JsonOutput(201, (string) file_get_contents(__DIR__ . '/schema/user.json'), 'The new user'),
        ];
    }
}
json
{
    "type": "object",
    "properties": {
        "name": { "type": "string", "minLength": 1 },
        "email": { "type": "string", "format": "email" }
    },
    "required": ["name", "email"],
    "additionalProperties": false
}

A missing email, broken JSON or an empty body answers 400.

The test ​

bash
#!/usr/bin/env bash
# End-to-end check: valid and invalid query parameters and JSON bodies.
set -euo pipefail
cd "$(dirname "$0")"
[[ -f vendor/autoload.php ]] || { echo "fail: vendor/ is missing, run composer install first" >&2; exit 1; }
source ../ci/lib.sh
serve 8082

request "$BASE/api/v1/users?status=active&limit=1"
expect_status 200 "filtered list"
expect_body '{"users":[{"id":1,"name":"Alice","email":"alice@example.com","status":"active"}]}' "filtered list"
ok "GET ?status=active&limit=1 returns one active user"

request "$BASE/api/v1/users?id%5B%5D=2&id%5B%5D=3"
expect_status 200 "multi id"
expect_body '"name":"Bob"' "multi id"
expect_body '"name":"Carol"' "multi id"
ok "GET ?id[]=2&id[]=3 returns both users"

request "$BASE/api/v1/users?status=deleted"
expect_status 400 "value outside setAvailableValues"
expect_body '"message":"wrong input"' "value outside setAvailableValues"
expect_body '"status":["Field contains not available value(s)"]' "value outside setAvailableValues"
ok "GET ?status=deleted answers 400 with the field error"

request "$BASE/api/v1/users?limit=abc"
expect_status 200 "non-numeric integer"
expect_body '"name":"Carol"' "non-numeric integer"
ok "GET ?limit=abc passes as null, the handler falls back to 10"

request "$BASE/api/v1/users?id%5B%5D=x"
expect_status 200 "non-numeric multi item"
ok "GET ?id[]=x passes too, the item becomes null"

request -X POST -H 'Content-Type: application/json' -d '{"name":"Dave","email":"dave@example.com"}' "$BASE/api/v1/users"
expect_status 201 "valid body"
expect_body '"user":{"id":4,"name":"Dave","email":"dave@example.com","status":"active"}' "valid body"
ok "POST a valid body answers 201"

request -X POST -H 'Content-Type: application/json' -d '{"name":"Dave"}' "$BASE/api/v1/users"
expect_status 400 "missing property"
expect_body 'The property email is required' "missing property"
ok "POST without email answers 400 with the schema error"

request -X POST -H 'Content-Type: application/json' -d '{"name":' "$BASE/api/v1/users"
expect_status 400 "broken JSON"
expect_body '"user":["Syntax error"]' "broken JSON"
ok "POST broken JSON answers 400"

request -X POST "$BASE/api/v1/users"
expect_status 400 "empty body"
expect_body '"user":["missing data"]' "empty body"
ok "POST without a body answers 400"

FAQ ​

What does a 400 response contain? ​

{"status":"error","message":"wrong input"}, and the errors per field in detail when details are on. See Params.

Why are the outputs declared too? ​

The list and the created user are checked against user-list.json and user.json in development. See Outputs.

How do I make a param required? ​

Call setRequired(). A missing value answers 400 with Field is required.

Can I validate a POST form instead of JSON? ​

Use PostInputParam for each field. See Params.

Do the schemas show up in OpenAPI? ​

Yes, as the request body and response schemas. See OpenAPI.

Released under the MIT License.