Validation

  • You can define resource APIs in the JSON schema.
  • You can separate the validation code with @Valid, @OnValidate annotation.
  • Please see the form for validation by web form.

JSON Schema

The JSON Schema is the standard for describing and validating JSON objects. @JsonSchema and the resource body returned by the method of annotated resource class are validated by JSON schema.

Install

If you want to validate in all contexts including production, create AppModule, if validation is done only during development, create DevModule and install within it

use BEAR\Resource\Module\JsonSchemaModule; // Add this line
use BEAR\Package\AbstractAppModule;

class AppModule extends AbstractAppModule
{
    protected function configure()
    {
        // ...
        $this->install(
            new JsonSchemaModule(
                $appDir . '/var/json_schema',
                $appDir . '/var/json_validate'
            )
        );  // Add this line
    }
}

Create directories for the JSON schema files

mkdir var/json_schema
mkdir var/json_validate

In the var/json_schema/, store the JSON schema file which is the specification of the body of the resource, and the var/json_validate/ stores the JSON schema file for input validation.

@JsonSchema annotation

Annotate the method of the resource class by adding @JsonSchema, then add the schema property by specifying the JSON schema file name, which is user.json for this purpose.

schema

src/Resource/App/User.php


use BEAR\Resource\Annotation\JsonSchema; // Add this line

class User extends ResourceObject
{
    #[JsonSchema('user.json')]
    public function onGet(): static
    {
        $this->body = [
            'firstName' => 'mucha',
            'lastName' => 'alfons',
            'age' => 12
        ];

        return $this;
    }
}

We will create a JSON schema named /var/json_schema/user.json

{
  "type": "object",
  "properties": {
    "firstName": {
      "type": "string",
      "maxLength": 30,
      "pattern": "[a-z\\d~+-]+"
    },
    "lastName": {
      "type": "string",
      "maxLength": 30,
      "pattern": "[a-z\\d~+-]+"
    }
  },
  "required": ["firstName", "lastName"]
}

key

If the body has an index key, specify it with the key property of the annotation


use BEAR\Resource\Annotation\JsonSchema; // Add this line

class User extends ResourceObject
{
    #[JsonSchema(key:'user', schema:'user.json')]
    public function onGet()
    {
        $this->body = [
            'user' => [
                'firstName' => 'mucha',
                'lastName' => 'alfons',
                'age' => 12
            ]
        ];        

        return $this;
    }
}

params

The params property specifies the JSON schema file name for the argument validation


use BEAR\Resource\Annotation\JsonSchema; // Add this line

class Todo extends ResourceObject
{
    #[JsonSchema(key:'user', schema:'user.json', params:'todo.post.json')]
    public function onPost(string $title)

We place the JSON schema file

/var/json_validate/todo.post.json

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "/todo POST request validation",
  "properties": {
    "title": {
      "type": "string",
      "minLength": 1,
      "maxLength": 40
    }
}

By constantly verifying in a standardized way instead of proprietary documentation, the specification is reliable and understandable to both humans and machines.

target

To apply schema validation to the representation of the resource object (the rendered result) rather than to the body of the ResourceObject, specify the option target='view'.

#[JsonSchema(schema: 'user.json', target: 'view')]

@Valid annotation

The @Valid annotation is a validation for input. You can set up validation as AOP for your method. By separating validation logic from the method, the code will be readable and testable.

Validation libraries are available such as Aura.Filter, Respect\Validation, and PHP Standard Filter

Install

Install Ray.ValidateModule via composer.

composer require ray/validate-module

Installing ValidateModule in your application module src/Module/AppModule.php.

use Ray\Validation\ValidateModule;

class AppModule extends AbstractAppModule
{
    protected function configure()
    {
        // ...
        $this->install(new ValidateModule);
    }
}

Annotation

There are three annotations @Valid, @OnValidate, @OnFailure for validation.

First of all, annotate the method that you want to validate with @Valid

use Ray\Validation\Annotation\Valid;

class News
{
    /**
     * @Valid
     */
    public function createUser($name)
    {

Validation will be conducted in the method annotated with @OnValidate.

The arguments of the method should be the same as the original method. The method name can be anything.

use Ray\Validation\Annotation\OnValidate;

class News
{
    /**
     * @OnValidate
     */
    public function onValidate($name)
    {
        $validation = new Validation;
        if (! is_string($name)) {
            $validation->addError('name', 'name should be string');
        }

        return $validation;
    }

Add validations to your elements by addError() with the element name and error message as parameters, then return the validation object.

When validation fails, the exception Ray\Validation\Exception\InvalidArgumentException will be thrown, but if you have a method annotated with the @OnFailure, it will be called, instead of throwing an exception

use Ray\Validation\Annotation\OnFailure;

class News
{
    /**
     * @OnFailure
     */
    public function onFailure(FailureInterface $failure)
    {
        // original parameters
        list($this->defaultName) = $failure->getInvocation()->getArguments();

        // errors
        foreach ($failure->getMessages() as $name => $messages) {
            foreach ($messages as $message) {
                echo "Input '{$name}': {$message}" . PHP_EOL;
            }
        }
    }

In the method annotated with @OnFailure, you can access the validated messages with $failure->getMessages() and also you can get the object of the original method with $failure->getInvocation().

Various validation

If you want to have different validations for a class, you can specify the name of the validation like below

use Ray\Validation\Annotation\Valid;
use Ray\Validation\Annotation\OnValidate;
use Ray\Validation\Annotation\OnFailure;

class News
{
    /**
     * @Valid("foo")
     */
    public function fooAction($name, $address, $zip)
    {

    /**
     * @OnValidate("foo")
     */
    public function onValidateFoo($name, $address, $zip)
    {

    /**
     * @OnFailure("foo")
     */
    public function onFailureFoo(FailureInterface $failure)
    {

Other validation

If you need to implement complex validation, you can have another class for validation and inject it. And then call in the method annotated with the onValidate. You can also change your validation behavior by context with DI.