Sunday, January 9, 2022

[FIXED] Yii2 - Validating nested objects

Issue

Here's a question about a topic that Ive been thinking about for a while.

In Yii2, it is recommended generally to create Form Models for your requests. Rules are added to those models to validate the input. An example is the EntryForm in the Yii2 guide

<?php

namespace app\models;

use Yii;
use yii\base\Model;

class EntryForm extends Model
{
    public $name;
    public $email;

    public function rules()
    {
        return [
            [['name', 'email'], 'required'],
            ['email', 'email'],
        ];
    }
}

My problem is, when we have nested objects. An example is a form for creating Customer with multiple Branches. If Customer and Branch are two separate models, but both get submitted in one form, what is the best option for validating input from such a nested form. Bear in mind that here the input is nested. Example:

{
  "name": "customer",
  "vat_no": "12345678",
  "time_zone": 277,
  "category": 1,
  "email": "customer@mycustomer.com",
  "stores":[
    {
        "name": "store1",
        "phone": 1234567
    },
    {
        "name": "store2",
        "phone": 2345678
    }
]
}

Solution

For simple cases you may use one model and custom validator inside of your form model:

public function rules() {
    return [
        // ...
        ['stores', 'validateStores'],
    ];
}

public function validateStores() {
    $phoneValidator = new StringValidator(); // use real validators
    $nameValidator = new StringValidator(); // use real validators
    foreach ($this->stores as $store) {
        if (!$phoneValidator->validate($store['phone'], $error)) {
            $this->addError('stores', $error);
            return; // stop on first error
        }
        if (!$nameValidator->validate($store['name'], $error)) {
            $this->addError('stores', $error);
            return; // end on first error
        }
    }
}

validateStores() may be extracted to separate validator class, then you may also use EachValidator instead of foreach.


For more complicated nested models you should probably create separate StoreForm model for stores (so you will have nested form models), and call validate() on children.

/**
 * @var StoreForm[]
 */
public $stores;

public function rules() {
    return [
        // ...
        ['stores', 'validateStores'],
    ];
}

public function validateStores() {
    foreach ($this->stores as $store) {
        if (!$store->validate()) {
            $this->addError('stores', 'Stores config is incorrect.');
            return;
        }
    }
}


Answered By - rob006

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.